728x90

실무에서 조회기능을 메인으로 개발하고 있다보니 JPA 데이터 조회 최적화에 항상 관심을 가지고 있다. 특히 엔티티 설계시 엔티티간의 연관관계에 대해 중점적으로 설계하였고 이를 실무 개발에 적용하고자 하였다. 

이에 해당 내용을 명확히 이해하고 적용하고자 JPA 프록시와 엔티티 연관 관계에 대해서 정리해보고자 한다.

그에 앞서 Proxy라는 것을 알아보자.
백엔드 개발자 아니 개발자 라면 Proxy Sever의 개념을 알고 있을 것이다. 

Proxy Server (https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9D%EC%8B%9C_%EC%84%9C%EB%B2%84)

Proxy Server의 개념에 대해 깊이 있게 들어가게 되면 글의 길이가 길어지니 간단히간단히 이야기하자면 Proxy Server는 실 서버와 데이터의 도메인 및 IP를 외부에 오픈하기 전 중계 역할을 한다고 생각하면 된다.

예를 들자면 내가 B.com이라는 도메인으로 데이터를 처리하고 있다고 해보자. 하지만 외부 인터넷망에 B.com 이라는 도메인을 오픈하게 될 경우 보안상으로 이슈가 생길 수 있다.
이를 A.com이라는 도메인으로 서비스를 제공하고 Proxy 설정을 통해 A.com에 요청을 B.com으로 연결해준다면 외부 인터넷망에 B.com의 정보를 오픈할 필요가 없어 보안성이 향상 될 수 있다.


각설하고 이제 JPA 프록시에 대해 알아보자.
밑의 코드는 음식 FoodEntity (이하 Food) 와 음식점 StoreEntity (이하 Store) 의 클래스 이며 두 엔티티의 관계는 N : 1 즉 @ManyToOne 관계이다.

/*
Food Entity
*/
package com.jpastudy.ms.domain.Entity;


import lombok.Getter;
import javax.persistence.*;

@Getter
@Entity
@Table(name = "tb_test_food")
public class FoodEntity {

    @Id
    @Column(name="food_id")
    private String foodId;
    private String foodName;
    private String foodCalorie;


    @ManyToOne(fetch = FetchType.LAZY) // 지연 로딩
    @JoinColumn(name = "store_id")
    public StoreEntity storeEntity;

}
/*
Store Entity
*/
package com.jpastudy.ms.domain.Entity;


import lombok.Getter;

import javax.persistence.*;


@Getter
@Entity
@Table(name = "tb_test_store")
public class StoreEntity {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private String storeId;
    private String storeName;
    private String storeNumber;
    private String address;
    

}

Table View



Food 와 Store는 N : 1 의 연관관계가 맺어진 것은 이해 했다. 그렇다면 Food를 조회 할 때 항상 Store의 데이터를 조회 해야 할까?
난 Food의 데이터만을 활용하여 뒷단 로직을 개발하고자 하는데 Store 데이터까지 모두 조회하게 되면 이는 리소스 낭비 일 것 이다.


각설하고 코드로 살펴보자. 우선 Food의 값만 조회해보자.

    @Test
    public void proxyTest(){

        FoodEntity foodEntity = em.find(FoodEntity.class,5L); // food_id 값이 5L인 데이터를 찾는다.
        System.out.println("======= 쿼리 전송 =======");

        System.out.println("Food ID : " +foodEntity.getFoodId());
        System.out.println("Food Name : " + foodEntity.getFoodName());
        System.out.println("Food Calorie : " + foodEntity.getFoodCalorie());

        System.out.println("======= 쿼리 결과 =======");
        em.close();
    }

항정살이 먹고 싶다.

깔끔하다.
Food값을 조회하여 ID, Name, Calorie 값들을 가져왔다. 

그렇다면 그렇다면 Food와 연관관계 매핑되어 있는 Store의 값을 조회해보자. 

  @Test
    public void proxyTest(){

        FoodEntity foodEntity = em.find(FoodEntity.class,5L); // food_id 값이 5L인 데이터를 찾는다.
        System.out.println("======= 쿼리 전송 =======");

        System.out.println("Food ID : " +foodEntity.getFoodId());
        System.out.println("Food Name : " + foodEntity.getFoodName());
        System.out.println("Food Calorie : " + foodEntity.getFoodCalorie());
        System.out.println(foodEntity.getStoreEntity().getClass());


        System.out.println("======= 쿼리 결과 =======");

        System.out.println("///////////////////////////////");
        System.out.println("///////////////////////////////");

        System.out.println("======= Store 데이터 =======");
        System.out.println("Store ID : " + foodEntity.getStoreEntity().getStoreId());
        System.out.println("Store Name : " + foodEntity.getStoreEntity().getStoreName());
        System.out.println("Store Address : " + foodEntity.getStoreEntity().getAddress());
        System.out.println("Store Number : " + foodEntity.getStoreEntity().getStoreNumber());

        em.close();
    }

 

 

 

store 조회

 

 

여기서  Store 데이터를 조회해오는 것을 보면

Food Name : 항정살
Food Calorie : 500
class com.jpastudy.ms.domain.Entity.StoreEntity$HibernateProxy$w7bUtIS5
======= 쿼리 결과 =======
///////////////////////////////
///////////////////////////////
======= Store 데이터 =======
Store ID : 1 
2022-01-31 20:41:55.592 DEBUG 55151 --- [           main] org.hibernate.SQL                        : 
    select
        storeentit0_.store_id as store_id1_1_0_,
        storeentit0_.address as address2_1_0_,
        storeentit0_.store_name as store_na3_1_0_,
        storeentit0_.store_number as store_nu4_1_0_ 
    from
        tb_test_store storeentit0_ 
    where
        storeentit0_.store_id=?
2022-01-31 20:41:55.594  INFO 55151 --- [           main] p6spy                                    : #1643629315594 | took 0ms | statement | connection 2| url jdbc:mysql://localhost/test?serverTimezone=UTC
select storeentit0_.store_id as store_id1_1_0_, storeentit0_.address as address2_1_0_, storeentit0_.store_name as store_na3_1_0_, storeentit0_.store_number as store_nu4_1_0_ from tb_test_store storeentit0_ where storeentit0_.store_id=?
select storeentit0_.store_id as store_id1_1_0_, storeentit0_.address as address2_1_0_, storeentit0_.store_name as store_na3_1_0_, storeentit0_.store_number as store_nu4_1_0_ from tb_test_store storeentit0_ where storeentit0_.store_id=1;
Store Name : 제주돌돗촌
Store Address : 경기도 고양시
Store Number : 0311231234



1. Store ID : 기존의 영속성 컨텍스트 값을 가져온다.
2. Store Name, Address, Number는 Store값을 조회시 Store ID값을 활용한 조회 쿼리가 나간다.

그리고 이 콘솔창에서 JPA Proxy를 확인 할 수 있다. (line 3)

class com.jpastudy.ms.domain.Entity.StoreEntity$HibernateProxy$w7bUtIS5

Food를 조회했을 때 Store 객체는 'HibernateProxy' 객체 형체로 조회 되며 Store Proxy 초기화 및 호출시 영속성 컨텍스트를 조회하고 값이 없으면 DB에 쿼리를 날려 실제 값을 얻게 된다.

또한 Proxy값이 변화하는 것이 아닌 Proxy 객체를 통해서 실제 Entity로 접근 하는 것이다.


그리고 Food 객체를 Detach, Clear의 메소드를 통해서 준영속성 컨텍스트 상태로 변경 될 경우 해당 프록시 값 초기화 및 호출시  다음과 같은 에러 ( could not initialize proxy  - no Session) 가 발생한다.

session이 없다.


그리고 엔티티간 연관관계에서 Annotation으로 fetch 타입의 지연 로딩과 지연 로딩을 설정할 수 있다.

1. 지연 로딩


지연 로딩은 다음과 같이 설정할 수 있으며  (fetch = FetchType.LAZY)

@ManyToOne(fetch = FetchType.LAZY) // 지연로딩
@JoinColumn(name = "store_id")
public StoreEntity storeEntity;

위의 데이터 쿼리 예시 처럼  Store 데이터 조회 시 쿼리가 한번 더 나가게 된다.



2. 즉시 로딩

즉시 로딩은 다음과 같이 설정할 수 있다. (fetch = FetchType.EAGER)

@ManyToOne(fetch = FetchType.EAGER) // 즉시로딩
@JoinColumn(name = "store_id")
public StoreEntity storeEntity;

이 경우에는 Food 조회 시 연관관계에 있는 Store 데이터 값들을 프록시가 아닌 실제 객체 값들을 조회하는 쿼리가 나가게 된다.

즉시 로딩

Food Name : 항정살
Food Calorie : 500
class com.jpastudy.ms.domain.Entity.StoreEntity
======= 쿼리 결과 =======
///////////////////////////////
///////////////////////////////
======= Store 데이터 =======
Store ID : 1
Store Name : 제주돌돗촌

더 나아가  line3에서 Proxy가 아닌 실제 Entity값을 가져오는 것을 확인 할 수 있다.

 

JPA 김영한님의 강의에서는 무조건 실무에서 Entity간의 관계는 지연로딩 (fetch = FecthType.LAZY) 설정으로  적용 되어야 한다고 강조했다.


그 이유는 AEnttiy.class 에 BEntity, CEntity 여러개의 연관관계가 매핑되어 있을 경우 즉시 로딩 설정을 하게 되면 (fetch = FecthType.EAGER) 그 연관관계 전체에 대한 쿼리가 나가고 이는 성능 이슈가 발생할 수 있기 때문이다.


즉 어떤 쿼리가 나갈지 모르기 때문이다.


끝.


현재 실무에서 QueryDSL을 이용하여 전체 Entity값을 DTO로 조회하고 있어 Entity간 연관관계 속성을 쓰고 있지 않다.
이번 포스팅을 통해 JPA Proxy 개념을 다시 한번 체크하고 조회 로직의 성능 향상을 위해 해당 내용을 적용할 수 있는지 검토 및 적용해 볼 예정이다.

 

 

Quote

에르주 tistory

728x90

실무에서 JPA를 활용하다보면 Entity 생성시 
@NoargsConstructor (access = AccessLevel.PROTECTED) 이라는 Annotation을 붙여서 개발을 하게 된다. 이에 조금 더 정확히 이해하고자 이번 블로그 글로 언급하고자 한다.

이 글을 읽는 분들은 모두 알다시피 Lombok 라이브러리에는 생성자 관련한 두개의 어노테이션이 존재한다.

  • AllargsConstructor
  • NoArgsConstructor

 

1. AllargsConstrutor


말 그대로 '모든 매개변수 생성자'인 것 처럼 해당 클래스 내의 모든 변수값을 가진 생성자를 자동으로 만들어 준다.

@Setter
@Getter
@AllArgsConstructor
public class testDto {

    private String id;
    private String userName;
    private String Age;
    private String address;


}


// 서로 같다.

@Setter
@Getter
public class testDto {

    private String id;
    private String userName;
    private String Age;
    private String address;

    public testDto(String id, String userName, String age, String address) {
        this.id = id;
        this.userName = userName;
        this.Age = age;
        this.address = address;
    }
}

앗 생성자가 이미 생성되었다.

 

2. NoArgsConstructor


해당 어노테이션의 의미는 말 그대로 "아무런 매개변수가 없는 생성자" 이다.

@Setter
@Getter
@NoArgsConstructor
public class testDto {

    private String id;
    private String userName;
    private String age;
    private String address;
    
}

// 같다.

@Setter
@Getter
public class testDto {

    private String id;
    private String userName;
    private String age;
    private String address;


    public testDto() {
    }
}

앗 이미 생성되었다..


특히 생성자 접근 Level을 다음과 같은 설정값으로 줄 수 있다.

access = AccessLevel.PROTECTED 
access = AccessLevel.PRIVATE

자바 개발자라면 모두 아는 public / protected / private 접근 제한으로 

접근 Level 접근 할 수 없는 클래스
protected 다른 패키지에 소속된 클래스 (상속 제외)
private 모든 외부 클래스

 


간단히 @AllargsConstructor @NoArgsConstructor에 대해서 알아봤고 이제 이 포스트의 진짜 주제에 대해 알아보자.

Entity Class에는

@NoArgsConstructor(access = AccessLevel.PROTECTED)을 사용할까?
@NoArgsConstructor(access = AccessLevel.PRIVATE)는 안되는 건가?

 


주저리 주저리 설명하기 보다 코드로 살펴보자.

1. @NoArgsConstructor(access = AccessLevel.PROTECTED)

PROTECTED

 

2. @NoArgsConstructor(access = AccessLevel.PRIVATE)

PRIVATE 

 

AccessLevel을 PRIVATE로 설정했을 경우에는 다음과 같은 에러가 발생한다.

Class 'StoreEntity' should have [public, protected] no-arg constructor 

 

JSR-000338의 Entity 설명을 보면

The entity class must have a no-arg constructor. The entity class may have other constructors as well.
The no-arg constructor must be public or protected.
// Entity 클래스는 매개변수가 없는 생성자의 접근 레벨이 public 또는 protected로 해야 한다.

... 

An instance variable must be directly accessed only from within the methods of the entity by the entity instance itself.
// 인스턴스 변수는 직접 접근이 아닌 내부 메소드로 접근해야 한다.

이에 따라 @Entity 선언 후 @NoArgsContructor에서 접근 Level에 따라 경고가 발생하고 있는 것이다. (Complie시 오류 검출 안됌)
또한 Entity 클래스 인스턴스 변수는 직접 접근이 아닌 내부 메소드로 접근해야 한다. (ex. Getter, Setter 사용)


결과부터 이야기하자면 그 원인은

Entity Proxy 조회

때문이다.

Proxy에 대한 설명은 해당 링크 참고 https://erjuer.tistory.com/105

 

[JPA] 프록시(Proxy)와 엔티티 연관 관계(LAZY, EAGER)

실무에서 조회기능을 메인으로 개발하고 있다보니 JPA 데이터 조회 최적화에 항상 관심을 가지고 있다. 특히 엔티티 설계시 엔티티간의 연관관계에 대해 중점적으로 설계하였고 이를 실무 개발

erjuer.tistory.com

 


예시를 들었던 음식 엔티티 클래스 FoodEntity(이하 Food), 음식점 클래스 StoreEntity (이하 Store)로 살펴보자.
Food와 Store는 N : 1 관계이다. 

총 4개의 경우의 수를 살펴보자.

  1. Food와 Store 모두 (access = AccessLevel.PROTECTED)
  2. Food : (access = AccessLevel.PROTECTED) , Store  : (access = AccessLevel.PRIVATE)
  3. Food : (access = AccessLevel.PRIVATE) , Store  : (access = AccessLevel.PROTECTED)
  4. Food와 Store 모두 (access = AccessLevel.PRIVATE)


이며 조회를 위한 전제 조건은 Proxy를 활용할 것이므로

  • @ManyToOne(fetch = FetchType.LAZY)

이다.

조회 Test는 다음과 같은 로직으로 되어 있다.

@SpringBootTest
public class ProxyTest {


    @Autowired
    private EntityManager em;



    @Test
    @Transactional
    public void proxyTest(){

        FoodEntity foodEntity = em.find(FoodEntity.class,5L); // food_id 값이 5L인 데이터를 찾는다.
        System.out.println("======= 쿼리 전송 =======");

        System.out.println("Food ID : " +foodEntity.getFoodId());
        System.out.println("Food Name : " + foodEntity.getFoodName());
        System.out.println("Food Calorie : " + foodEntity.getFoodCalorie());
        System.out.println(foodEntity.getStoreEntity().getClass());


        System.out.println("======= 쿼리 결과 =======");

        System.out.println("///////////////////////////////");
        System.out.println("///////////////////////////////");

        System.out.println("======= Store 데이터 =======");
        System.out.println("Store ID : " + foodEntity.getStoreEntity().getStoreId());
        System.out.println("Store Name : " + foodEntity.getStoreEntity().getStoreName());
        System.out.println("Store Address : " + foodEntity.getStoreEntity().getAddress());
        System.out.println("Store Number : " + foodEntity.getStoreEntity().getStoreNumber());

        em.close();
    }

 

1.  Food와 Store 모두 (access = AccessLevel.PROTECTED) : 정상 동작

이상 없이 조회된다.

정상 조회

======= 쿼리 전송 =======
Food Proxy ? Entity : class com.jpastudy.ms.domain.Entity.FoodEntity // 실제 Entity
Food ID : 5
Food Name : 항정살
Food Calorie : 500
Store Proxy ? Entity : class com.jpastudy.ms.domain.Entity.StoreEntity$HibernateProxy$LDEXNcvd
// Proxy
======= 쿼리 결과 =======

콘솔에서 FoodEntity 조회시에는 실제 Entity가 조회 되지만 StoreEntity 조회시 HibernateProxy 클래스를 사용하는 것을 확인 할 수 있다.

 

2. Food : (access = AccessLevel.PROTECTED) , Store  : (access = AccessLevel.PRIVATE) : 오류 발생

앗 오류가 발생했다.

오류 로그을 살펴보면

HHH000143: Bytecode enhancement failed because no public, protected or package-private default constructor was found for entity: com.jpastudy.ms.domain.Entity.StoreEntity. Private constructors don't work with runtime proxies!


Food 조회시 Store는 Proxy 객체로 조회 되는데 Store의 접근 권한이 PRIVATE이므로 Proxy 객체 생성하는 로직에서 오류가 발생하였다.


3. Food : (access = AccessLevel.PRIVATE) , Store  : (access = AccessLevel.PROTECTED) : 정상 동작

정상 동작

해당 코드는 정상 작동 한다.

2022-02-06 11:27:07.204 DEBUG 18716 --- [           main] org.hibernate.SQL                        : 
    select
        foodentity0_.food_id as food_id1_0_0_,
        foodentity0_.food_calorie as food_cal2_0_0_,
        foodentity0_.food_name as food_nam3_0_0_,
        foodentity0_.store_id as store_id4_0_0_ 
    from
        tb_test_food foodentity0_ 
    where
        foodentity0_.food_id=?
2022-02-06 11:27:07.245  INFO 18716 --- [           main] p6spy                                    : #1644114427245 | took 17ms | statement | connection 2| url jdbc:mysql://localhost/test?serverTimezone=UTC
select foodentity0_.food_id as food_id1_0_0_, foodentity0_.food_calorie as food_cal2_0_0_, foodentity0_.food_name as food_nam3_0_0_, foodentity0_.store_id as store_id4_0_0_ from tb_test_food foodentity0_ where foodentity0_.food_id=?
select foodentity0_.food_id as food_id1_0_0_, foodentity0_.food_calorie as food_cal2_0_0_, foodentity0_.food_name as food_nam3_0_0_, foodentity0_.store_id as store_id4_0_0_ from tb_test_food foodentity0_ where foodentity0_.food_id=5;
======= 쿼리 전송 =======
Food Proxy ? Entity : class com.jpastudy.ms.domain.Entity.FoodEntity  // 실제 Entity 객체
Food ID : 5
Food Name : 항정살
Food Calorie : 500
Store Proxy ? Entity : class com.jpastudy.ms.domain.Entity.StoreEntity$HibernateProxy$3YCd837F
// Proxy 객체

그 이유는 Food는 em.find를 통해 실제 Entity 객체로 조회 되었기 때문이다. 그리고 Store는 Protected이므로 정상적으로 Proxy 객체가 생성된 것을 확인할 수 있다.


4. Food와 Store 모두 (access = AccessLevel.PRIVATE) : 오류 발생

 

Food는 실제 Entity 객체가 생성되고 조회 쿼리가 발생하였으나 Store는 Private 선언으로 Proxy 객체 생성에 오류가 발생하였다.



그렇다면 궁금한 것 하나 Food 또한 Proxy로 조회하는 getReference를 활용하면 어떻게 될까?

FoodEntity foodEntity = em.getReference(FoodEntity.class,5L); // food_id 값이 5L인 데이터를 찾는다.

 

오류 발생

당연하게도 Food 또한 Proxy 객체 생성에 오류가 발생한다.

 


마지막으로 테스트 하면서 의문이 들어 하나의 테스트 케이스를 추가해보자.

5. Food와 Store 모두 (access = AccessLevel.PRIVATE) 로 조회하지만 Proxy가 아닌 실제 Entity 객체로 조회한다면 어떻게 될까? 즉,  EAGER (즉시로딩)

  • @ManyToOne(fetch = FetchType.EAGER)
// Food Entity 클래스

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "store_id")
public StoreEntity storeEntity;

정상동작

 

즉시 로딩일 경우 Proxy 객체가 생성되는 것이 아닌 Entity를 바로 조회하기 때문에 오류가 발생하지 않는다.


이번 테스트를 통해 Entity의 access = AccessLevel 은 Proxy와 관련이 되어 있다는 것을 알 수 있었다. 물론 Public으로 설정시에도 Proxy 객체 생성이 가능하지만 Entity 외부 접근을 차단하는 Protected를 활용하는 것이 안정성 측면에서 더 낫다.



끝.

예시코드는 다음 레포지토리에 있으며 차근 차근 채워나갈 예정이다.

https://github.com/pminsu01/JPAStudy

 

GitHub - pminsu01/JPAStudy: JPA Study Repository

JPA Study Repository. Contribute to pminsu01/JPAStudy development by creating an account on GitHub.

github.com

 

728x90

Entity에서 참조할 때 No Argument Constructor 의 경우에 참조가능한 범위가 있다. 특히 이 경우에 AccessLevel 을 Package 단계로 설정해두면 빨간맛을 뱉는데, 물론 Compile 자체에는 문제가 없을지라도 빨간맛을 보기 싫다면 변경하면 된다.

 

@AllArgsConstructor(access = AccessLevel.PUBLIC)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
@Builder(toBuilder = true)
@Table(name = "batch_user_emails")

 

이걸

@NoArgsConstructor(access = AccessLevel.PUBLIC)

Public 단계로 바꿔주면 된다.

 

에러를 그대로 해석하면 되는 문제다.

 

아래는 다른 블로그에서 찾아온 기본 맛에 대한 글

@NoArgsConstructor

기본 사용법은 다음과 같습니다.

@NoArgsConstructor
public class BookWithLombok {

    private Long id;
    private String isbn;
    private String name;
    private String author;
}

자바로 표현하면 다음과 같습니다.

public class BookWithOutLombok {

    private Long id;
    private String isbn;
    private String name;
    private String author;

    public BookWithOutLombok() {

    }
}

@AllArgsConstructor

기본 사용법은 다음과 같습니다.

@AllArgsConstructor
public class BookWithLombok {

    private Long id;
    private String isbn;
    private String name;
    private String author;
    private boolean useYn;
}

자바로 표현하면 다음과 같습니다.

public class BookWithLombok {
    private Long id;
    private String isbn;
    private String name;
    private String author;
    private boolean useYn;

    public BookWithLombok(final Long id, final String isbn, final String name, final String author, final boolean useYn) {
        this.id = id;
        this.isbn = isbn;
        this.name = name;
        this.author = author;
        this.useYn = useYn;
    }
}

@RequiredArgsConstructor

기본 사용법은 다음과 같습니다.

@RequiredArgsConstructor
public class BookWithLombok {

    private final Long id;
    private final String isbn;
    private final String name;
    private final String author;
    private boolean useYn;
}

자바로 표현하면 다음과 같습니다.

public class BookWithLombok {
    private final Long id;
    private final String isbn;
    private final String name;
    private final String author;
    private boolean useYn;

    public BookWithLombok(final Long id, final String isbn, final String name, final String author) {
        this.id = id;
        this.isbn = isbn;
        this.name = name;
        this.author = author;
    }
}

 

 

access - 접근제한자

생성자의 대해서 접근제한자를 지정할 수 있습니다. 기본 접근제한자는 public 입니다.
접근제한자 목록은 다음과 같습니다.

PUBLIC

모든 곳에서 접근 가능합니다.
다음과 같이 사용할 수 있습니다.

@NoArgsConstructor(access = AccessLevel.PUBLIC)

자바로 표현하면 다음과 같습니다.

public class BookWithLombok {
    private Long id;
    private String isbn;
    private String name;
    private String author;

    public BookWithLombok() {
    }
}

MODULE

같은 패키지내에서 접근 가능합니다.
다음과 같이 사용할 수 있습니다.

@NoArgsConstructor(access = AccessLevel.MODULE)

자바로 표현하면 다음과 같습니다.
default 와 동일하며 같은 Lombok에서는 package 와 동일합니다.

public class BookWithLombok { 
    private Long id; 
    private String isbn; 
    private String name; 
    private String author; 

    BookWithLombok() { 
    } 
}

PROTECTED

같은 패키지 또는 자식 클래스에서 사용할 수 있습니다.
다음과 같이 사용할 수 있습니다.

@NoArgsConstructor(access = AccessLevel.PROCTECTED)

자바로 표현하면 다음과 같습니다.

public class BookWithLombok {
    private Long id;
    private String isbn;
    private String name;
    private String author;

    protected BookWithLombok() {
    }
}

PACKAGE

같은 패키지안에서 접근 가능하며 MODULE 과 동일한 기능을 합니다.
다음과 같이 사용할 수 있습니다.

@NoArgsConstructor(access = AccessLevel.PACKAGE)

자바로 표현하면 다음과 같습니다.

public class BookWithLombok {
    private Long id;
    private String isbn;
    private String name;
    private String author;

    BookWithLombok() {
    }
}

PRIVATE

내부 클래스에서만 사용할 수 있습니다.
다음과 같이 사용할 수 있습니다.

@NoArgsConstructor(access = AccessLevel.PRIVATE)

자바로 표현하면 다음과 같습니다.

public class BookWithLombok {
    private Long id;
    private String isbn;
    private String name;
    private String author;

    private BookWithLombok() {
    }
}

NONE

기본값인 PUBLIC 과 동일합니다.
다음과 같이 사용할 수 있습니다.

@NoArgsConstructor(access = AccessLevel.NONE)

자바로 표현하면 다음과 같습니다.

public class BookWithLombok {
    private Long id;
    private String isbn;
    private String name;
    private String author;

    public BookWithLombok() {
    }
}

출처: https://lovethefeel.tistory.com/71 [사는 이야기:티스토리]

728x90

 

 

말 그대로 import 구문을 빠트렸을 때 발생할 수 있는 상태이다. 일반적으로는 assertion 을 좀더 가독성을 늘리기 위해서 Hamcrest matcher 와 사용되곤 하는데 JUnit 에 들어있다. 

 

또한 Spring Boot test starter 에 의존성을 가진다

 

 

상단부에서 라이브러리를 납치해준다. IntelliJ 에서 import 하고 싶다고 마우스를 댓다간 못찾는 사태가 있다

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;

 

 

 

JUnit 5(JUnit Jupiter)를 사용하는 경우에는 대게 JUnit 5의 Assertion 을 사용하고, 이 경우에는 직접적으로 assertThat method를 포함시키진 않는다.

 

하쥐만 Hamcrest matcher 를 import 구문을 상단에 포함시킴으로써 같이 사용할 수 있따.

 

예시

@Test
public void shouldLoginUserSuccessfully() throws Exception {
    // ... setup for mockMvc.perform ...

    MvcResult result = mockMvc.perform(/* your mockMvc perform call */).andReturn();
    String responseBody = result.getResponse().getContentAsString();

    // Use assertThat with a Hamcrest matcher
    assertThat(responseBody, containsString("expectedToken"));
}

 

 

취향껏 추가해보자

JUnit 5를 사용하고 native assertion library 사용에서 말뚝을 박고 싶은 경우에는 Assertion.assertTrue 를 String.contains 와 합쳐서 쓸 수 있다.

 

import org.junit.jupiter.api.Assertions;

// Inside your test method
Assertions.assertTrue(responseBody.contains("expectedToken"), "Response body does not contain expected token");

+ Recent posts