2022. 4. 15. 16:55ㆍSpring/Spring Data JPA Tutorial
- 사전작업
- LazyInitializationException 해결
- N+1 문제 해결
- Transactional 내부에서 연관관계 미리 조회
- Service
- batch size 미적용시
- batch size 적용
- Entity 설정에서 FetchType.EAGER로 설정
- EntityGraph
- fetch join
- Transactional 내부에서 연관관계 미리 조회
1. 사전작업
이전 포스팅에 이어서 이번 시간에는 1:N 연관관계 필드값을 가지는 엔티티 조회에서 발생될 수 있는 LazyInitializationException을 해결하는 방법을 알아볼 것입니다.
1.1. Entity 수정
1.1.1. User
User 엔티티가 필드로 갖는 stores를 이용하여 1:N 연관관계에 발생되는 에러현상을 살펴볼 것이기 설정되어있기때문에, 이전시간에 설정했던 @JsonIgnore를 제거합니다.
@NoArgsConstructor @DynamicInsert @DynamicUpdate @Getter @Entity @Table(name = "user", indexes = {@Index(name = "UK_USER_EMAIL", columnList = "email", unique = true)}) @Where(clause = "active = true") public class User implements Serializable { ... @OneToMany @JoinColumn(name = "user_id") @Setter private List<Store> stores = new ArrayList<>(); }
서로 순환참조되는 에러를 방지하기 위해 user 필드값에 @JsonIgnore 설정을 추가합니다.
관련 에러: HttpMessageNotWritableException: Could not write JSON: Infinite recursion
@NoArgsConstructor @DynamicInsert @DynamicUpdate @Getter @Entity public class Store { ... @JsonIgnore @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "FK_USER_STORE")) private User user; }
1.2. UserRepository 수정
기존의 UserRepository는 Repository를 상속받고 있었습니다.
JPA에서 자주 이용되는 api를 정의해둔 JpaRepository를
public interface UserRepository extends JpaRepository<User, Long> { ... }
기존에 정의했었던 Optional<User> findById(Long id)
, User save(User user)
, void delete(User user);
는 JpaRepository에서 이미 구현하고 있기 때문에 제거합니다.
1.3. api 테스트
엔티티를 위와 같이 수정한 후 1번 user를 조회해보면 아래와 같은 에러가 발생됩니다.
swagger 화면
response를 내보내기 전에 controller에서 열어본 user 객체의 모습입니다. stores를 읽어들이는 부분에서 LazyInitializationException 이 발생되어 있습니다.
2022-04-14 15:50:33.811 WARN 15648 --- [nio-8989-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: me.jiniworld.demo.domain.entity.User.stores, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: me.jiniworld.demo.domain.entity.User.stores, could not initialize proxy - no Session (through reference chain: java.util.HashMap["user"]->me.jiniworld.demo.domain.entity.User["stores"])]
2. LazyInitializationException 해결
2.1. @JsonIgnore
@OneToMany 연관관계 설정되어있는 stores 필드값에 @JsonIgnore를 설정합니다.
@NoArgsConstructor @DynamicInsert @DynamicUpdate @Getter @Entity @Table(name = "user", indexes = {@Index(name = "UK_USER_EMAIL", columnList = "email", unique = true)}) @Where(clause = "active = true") public class User implements Serializable { ... @JsonIgnore @OneToMany @JoinColumn(name = "user_id") @Setter private List<Store> stores = new ArrayList<>(); }
그러면, api가 에러없이 동작됩니다.
그러나 response에 stores 정보가 포함되지 않기 때문에 우리가 원하는 결과가 아닙니다.
로그상에도 user를 조회하는 쿼리만 발생했습니다.
Hibernate: select user0_.id as id1_1_0_, user0_.active as active2_1_0_, user0_.birth_date as birth_da3_1_0_, user0_.created_at as created_4_1_0_, user0_.email as email5_1_0_, user0_.name as name6_1_0_, user0_.password as password7_1_0_, user0_.phone_number as phone_nu8_1_0_, user0_.sex as sex9_1_0_, user0_.type as type10_1_0_, user0_.updated_at as updated11_1_0_ from user user0_ where user0_.id=? and ( user0_.active = 1 )
2.2. Transactional 내부에서 연관관계 미리 조회 권장
@Transactional 설정된 UserService 내의 메서드에서, stores 정보를 미리 LOAD합니다.
@Transactional(readOnly = true) @RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; public User select(Long id) { User user = userRepository.findById(id).orElse(null); user.getStores().stream().forEach(store -> store.getName()); return user; } }
영속상태에서 미리 연관관계 정보를 로드해두면, LAZY 관련 에러가 발생되지 않습니다.
api를 실행해봅시다.
이번에는 우리가 원하던 stores 정보도 함께 포함되어 response가 출력되었습니다.
user를 조회하는 쿼리와 user_id를 이용하여 stores를 조회하는 쿼리가 발생했습니다.
Hibernate: select user0_.id as id1_1_0_, user0_.active as active2_1_0_, user0_.birth_date as birth_da3_1_0_, user0_.created_at as created_4_1_0_, user0_.email as email5_1_0_, user0_.name as name6_1_0_, user0_.password as password7_1_0_, user0_.phone_number as phone_nu8_1_0_, user0_.sex as sex9_1_0_, user0_.type as type10_1_0_, user0_.updated_at as updated11_1_0_ from user user0_ where user0_.id=? and ( user0_.active = 1 ) Hibernate: select stores0_.user_id as user_id4_0_0_, stores0_.id as id1_0_0_, stores0_.id as id1_0_1_, stores0_.industry as industry2_0_1_, stores0_.name as name3_0_1_, stores0_.user_id as user_id4_0_1_ from store stores0_ where stores0_.user_id=?
아래는 쿼리를 직접 조회한 결과 화면입니다.
2.3. Entity 설정에서 FetchType.EAGER로 설정 비권장
@Entity @Table(name = "user", indexes = {@Index(name = "UK_USER_EMAIL", columnList = "email", unique = true)}) @Where(clause = "active = true") public class User implements Serializable { ... @OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "user_id") @Setter private List<Store> stores = new ArrayList<>(); }
EAGER 방식으로 조회할 경우, left outer join을 이용하여 조회됩니다.
이용된 쿼리를 보면 1개의 쿼리만 실행된것을 확인할 수 있는데, @OneToMany 연관관계에서는 EAGER방식으로 데이터를 읽어들이는 것을 권장하지 않습니다.
Hibernate: select user0_.id as id1_1_0_, user0_.active as active2_1_0_, user0_.birth_date as birth_da3_1_0_, user0_.created_at as created_4_1_0_, user0_.email as email5_1_0_, user0_.name as name6_1_0_, user0_.password as password7_1_0_, user0_.phone_number as phone_nu8_1_0_, user0_.sex as sex9_1_0_, user0_.type as type10_1_0_, user0_.updated_at as updated11_1_0_, stores1_.user_id as user_id4_0_1_, stores1_.id as id1_0_1_, stores1_.id as id1_0_2_, stores1_.industry as industry2_0_2_, stores1_.name as name3_0_2_, stores1_.user_id as user_id4_0_2_ from user user0_ left outer join store stores1_ on user0_.id=stores1_.user_id where user0_.id=? and ( user0_.active = 1 )
위의 쿼리를 직접 실행시켜보면 이유를 알 수 있습니다.
User 하나는 여러개의 Store를 가지고 있을 수 있고, 이것을 JPA를 이용하여 조회할 경우 기준이 되는 엔티티인 User 정보가 중복되게 됩니다.
left outer join 하기 때문에 stores값이 없어도 user 정보를 읽을 수 있습니다.
3번 사용자를 조회한 결과는 아래와 같습니다.
2.4. EntityGraph 비권장
이전시간에 배웠듯, EntityGraph 역시, EAGER 방식으로 연관관계를 가져옵니다.
2.3 와 똑같이 left outer join으로 데이터를 읽어오기 때문에, LazyInitializationException 에러는 해결할 수 있으나, 기준이 되는 User 엔티티 정보를 중복적으로 가져오기 때문에 비권장하는 방식입니다.
다만, EntityGraph는 Distinct 설정은 가능하다는 장점이 있습니다.
2.4.1. Repository
public interface UserRepository extends Repository<User, Long> { @EntityGraph(attributePaths = "stores") Optional<User> findDistinctWithStoresById(Long id); }
2.4.2. Service
@Transactional(readOnly = true) @RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; public User select(Long id) { User user = userRepository.findDistinctWithStoresById(id).orElse(null); return user; } }
2.4.3. query
Hibernate: select distinct user0_.id as id1_1_0_, stores1_.id as id1_0_1_, user0_.active as active2_1_0_, user0_.birth_date as birth_da3_1_0_, user0_.created_at as created_4_1_0_, user0_.email as email5_1_0_, user0_.name as name6_1_0_, user0_.password as password7_1_0_, user0_.phone_number as phone_nu8_1_0_, user0_.sex as sex9_1_0_, user0_.type as type10_1_0_, user0_.updated_at as updated11_1_0_, stores1_.industry as industry2_0_1_, stores1_.name as name3_0_1_, stores1_.user_id as user_id4_0_1_, stores1_.user_id as user_id4_0_0__, stores1_.id as id1_0_0__ from user user0_ left outer join store stores1_ on user0_.id=stores1_.user_id where ( user0_.active = 1 ) and user0_.id=?
2.5. fetch join 비권장
fetch join은 EntityGraph와 join방식이 다른것 말고는 유사합니다.
미리 연관관계를 읽어오기 때문에 LazyInitializationException 에러를 방지할 수 있으나, EntityGraph와 마찬가지로 기준이 되는 User 엔티티 정보를 중복적으로 가져옵니다.
또, join 방식이 inner join이기 때문에 연관관계 설정된 stores값이 없을 경우, user 도 조회되지 않는다는 점에서 EntityGraph와 차이가 있습니다.
2.5.1. Repository
public interface UserRepository extends Repository<User, Long> { @Query("SELECT DISTINCT u FROM User u join fetch u.stores WHERE u.id = ?1") Optional<User> findDistinctWithStoresById(Long id); }
Hibernate: select distinct user0_.id as id1_1_0_, stores1_.id as id1_0_1_, user0_.active as active2_1_0_, user0_.birth_date as birth_da3_1_0_, user0_.created_at as created_4_1_0_, user0_.email as email5_1_0_, user0_.name as name6_1_0_, user0_.password as password7_1_0_, user0_.phone_number as phone_nu8_1_0_, user0_.sex as sex9_1_0_, user0_.type as type10_1_0_, user0_.updated_at as updated11_1_0_, stores1_.industry as industry2_0_1_, stores1_.name as name3_0_1_, stores1_.user_id as user_id4_0_1_, stores1_.user_id as user_id4_0_0__, stores1_.id as id1_0_0__ from user user0_ inner join store stores1_ on user0_.id=stores1_.user_id where ( user0_.active = 1 ) and user0_.id=?
1번 사용자에 대해서는 2.4 와 똑같이 응답이 나오지만,
3번 사용자는 stores값이 없어서 user 정보를 읽을 수 없습니다.
3. N+1 문제 해결
user를 모두 조회하는 GET /api/users
라는 api가 있다고 해볼때,
2.2 ~ 2.5 에서 살펴보았던 방식을 user 모두 조회에 적용해서, N + 1 문제에 대해 알아보도록 합시다.
3.1. Transactional 내부에서 연관관계 미리 조회
user의 stores를 forEach를 이용하여 @Transactional이 끝나기 전에 미리 조회하여, Controller에서 LazyInitializationException 에러가 발생되지 않도록 합니다.
3.1.1. Service
@Transactional(readOnly = true) @RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; public List<User> selectAll() { List<User> users = userRepository.findAll(); users.stream() .forEach(user -> user.getStores().stream() .filter(store -> store != null) .forEach(store -> store.getName())); return users; } }
@OneToMany 관계에서 N+1 문제를 개선하기 위해 필수적으로 이용해야할 설정이 batch size 입니다.
batch size를 설정하지 않았을때, 위의 서비스로 정의된 api를 실행했을 경우 아래와 같이 쿼리가 실행됩니다.
Hibernate: select user0_.id as id1_1_, user0_.active as active2_1_, user0_.birth_date as birth_da3_1_, user0_.created_at as created_4_1_, user0_.email as email5_1_, user0_.name as name6_1_, user0_.password as password7_1_, user0_.phone_number as phone_nu8_1_, user0_.sex as sex9_1_, user0_.type as type10_1_, user0_.updated_at as updated11_1_ from user user0_ where ( user0_.active = 1 ) Hibernate: select stores0_.user_id as user_id4_0_0_, stores0_.id as id1_0_0_, stores0_.id as id1_0_1_, stores0_.industry as industry2_0_1_, stores0_.name as name3_0_1_, stores0_.user_id as user_id4_0_1_ from store stores0_ where stores0_.user_id=? Hibernate: select stores0_.user_id as user_id4_0_0_, stores0_.id as id1_0_0_, stores0_.id as id1_0_1_, stores0_.industry as industry2_0_1_, stores0_.name as name3_0_1_, stores0_.user_id as user_id4_0_1_ from store stores0_ where stores0_.user_id=? Hibernate: select stores0_.user_id as user_id4_0_0_, stores0_.id as id1_0_0_, stores0_.id as id1_0_1_, stores0_.industry as industry2_0_1_, stores0_.name as name3_0_1_, stores0_.user_id as user_id4_0_1_ from store stores0_ where stores0_.user_id=?
조회하고자하는 엔티티인 user를 조회하는 쿼리 1번 + 조회해야할 연관관계(stores)를 user의 count만큼 조회하는 쿼리 3번 → N + 1 문제 발생!!!
3.1.3. batch size 적용시 권장
batch size를 설정할 경우, 1:N 연관관계를 갖는 필드값을 batch size만큼 한번에 IN절을 이용하여 조회할 수 있습니다.
즉, batch size를 설정하면, 1:N 연관관계에서 발생되는 N+1 문제를 개선할 수 있습니다.
batch size를 설정할 수 있는 방법은 2가지가 있습니다.
- 각 엔티티별 필드값에 직접 정의
- 프로퍼티를 이용하여 1:N 연관관계 필드값에 기본 batch size 정의
@OneToMany 연관관계 필드값에 직접 @BatchSize를 설정합니다.
@NoArgsConstructor @DynamicInsert @DynamicUpdate @Getter @Entity @Table(name = "user", indexes = {@Index(name = "UK_USER_EMAIL", columnList = "email", unique = true)}) @Where(clause = "active = true") public class User implements Serializable { ... @OneToMany(fetch = FetchType.EAGER) @BatchSize(size = 100) @JoinColumn(name = "user_id") @Setter private List<Store> stores = new ArrayList<>(); }
application.yml에 기본 batch size를 지정합니다.
application.yml에 기본 batch size를 설정하고, Entity에 따라 batch size를 더 키우고 싶으면 직접 설정하는 것을 권장합니다.
spring: jpa: properties: hibernate: default_batch_fetch_size: 100
size는 100~1000 사이의 수를 지정는 것을 권장하며, size가 클수록 API 성능은 좋아지나, DB과부화가 걸릴 수 있습니다.
아래는 BatchSize를 적용한 후 users 전체 조회시 실행된 쿼리입니다.
N+1 문제가 개선되었습니다.
Hibernate: select user0_.id as id1_1_, user0_.active as active2_1_, user0_.birth_date as birth_da3_1_, user0_.created_at as created_4_1_, user0_.email as email5_1_, user0_.name as name6_1_, user0_.password as password7_1_, user0_.phone_number as phone_nu8_1_, user0_.sex as sex9_1_, user0_.type as type10_1_, user0_.updated_at as updated11_1_ from user user0_ where ( user0_.active = 1 ) Hibernate: select stores0_.user_id as user_id4_0_1_, stores0_.id as id1_0_1_, stores0_.id as id1_0_0_, stores0_.industry as industry2_0_0_, stores0_.name as name3_0_0_, stores0_.user_id as user_id4_0_0_ from store stores0_ where stores0_.user_id in ( ?, ?, ? )
만일 user의 수가 302개일 경우, BatchSize가 100이므로, IN절을 이용한 store 조회는 총 4번 일어납니다.
3.2. Entity 설정에서 FetchType.EAGER로 설정 비권장
3.2.1. Entity
Entity에 설정하는 것은 2.3 과 똑같이, Entity의 stores 필드값만 수정해주면 됩니다.
@NoArgsConstructor @DynamicInsert @DynamicUpdate @Getter @Entity @Table(name = "user", indexes = {@Index(name = "UK_USER_EMAIL", columnList = "email", unique = true)}) @Where(clause = "active = true") public class User implements Serializable { ... @OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "user_id") @Setter private List<Store> stores = new ArrayList<>(); }
3.2.2. Service
Service 메서드에 forEach를 이용하여 stores를 조회하는 코드도 필요하지 않습니다.
@Transactional(readOnly = true) @RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; public List<User> selectAll() { List<User> users = userRepository.findAll(); return users; } }
3.2.3. batch size 미적용시
EAGER 방식이니, user조회할때에 left outer join을 하여 1번의 쿼리만 실행될거라고 예상하겠지만, 예상과 달리 3.1 처럼 N + 1 문제 가 발생되었습니다.
Hibernate: select user0_.id as id1_1_, user0_.active as active2_1_, user0_.birth_date as birth_da3_1_, user0_.created_at as created_4_1_, user0_.email as email5_1_, user0_.name as name6_1_, user0_.password as password7_1_, user0_.phone_number as phone_nu8_1_, user0_.sex as sex9_1_, user0_.type as type10_1_, user0_.updated_at as updated11_1_ from user user0_ where ( user0_.active = 1 ) Hibernate: select stores0_.user_id as user_id4_0_0_, stores0_.id as id1_0_0_, stores0_.id as id1_0_1_, stores0_.industry as industry2_0_1_, stores0_.name as name3_0_1_, stores0_.user_id as user_id4_0_1_ from store stores0_ where stores0_.user_id=? Hibernate: select stores0_.user_id as user_id4_0_0_, stores0_.id as id1_0_0_, stores0_.id as id1_0_1_, stores0_.industry as industry2_0_1_, stores0_.name as name3_0_1_, stores0_.user_id as user_id4_0_1_ from store stores0_ where stores0_.user_id=? Hibernate: select stores0_.user_id as user_id4_0_0_, stores0_.id as id1_0_0_, stores0_.id as id1_0_1_, stores0_.industry as industry2_0_1_, stores0_.name as name3_0_1_, stores0_.user_id as user_id4_0_1_ from store stores0_ where stores0_.user_id=?
3.2.4. batch size 적용
batch size를 적용할 경우 3.1.3 에서와 마찬가지로 N+1 문제를 개선됩니다. 다만, left outer join은 이뤄지지 않고, BatchSize에 맞춰 IN절로 stores를 조회합니다.
batch size를 설정한 후, api를 실행해보니 아래와 같이 쿼리가 개선된 것을 확인해 볼 수 있습니다.
Hibernate: select user0_.id as id1_1_, user0_.active as active2_1_, user0_.birth_date as birth_da3_1_, user0_.created_at as created_4_1_, user0_.email as email5_1_, user0_.name as name6_1_, user0_.password as password7_1_, user0_.phone_number as phone_nu8_1_, user0_.sex as sex9_1_, user0_.type as type10_1_, user0_.updated_at as updated11_1_ from user user0_ where ( user0_.active = 1 ) Hibernate: select stores0_.user_id as user_id4_0_1_, stores0_.id as id1_0_1_, stores0_.id as id1_0_0_, stores0_.industry as industry2_0_0_, stores0_.name as name3_0_0_, stores0_.user_id as user_id4_0_0_ from store stores0_ where stores0_.user_id in ( ?, ?, ? )
user 단건조회가 아닌 경우 위와같이 user와 store가 각각 조회되기 때문에 코드상으로는 문제가 없지만, 단건 조회할때에는 EAGER 로 설정되어있기 때문에 left outer join으로 데이터를 가져와서 데이터가 중복되게 됩니다.
그 외에도, Entity상에 fetchType을 EAGER로 정의하는것은 비권장 사항입니다. 위의 코드는 실행 결과를 보기 위함이고 LAZY로 정의하고 다른 방법을 이용하여 LazyInitializationException 에러를 해결하기를 권장합니다.
3.3. EntityGraph 비권장
Entity에 설정했던 fetchType.EAGER 설정은 제거하고, @EntityGraph를 이용하여 stores 정보를 가져와봅니다.
3.3.1. Service
@Transactional(readOnly = true) @RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; public List<User> selectAll() { List<User> users = userRepository.findDistinctWithStoresBy(); return users; } }
3.3.2. Repository
public interface UserRepository extends JpaRepository<User, Long> { @EntityGraph(attributePaths = "stores") List<User> findDistinctWithStoresBy(); }
3.3.3. query
EntityGraph를 이용하니, 우리가 원하던 대로 left outer join을 이용하여 1번의 쿼리만 이용되었습니다.
Hibernate: select distinct user0_.id as id1_1_0_, stores1_.id as id1_0_1_, user0_.active as active2_1_0_, user0_.birth_date as birth_da3_1_0_, user0_.created_at as created_4_1_0_, user0_.email as email5_1_0_, user0_.name as name6_1_0_, user0_.password as password7_1_0_, user0_.phone_number as phone_nu8_1_0_, user0_.sex as sex9_1_0_, user0_.type as type10_1_0_, user0_.updated_at as updated11_1_0_, stores1_.industry as industry2_0_1_, stores1_.name as name3_0_1_, stores1_.user_id as user_id4_0_1_, stores1_.user_id as user_id4_0_0__, stores1_.id as id1_0_0__ from user user0_ left outer join store stores1_ on user0_.id=stores1_.user_id where ( user0_.active = 1 )
left outer join을 이용해서 user를 조회해기 때문에 stores가 없는 3번 사용자 코코도 조회됩니다.
{ "result": "SUCCESS", "user": [ { "id": 1, "type": "BASIC", "email": "jini@jiniworld.me", "name": "지니", "sex": "F", "birthDate": "19920201", "phoneNumber": "01011112222", "active": true, "createdAt": "2022-04-11T07:59:07.148+00:00", "updatedAt": null, "stores": [ { "id": 1, "name": "우리의 시간", "industry": "카페" }, { "id": 2, "name": "에머이", "industry": "베트남음식" } ] }, { "id": 2, "type": "OWNER", "email": "2han@appleboxy.xyz", "name": "이한", "sex": "M", "birthDate": "19880702", "phoneNumber": "01088887777", "active": true, "createdAt": "2022-04-13T03:21:30.542+00:00", "updatedAt": null, "stores": [ { "id": 3, "name": "한잔의 추억", "industry": "술집" } ] }, { "id": 3, "type": "OWNER", "email": "coco@jiniworld.me", "name": "코코", "sex": "F", "birthDate": "19980117", "phoneNumber": "01055557741", "active": true, "createdAt": "2022-04-13T07:14:01.098+00:00", "updatedAt": null, "stores": [] } ] }
EntityGraph를 이용하여 users 를 조회하는 것은 쿼리는 1개만 실행되기 때문에 효율적으로 보일수도 있으나, left outer join 방식을 이용하기 때문에 위와같이 중복되는 데이터를 초래합니다.
EntityGraph를 이용하는 것 보다는 영속상태일 때 직접 stores 데이터를 LOAD하는 것을 권장합니다. (fetch join 역시 마찬가지)
3.4. fetch join 비권장
3.4.1. Repository
public interface UserRepository extends JpaRepository<User, Long> { @Query("SELECT DISTINCT u FROM User u join fetch u.stores") List<User> findDistinctWithStoresBy(); }
3.4.2. query
fetch join으로 조회했을 때에도 inner join을 이용하여 1번의 쿼리만 이용됩니다.
Hibernate: select distinct user0_.id as id1_1_0_, stores1_.id as id1_0_1_, user0_.active as active2_1_0_, user0_.birth_date as birth_da3_1_0_, user0_.created_at as created_4_1_0_, user0_.email as email5_1_0_, user0_.name as name6_1_0_, user0_.password as password7_1_0_, user0_.phone_number as phone_nu8_1_0_, user0_.sex as sex9_1_0_, user0_.type as type10_1_0_, user0_.updated_at as updated11_1_0_, stores1_.industry as industry2_0_1_, stores1_.name as name3_0_1_, stores1_.user_id as user_id4_0_1_, stores1_.user_id as user_id4_0_0__, stores1_.id as id1_0_0__ from user user0_ inner join store stores1_ on user0_.id=stores1_.user_id where ( user0_.active = 1 )
단, inner join으로 user정보를 가져오기 때문에 stores가 없는 3번 사용자인 코코는 response에 포함되지 않습니다.
{ "result": "SUCCESS", "user": [ { "id": 1, "type": "BASIC", "email": "jini@jiniworld.me", "name": "지니", "sex": "F", "birthDate": "19920201", "phoneNumber": "01011112222", "active": true, "createdAt": "2022-04-11T07:59:07.148+00:00", "updatedAt": null, "stores": [ { "id": 1, "name": "우리의 시간", "industry": "카페" }, { "id": 2, "name": "에머이", "industry": "베트남음식" } ] }, { "id": 2, "type": "OWNER", "email": "2han@appleboxy.xyz", "name": "이한", "sex": "M", "birthDate": "19880702", "phoneNumber": "01088887777", "active": true, "createdAt": "2022-04-13T03:21:30.542+00:00", "updatedAt": null, "stores": [ { "id": 3, "name": "한잔의 추억", "industry": "술집" } ] } ] }
++
- How to fix N + 1 problems in Spring Boot
- How to set default batch size in Spring Boot
※ GitHub에서 demo 프로젝트(v1.0.6)를 다운받아 볼 수 있습니다.