QueryDSL 값이 존재하는지 boolean 값만 반환하고 싶을때.

보통 쿼리에서 true/false 값만 반환한다고 하면..
해당 값이 존재하는지 아닌지의 여부만 판단하고 싶을 때 일 것이다.

여느 때와 같이 queryDSL 을 짜고 있었는데 ...

값이 존재하는지 아닌지 여부만 반환하고 싶은데,

CASE 문을 통해서 해야 하나?

public boolean hasRegisteredUser(Long custGroupSeq) {
    return jpaQueryFactory.select(
                new CaseBuilder()
                	.when(custGroup.count().gt(0).then(true)
                    .otherwise(false)
            )
            .from(custGroup)
            .where(custGroup.custGroupSeq.eq(custGroupSeq))
            .fetchOne();
}

 

처음엔 이렇게 쿼리를 짰었는데..

"Unboxing of * may produce 'NullPointerException

 

이런 찝찝한 경고문이 떠서 이 방법은 안전하지 않은 것 같았다.

 

답은 생각보다 간단하게 나왔다.

마지막에 fetch().size() > 0 을 붙여보고 깨달았다.

그냥 이렇게 하자.

 

참고문서 (나랑 비슷한 고민을 한 개발자가 있었다.)

https://stackoverflow.com/questions/64910252/querydsl-how-to-use-exist-to-return-boolean

 

QueryDsl how to use exist() to return boolean

I want to upgrade queryDsl from verision 3 to version 4. But i've an issue with exist() In QueryDsl 3 exist return a Boolean but in QUeryDSL 4 it return a BooleanOperation. In my case i really wan...

stackoverflow.com

 

 

결론

public boolean hasRegisteredUser(Long custGroupSeq) {
    return !jpaQueryFactory
            .selectFrom(custGroup)
            .where(custGroup.custGroupSeq.eq(custGroupSeq))
            .fetch().isEmpty();
}

 

 

쿼리가 훨~씬 깔끔해지고 안정성도 올라갔다!

그 어느 날과 같이 평범하게 개발을 하고 있던 날..

QueryDSL 에서 Projections 를 이용하여 DTO로 변환하는 과정을 개발하고 있었다.

그렇다.
오류가 발생했다.

 

class com.querydsl.core.types.QBean cannot access a member of class com.xxx(패키지명) with modifiers "protected"

 

 

대충 보아하니 접근제한자를 private 로 지정한 무언가에 대해 접근할 수 없어서 protected 로 변경하라는 거 같은데,

예상과 맞게도 문제의 원인은 다음과 같았다.

 

 

QueryDSL 프레임워크인 QBean이 클래스 com.xxx에서 접근수준이 protected 이상으로 선언된 멤버(필드 또는 메서드)에 접근하려고 하는데, 접근수준(접근제한자) 가 private로 되어 있어서 접근을 하지 못한 것 이었다.

 

 

그런데 왜 QueryDSL의 QBean이 접근수준이 private인 멤버에 접근하려고 한 것일까?

 

QueryDSL 구문을 짤때, 엔티티 자체로 반환하는 것을 막기 위해서 DTO로 변환해서 반환하곤 한다.

나도 그렇게 개발하고 있었고...

 

 

결론을 설명하기 전에, 엔티티를 DTO로 변환해주는 Projections 클래스에 대해서 알아보자.

먼저 QueryDSL에서 엔티티를 DTO로 변환하여 반환하는 방법은 Projections 클래스를 이용해서, 크게 3가지가 있다.

 

① Projections.bean(someDto.class, ...변환하고 싶은 setter가 존재하는 필드 나열)

즉, Setter 메서드를 통해서 접근하는 방법

import static com.querydsl.core.types.Projections.bean;

QBook qBook = QBook.book;

List<BookDTO> bookDTOs = queryFactory
    .select(bean(BookDTO.class, qBook.title, qBook.author.name.as("authorName")))
    .from(qBook)
    .fetch();

 

② Projections.fields(someDto.class, ...변환하고 싶은 필드 나열)

즉, DTO의 필드명을 직접 지정하여 필드에 접근하는 방법

import static com.querydsl.core.types.Projections.fields;

QBook qBook = QBook.book;

List<BookDTO> bookDTOs = queryFactory
    .select(fields(BookDTO.class, qBook.title, qBook.author.name.as("authorName")))
    .from(qBook)
    .fetch();

 

 

③ Projections.constructor(someDto.class, ...선언한 생성자의 필드 나열)

즉, DTO 클래스의 생성자를 호출하여 DTO 객체를 생성하는 방법

import static com.querydsl.core.types.Projections.constructor;

QBook qBook = QBook.book;

List<BookDTO> bookDTOs = queryFactory
    .select(constructor(BookDTO.class, qBook.title, qBook.author.name.as("authorName")))
    .from(qBook)
    .fetch();

 

 

 

기본적으로 Projections는 인자 값이 없는 기본 생성자와 프로퍼티의 Getter/Setter의 접근을 반드시 필요로 하는데, 기본 생성자를 Protected 로 지정해버리면 해당 메서드에 접근할 수 없어 오류가 나는 것이다. (보통 getter/setter는 public 으로 지정하니, 문제가 없을 것이라 생각한다.)

 

 

결론

 

 

DTO 의 @NoArgsConstructor 는 접근레벨=PUBLIC 으로 선언하자.


엔티티를 선언할때, 기본생성자를 지정할때 PROTECTED로 선언하는 것이 보안상 안전하기 때문에 PROTECTED 로 선언하던 습관을 DTO에도 적용하니까 문제가 발생했던 것이다.

사건의 발단

React 단에서 boolean 컬럼 값이 포함된 JSON 객체를 담아 POST 요청을 보냈고,
Spring 에서는 해당 객체를 boolean 컬럼이 포함된 DTO 객체로 @RequestBody 응답을 받았다.

그런데...., 이상하게도!

다른 모든 컬럼 값은 제대로 받아지는데, boolean 값만 자꾸 기본값인 false로 받아지는 것이었다.

무언가.. boolean 값만 파라미터 바인딩에 문제가 생긴 것이 분명했다.

원인은 뭐였을까?

 

DTO 내부에 boolean 컬럼 값의 경우 `is` 접두사로 시작했기 때문이었다.

 

 

이게 왜 안될까?

 

좀더 본질적인 원인은 Lombok 의 @Getter 동작방식에 있었다.

다음은 Lombok 공식문서에서 @Getter @Setter 에 관한 내용이다.

 

 

https://projectlombok.org/features/GetterSetter

 

@Getter and @Setter

 

projectlombok.org

 

 

For boolean fields that start with is immediately followed by a titlecase letter, nothing is prefixed to generate the getter name.

Any variation on boolean will not result in using the is prefix instead of the get prefix; for example, returning java.lang.Boolean results in a get prefix, not an is prefix.

 

 

해석해보면 다음과 같다.

 

boolean 타입의 필드가 is로 시작하고 그 다음에 대문자가 오는 경우, getter 메서드 이름에 아무 것도 접두사로 붙이지 않습니다. 즉, 필드 이름 그대로 사용합니다.

boolean 타입이 아닌 java.lang.Boolean과 같은 타입을 반환하는 경우, getter 메서드는 is가 아닌 get 접두사를 사용합니다.

 

 

 

정리하자면,

 

Lombok 에서는 @Getter @Setter 어노테이션을 이용해서 getter/setter 메소드를 자동으로 생성해주는데, 이때 boolean 타입은 getter 바인딩 시에 `get` 이 아닌 `is` 접두사를 붙여서 getter 메서드를 생성해주기 때문에 내부적으로 is 가 두 번 호출되는 이상한 네이밍의 메서드가 생성되었기 때문에 DTO가 인식할 수 없었던 것이다.

 

 

JSON 필드명과 DTO 필드명이 일치하지 않으면 Jackson이 JSON 필드를 DTO 필드에 매핑하지 못한다. 예를 들어, JSON 필드가 "isSomeBoolean": true이고 DTO 필드가 private boolean isSomeBoolean;이라면, Jackson은 이를 자동으로 매핑하지 못한다.

 

 

Java Bean 규약에 따르면 boolean 타입의 변수에 대한 getter 메서드는 is로 시작하고, setter 메서드는 set으로 시작해야 한다. 하지만 필드 이름 자체에 is를 포함시켜버리면 규약에 맞지 않게 되면서, 바인딩이 잘 되지 않는 상황이 발생하는 것이다.

Spring Security 란?

Spring 에서 제공하는 강력한 보안 기능이다. 특히, 커스텀이 가능한 인증(Authentication)과 인가(Authorization)의 두 가지 주요 기능을 제공한다.

 


인증 (Authentication)

사용자가 "누구"인지 확인하는 과정이다. 자신을 증명하기 위해서 자격 증명(ex. 사용자 이름이나 비밀번호)가 필요하다.

 

 

인가 (Authorization)

인증된 사용자가 특정 자원에 접근하거나 특정 작업을 수행할 수 있는 "권한"을 가지고 있는지 결정하는 것이다.

  • 경로 기반 인가 (특정 HTTP 경로)
  • 메소드 시큐리티 (Java 메소드 호출)
  • 표현식 기반 접근 제어 (보다 복잡한 규칙을 표현식으로 정의)

 

 

 

 

 

 

 

로그인 방식에는 크게 두가지로 나눌 수 있다. 바로 상태를 유지하는 인증(Stateful Authentication)상태를 유지하지 않는 인증(Stateless Authentication)이다.

 

상태를 유지하는 인증 (Stateful Authentication)

서버가 사용자의 로그인 상태를 확인할 때 쿠키나 세션을 사용하는 방식을 말한다. 사용자의 인증 상태는 서버에서 관리된다.

  • 폼 기반 로그인 방식 (Form-based Authentication, 가장 일반적)
    가장 일반적으로 사용되며, 사용자가 웹 폼(Form)을 통해 로그인 정보(아이디, 비밀번호)를 입력하는 방식이다. 서버는 이 정보를 받아 인증을 수행하고, 성공적인 인증 후에는 사용자의 세션을 생성하여 로그인 상태를 유지한다.

  • LDAP (Lightweight Directory Access Protocol, 내부 네트워크 또는 기업 환경에서 주로 쓰임)
    폼 기반 또는 다른 인증 방식을 통해 LDAP 서버에 접근하여 사용자의 자격 증명을 확인하는 방식이다.



상태를 유지하지 않는 인증 (Stateless Authentication)

상태를 유지하지 않는 인증은 서버가 사용자의 인증 상태를 세션에 저장하지 않는다. 대신, 클라이언트가 서버로 요청을 보낼 때마다 인증 정보를 포함하여 자격을 증명한다.

 

  • HTTP 기반 로그인 방식(HTTP Basic Authentication)
    HTTP 헤더사용자의 이름과 비밀번호를 Base64로 인코딩하여 전송한다. 서버는 매 요청마다 이 정보를 확인하여 인증을 수행한다. 인증정보는 저장되지 않으며, 각 요청은 독립적으로 처리된다.
  • OAuth2 (구글, Facebook API)
    외부 서비스 제공자를 통해 인증하는, 제 3자가 끼어드는 인증 방식이다. 사용자가 서비스 제공자를 통해 인증하고, 인증에 성공하면 토큰을 받는다. 이 토큰은 사용자의 요청마다 서버에 제출되어 인증을 위해 사용된다.

  • JWT (JSON Web Tokens)
    사용자가 인증하면, 서버는 JSON 형태의 웹 토큰을 생성하고 반환한다. 이 토큰은 사용자가 서버에 요청을 보낼 때마다 헤더에 포함시켜 전송한다. 토큰 자체에 사용자의 인증 정보가 포함되어 있어, 서버는 세션을 유지할 필요가 없다.

 

 

오늘은 스프링에서 자주 사용하는 Model 객체와 ModelMap 객체에 대해서 포스팅 해보겠다.

Model만 있는 줄 알았는데 ModelMap 이라는 객체도 있었다.


1. 스프링에서 Model 객체란?

스프링에서는 Controller 의 메소드들이 받는 파라미터(Argument)들을 관리하는 ArgumentResolver 가 존재한다. (정확하게는 HandlerMethodArgumentResolver 클래스에서 Argument들을 관리하는 역할을 한다.) 여기에 등록되어 있는, 자주 사용되는 파라미터 중 하나라고 할 수 있다. 주로 데이터를 관리하기 위한 파라미터이다. (MVC 패턴에서 M도 Model의 의미를 지니는 데, 그와 비슷하게 Model 객체도 Data를 전달하고 관리하기 위한 Argument 이다.)

 

Model과 ModelMap 두 객체는 기본적으로 상당히 비슷한 기능을 제공한다.

addAttribute(...) 메소드를 사용해서 다루고 싶은 데이터를 넣고,

getAttribute(...) 메소드를 이용해서 넣었던 데이터들을 빼서 사용할 수 있다.

 

 

2. Model  객체는 interface 이다.

public interface Model {
    
    Model addAttribute(String attributeName, @Nullable Object attributeValue);

	...
    
    @Nullable
    Object getAttribute(String attributeName);
}

Model 객체는 인터페이스로 구현되어 있기 때문에 map 인터페이스 구현체로의 변경이 쉬워서 코드의 유연성이 높다는 특징이 있다. 예를들어 TreeMap 이나 HashMap 으로도 호출할 수 있다.
(참고로, 페이지 redirect 시에 RUL parameter 정보들을 쉽게 관리하게 해주는 RedirectAttributes 인터페이스도 Model 인터페이스를 상속받는다.)

3. ModelMap 객체는 Class 이다. (LinkedHashMap)

public class ModelMap extends LinkedHashMap<String, Object> {

    public ModelMap addAttribute(String attributeName, @Nullable Object attributeValue) {
        Assert.notNull(attributeName, "Model attribute name must not be null");
        this.put(attributeName, attributeValue);
        return this;
    }

	...

    @Nullable
    public Object getAttribute(String attributeName) {
        return this.get(attributeName);
    }
}


구체적인 클래스로 구현되어 있기 때문에, LinkedHashMap 을 상속받은 클래스로 사용이 가능하다.

 

LinkedHashMap 클래스의 경우 HashMap 클래스를 상속받고 있는데, 이 둘(LinkedHashMap 과 HashMap)의 차이점은 순서의 유무이다. HashMap의 경우 순서 고려 없이 Entry에 Node 데이터를 집어 넣지만, LinkedHashMap의 경우 before, After Entry를 지정하여 put 한 순서데로 데이터를 저장한다는 차이점이 있다.

 

4. HandlerMethodArgumentResolver에 Model객체는 어떻게 등록되어 있을까?

public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
    public ModelMethodProcessor() {
    }

	// parameter 타입 특정하기 (간소화함)
    public boolean supportsParameter(MethodParameter parameter) {
        return Model.class;
    }

	// parameter 로써 역할 작성하기
    @Nullable
    public Object resolveArgument(...) throws Exception {
        ...
    }

	// return 타입 특정하기 (간소화함)
    public boolean supportsReturnType(MethodParameter returnType) {
        return Model.class;
    }

	// return 값으로써 역할 작성하기
    public void handleReturnValue(...) throws Exception {
        ...
    }
}

 

Model.class 의 경우 ModelMethodProcessor 클래스로 구현되어 있다. (참고로 함께 상속되어 있는 HandlerMethodReturnValueHandler 는 파라미터가 아니라 반환할 때의 데이터를 관리하기 위한 핸들러이다.)

 

기본적으로 스프링은, 컨트롤러의 파라미터들을 가져올 때 HandlerMethodArgumentResolver 를 상속받은 클래스들을 탐색한다. 마찬가지로 컨트롤러의 반환값들을 가져올 때는 HandlerMethodReturnValueHandler 인터페이스를 상속받은 클래스들을 탐색한다.

 

여기에 등록이 되어있어야 스프링이 파라미터나 반환값으로 인식할 수 있는 것이다.

프론트 단 기초 구성

1. VScode 를 이용하여 front 프로젝트를 생성할 폴더를 열어준 후 다음 명령어를 입력한다.

# npx create-react-app [프로젝트 명]
npx create-react-app front-dk

위 이미지와 같이 "happy hacking!" 이 출력되면 정상적으로 프로젝트가 만들어진 것이다.

 

 

 

2. 서버 검증

# 생성한 프로젝트로 이동
cd front-dk

# 서버 작동
npm start

 

 

 

 

백엔드 단 기초 구성

1. 인텔리 제이를 실행시켜서 new-project를 클릭한다. 그리고 사진과 같이 설정한다.

 

2. Test Controller를 만들어준다.

package com.opensource.backdk;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @GetMapping("/hello")
    public String main() {
        return "정상 출력";
    }

}

 

 

본격적으로 프론트단과 백엔드단 연동하기

1. Front :: package.json > proxy 추가

"proxy": "http://localhost:8080",

 

2. Prompt 에 다음과 같은 명령을 입력하여 백엔드 단의 값을 가져오는지 확인

curl localhost:8080/hello

 

3. App.js 수정 : React의 상태관리를 이용하여 값 가져오기

import React, { useState, useEffect } from 'react';
import './App.css';

function App() {

  const [message, setMessage] = useState("");

  useEffect(() => {
    fetch('/hello')
      .then(response => response.text())
      .then(message => {
        setMessage(message);
      });
  }, [])

  return (
    <div className="App">
      <header className="App-header">
          {message}
      </header>
    </div>
  );
}

export default App;

 

 

 

"연동 성공"

 

참고 : https://bug41.tistory.com/121

 

SpringBoot (Gradle Project) 에서 React 사용하기 (1)

* 시작하기전에 PC에 node, npm 은 필수로 설치되어있어야합니다. React 설치 준비 위해서는 아래 링크 참고하시면 됩니다. [React] ReactJS 입문 준비 https://blog.naver.com/bugkingus/221718414687 스프링부트..

bug41.tistory.com

 

스프링을 한마디로 표현하자면,

Spring은,
JSON 형태의 data를 반환해주는 서버 프레임워크이다.

 

먼저 웹 서버는 두가지 형태를 반환해주는데,

하나가 html, css 와 같은 프론트엔드 파일이고

또 하나는 JSON 형태의 그냥 "데이터"를 반환해준다.

이 때 스프링은 후자의 경우를 다루는 서버 프레임워크인 것이다.

 

예를들어, 만약에 Rest Controller 에서 Class 를 반환하게 된다면, Spring은 컨트롤러에서 반환된 Class 정보를 자기가 알아서 JSON으로 변환하여 웹화면에 보여준다.

 

이때 Rest Controller란?

Rest : 서버의 응답이 JSON 형식이라는 의미이며, html이나 css를 반환할 때는 그냥 Controller이다.

Controller : 요청한 값을 반환해주는 자동응답기이다.

 

따라서, Rest Controller 는 JSON으로 응답하는 자동응답기를 의미한다.

Create

abstract Timestamp 경우 밑에 3개 어노테이션 기억하기

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)

# 그리고 메인 함수 어노테이션도
@EnableJpaAuditing

 

Read

 

Update

response에서 속성 안나눠줌. 속성 안나눠주면 변경할 때 특정 id 찾기 불가능

ajax 에서 data 넣어줄 때 JSON.stringyfy() 안해줌

success response 됐을 때 새로고침(window.location.reload)안해줌. 변경안된줄

 

Delete

주소 articles 인데 자꾸 article 이라고 단수형으로 착각해서 작성함

https://codechacha.com/ko/java-parse-json/

 

Java - JSON을 파싱하는 가장 쉬운 방법

org.json 라이브러리를 사용하여 JSON을 파싱하는 방법을 소개합니다. JSON은 Object, Array, Key-Value 타입으로 이루어져 있으며 Value는 String, Int, Long, Boolean 등의 타입을 지원합니다.

codechacha.com

jar 파일이란 java archiv이다. 라이브러리 묶음이다.

 

https://memories95.tistory.com/127

 

[Spring Boot] 외부 jar 사용하기 (FileUpload, Json)

만약 Webjar에서 제공되지 않는 외부 jar파일을 사용하기 위해서는 어떻게 해야할까? : 답은 단순하게 직접 추가해서 사용하는 수밖에 없다. Gradle에서는 build.gradle 파일에 외부 중앙 저장소와 라이

memories95.tistory.com

jar 파일을 가져오는것은 Maven Repository에서 가져와서 build.gradle에 로드해주는 것같다.

 

https://blog.naver.com/dktmrorl/222169487095

 

[Java] 메이븐 저장소(Maven Repository) 개념

메이븐 저장소(Maven Repository) 개요 메이븐(Maven)은 빌드 툴이지만 라이브러리 의존성 관리 툴로 더...

blog.naver.com

마지막으로 Maven Repository 에서 JSON 라이브러리 가져오기

build.gradle > dependency 에 복붙하면 된다.

https://mvnrepository.com/artifact/org.json/json

 

22.03.10

AWS의 RDB라는 서비스는 나의 데이터를 원격서버에 저장해주는 서비스를 말한다. H2 database 같은 경우에는 일시적으로 서버를 켰을 때만 데이터가 저장되는 휘발성의 DB서버였지만, AWS의 RDB를 열어주면 서버를 끄더라도 DB가 날아가지않고 저장되어 있다. - 구매하고 설정하는 것은 또 따로 더 공부해야 할 것 같다.

 

og 태그란, 내가 만든 웹사이트의 주소를 공유했을 때 썸네일로 뜨는 카드를 일컫는다.

 

EC2 서비스는 내 도메인 서버 컴퓨터를 만들어주는 서비스이다. 이 때 EC2는 주소 도메인을 사는 것이 아니라 IP주소만을 사는 것이다. 도메인은 따로 구매해서 연결해주어야 한다.

 

SSH 는 Secure Shell Protocol의 약자로, 다른 컴퓨터에 접속할 때 쓰는 프로그램이다. 접속할 컴퓨터의 22번 방이 열려있어야 접속이 가능하다. AWS EC2의 경우 22번 포트는 항상 열어놓기에 항상 접속할 수 있는 상태이다.

 

포트포워딩이란, 포트 번호를 뒤에 입력하지 않아도 자동으로 :80 포트(통상적 기본 포트)에 접속하도록 도와주는 것을 말한다. 

 

nohup이란, 서버를 종료해도 계속해서 서버가 돌아가게 하는 것을 말한다. 서버를 킬 때 nohup 과 & 로 킨 이후에, 종료시킬 때는 번호를 알아낸 다음에 kill 명령어로 종료시킨다. 

 

내가 만든 웹사이트

http://bubblebub.shop/

 

완주 수강증

https://s3.ap-northeast-2.amazonaws.com/materials.spartacodingclub.kr/cert/img/new_cert_61fc425aac0be6d3c1afc26b_61f5106d2eb23534dcdc681a.jpg

+ Recent posts