서로 연관된 두 테이블간의 조회방법 설정
- EAGER
- LAZY
N + 1 문제 및 해결방법 알아보기
User Controller에서 Fetch Type을 알아보자.
User Controller
package com.example.myhome.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private Boolean enabled;
@ManyToMany
@JoinTable(
name = "user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
@JsonIgnore
private List<Role> roles = new ArrayList<>();
// @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Board> boards = new ArrayList<>();
}
postman에서 get으로 해주자.
localhost:8080/api/users/3
User Controller에서 @JsonIgnore을 넣어주어 권한이 없게 만들어 준다.
@JsonIgnore
@ManyToMany
@JoinTable(
name = "user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private List<Role> roles = new ArrayList<>();
그러면 아래와 같이 쿼리가 나오는데
Spring boot pretty sql을 검색해서 아래 코드를 넣어준다.
spring.jpa.properties.hibernate.format_sql=true
쿼리가 보기 좋게 나온다.
@JsonIgnore를 넣어서 필요없는 쿼리는 보이지 않게 해주자.
User Controller
// @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@JsonIgnore
private List<Board> boards = new ArrayList<>();
UserApiController에서 findAll을 통해서 user 정보의 모든 쿼리를 다 볼 수 있다.
UserApiController에서 아래와 같이 변수를 받도록 해주자.
@GetMapping("/users")
List<User> all() {
List<User> users = repository.findAll();
return users;
}
그리고 User Class에서 fetchtype을 lazy로 바꿔준다.
// @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@JsonIgnore
private List<Board> boards = new ArrayList<>();
첫번째 user의 Board를 호출해보자.
@RestController
@RequestMapping("/api")
@Slf4j
class UserApiController {
@GetMapping("/users")
List<User> all() {
List<User> users = repository.findAll();
users.get(0).getBoards().size();
return users;
}
UserApiController 아래와 같이 설정
package com.example.myhome.controller;
import com.example.myhome.model.Board;
import com.example.myhome.model.User;
import com.example.myhome.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api")
@Slf4j
class UserApiController {
@Autowired
private UserRepository repository;
@GetMapping("/users")
List<User> all() {
List<User> users = repository.findAll();
log.debug("getBoards().size() 호출전");
log.debug("getBoards().size() : {}", users.get(0).getBoards().size());
log.debug("getBoards().size() 호출후");
users.get(0).getBoards().size();
return users;
}
@PostMapping("/users")
User newUser(@RequestBody User newUser) {
return repository.save(newUser);
}
// Single item
@GetMapping("/users/{id}")
User one(@PathVariable Long id) {
return repository.findById(id).orElse(null);
}
@PutMapping("/users/{id}")
User replaceUser(@RequestBody User newUser, @PathVariable Long id) {
return repository.findById(id)
.map(user -> {
// user.setTitle(newuser.getTitle());
// user.setContent(newuser.getContent());
// user.setBoards(newUser.getBoards());
user.getBoards().clear();
user.getBoards().addAll(newUser.getBoards());
for(Board board : user.getBoards()) {
board.setUser(user);
}
return repository.save(user);
})
.orElseGet(() -> {
newUser.setId(id);
return repository.save(newUser);
});
}
@DeleteMapping("/users/{id}")
void deleteUser(@PathVariable Long id) {
repository.deleteById(id);
}
}
ApplicationProperties에서 아래 구문 추가
logging.level.com.example=DEBUG
spring.datasource.url=jdbc:mariadb://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
logging.level.org.hibernate.SQL=DEBUG
logging.level.com.example=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
spring.jpa.properties.hibernate.format_sql=true
여기서 EntityGraph 참고
UserRepository를 아래와 같이 설정(User의 변수명 참고)
package com.example.myhome.repository;
import com.example.myhome.model.Board;
import com.example.myhome.model.User;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(attributePaths = { "boards" })
List<User> findAll();
User findByUsername(String username);
}
User의 Boards 변수명
@JsonIgnore
private List<Board> boards = new ArrayList<>();
지금 아래 에러가 계속해서 떠서 확인이 불가하다. 해결하고 넘어가자.
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-06-04 18:09:10.378 ERROR 98911 --- [ restartedMain] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'webSecurityConfig': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:410) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.20.jar:5.3.20]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.resolveBeanReference(ConfigurationClassEnhancer.java:362) ~[spring-context-5.3.20.jar:5.3.20]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:334) ~[spring-context-5.3.20.jar:5.3.20]
at com.example.myhome.config.WebSecurityConfig$$EnhancerBySpringCGLIB$$56c24912.passwordEncoder(<generated>) ~[classes/:na]
at com.example.myhome.config.WebSecurityConfig.configureGlobal(WebSecurityConfig.java:43) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:724) ~[spring-beans-5.3.20.jar:5.3.20]
< 참고 :Spring Boot으로 웹 출시까지 #11. JPA로 조회방법(FetchType) 설정하기 >
https://www.youtube.com/watch?v=K3PU_rKtGLc&list=PLPtc9qD1979DG675XufGs0-gBeb2mrona&index=11
'Spring > SpringBoot' 카테고리의 다른 글
Spring Boot으로 웹 출시까지 #12. 권한에 맞는 화면 구성 및 API 호출 & 13. JPA 이용한 커스텀 쿼리 만들기 (0) | 2022.06.06 |
---|---|
Spring Boot으로 웹 출시까지 #10. JPA를 이용하여 @OneToMany 관계 설정하기 (0) | 2022.06.04 |
Spring Boot으로 웹 출시까지 #9. Spring Security를 이용한 로그인 처리 (0) | 2022.06.03 |
Spring Boot으로 웹 출시까지 #8. JPA를 이용한 페이지 처리 및 검색 (0) | 2022.06.02 |
Spring Boot으로 웹 출시까지 #7. JPA이용한 RESTful API 작성 (0) | 2022.06.02 |