[Spring] Spring JPA 2 - Spring Data JPA, Spring Data JPA CRUD
Spring Data JPA
Spring Data
- 데이터에 접근하는 코드를 줄이기 위한 스프링 프로젝트
- DDD(Domain-Driven Design, 도메인 주도 설계)에서의 데이터 접근 계층 구성 요소인 Repository 권장
- Repository 구현은 데이터와 관련된 아키텍쳐/제품에 따라 다름(각 종류별로 프로젝트가 만들어짐)
Spring Data JPA
- JPA를 이용해 데이터에 접근하기 위한 라이브러리
- Respository 인터페이스를 작성하여 간단하게 Entity를 참조/갱신할 수 있음
JPA Config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
@Configuration @EnableTransactionManagement @EnableJpaRepositories(basePackages = "com.repository") public class JPAConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .setScriptEncoding("UTF-8").build(); } @Bean public JpaVendorAdapter jpaVendorAdapter() { HibernateJpaVendorAdapter bean = new HibernateJpaVendorAdapter(); bean.setDatabase(Database.H2); bean.setGenerateDdl(true); return bean; } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) { LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean(); bean.setDataSource(dataSource); bean.setJpaVendorAdapter(jpaVendorAdapter); bean.setPackagesToScan("com.model"); return bean; } @Bean public JpaTransactionManager transactionManager(EntityManagerFactory emf) { return new JpaTransactionManager(emf); } } Colored by Color Scripter |
cs |
- Java JPA를 사용하기 위해 설정했던 config를 그대로 사용
- @EnableJpaRepositories(basePackages = "com.repository") 지정하여 @Repository를 탐색
Spring Data JPA Architecture
내부 처리
- Java의 JPA에서 EntityManager의 API 를 직접 호출한 것과 달리, Spring JPA에서는 framework 내부에서 자동으로 호출(사용자는 Spring JPA API 호출)
- Entity의 repository interface를 작성하고 JpaRespository를 상속받아 사용
- JpaRepository는 SimpleJpaRepository가 제공하는 API를 인터페이스화한 클래스
- Spring Data JPA는 DI container가 초기화되는 시점에 생성한 repository interface를 SimpleJpaRepository로 처리를 위임하는 proxy를 생성하고 bean으로 등록
- 실제 EntityManager의 API는 SimpleJpaRepository나 그 상위 클래스에서 호출
1. Repository
- 사용자가 JpaRepository를 상속받아 구현
2. RepositoryProxy
- Repository 생성 시, Spring Data JPA가 생성하는 proxy class
- Repository에 대해 SimpleJpaRepository 처리를 위임
- Service에서 호출
3. JpaRepository
- SimpleJpaRepository의 구현체
4. SimpleJpaRepository
- 실제로 EntityManager의 API를 호출
- RepositoryProxy 메소드가 호출될 때, 위임받아 처리
JpaRepository
- JpaRepository를 상속한 Repository 클래스를 생성하여 data 접근
1. Repository 작성
1 2 3 |
@Repository public interface TestJpaRepository extends JpaRepository<Entity, PK> { } Colored by Color Scripter |
cs |
- JpaRepository<Entity, PK>를 상속받은 interface 클래스 생성
2. JpaRepository API
- save : entity를 저장하는 메소드(insert, update)
- flush : EntityManager의 내용을 DB에 동기화하는 메소드
- saveAndFlush : entity에 대한 저장 작업 후 flush
- delete : entity를 삭제하는 메소드(delete)
- deleteAll : DB의 모든 레코드를 persistence context로 읽어와 삭제하는 메소드
- deleteInBatch : persistence context로 읽어오지 않고 DB의 모든 레코드를 삭제하는 메소드
- findOne : primary key로 DB에서 Entity를 찾아오는 메소드(select)
- findAll : 모든 entity를 찾아오는 메소드(select)
- exists : primary key에 해당하는 entity가 존재하는 확인하는 메소드
- count : entity의 갯수를 확인하는 메소드
예외 처리
- Spring Data JPA에서 제공
- Database 접근 시 발생하는 에러는 시스템 오류로 처리함
1. DataAccessException
- @Repository에서 발생한 예외를 DataAccessException으로 변환하는 구조 제공
- Data에 관련된 예외만 DataAccessException에서 처리
1) 예외 클래스
- DuplicateKeyException : 데이터 무결성 조건 위반 시 발생
- OptimisticLockingFailureException : optimistic lock 실패 시 발생(OptimisticLockException 발생 시)
- PessimisticLockingFailureException : pessimisic lock 실패 시 발생(PessimisticLockException 발생 시)
Repository 사용
Repository CRUD
1. Entity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
@Entity @Table(name = "user") public class User { @Id @Column(name = "id") private String id; @Column(name = "name") private String name; public User() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } Colored by Color Scripter |
cs |
- id와 name을 갖는 entity 생성
2. Repository
1 2 3 4 |
@Repository public interface UserRepository extends JpaRepository<User, String> { } Colored by Color Scripter |
cs |
- @Repository 지정
- JpaRepository<User, String>을 상속받는 interface 클래스 생성
3. Create(POST)
1) Controller
1 2 3 4 5 6 7 8 9 10 11 |
@RestController @RequestMapping(value = "/springjpa") public class SpringJpaController { @Autowired private SpringJpaService springJpaService; @RequestMapping(value = "/", method = RequestMethod.POST) public void createUser(@RequestBody User user) { springJpaService.createUser(user); } } Colored by Color Scripter |
cs |
- POST 메소드로 요청을 받고 SpringJpaService의 createUser 호출
2) Service
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Service public class SpringJpaService { @Autowired private UserRepository userRepository; public void createUser(User user) { Optional<User> exist = userRepository.findById(user.getId()); if(exist.isPresent()) { throw new TestException(HttpStatus.CONFLICT, "존재하는 ID 입니다."); } userRepository.save(user); } } Colored by Color Scripter |
cs |
- UserRepository의 findById로 Entity를 읽어옴
- Optional로 반환되어 존재하는지 확인하고 있을 시, Exception
- 없을 경우, save로 create
3) 결과
- 성공
- 실패
4. Read(GET)
1) Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@RestController @RequestMapping(value = "/springjpa") public class SpringJpaController { @Autowired private SpringJpaService springJpaService; @RequestMapping(value = "/{id}", method = RequestMethod.GET) public User getUserOne(@PathVariable String id) { return springJpaService.getUserOne(id); } @RequestMapping(value = "/", method = RequestMethod.GET) public List<User> getUserAll() { return springJpaService.getUserAll(); } } Colored by Color Scripter |
cs |
- 하나의 user를 가져오는 핸들러와 모든 user를 가져오는 handler
2) Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Service public class SpringJpaService { @Autowired private UserRepository userRepository; public User getUserOne(String id) { return userRepository.findById(id) .orElseThrow(() -> new TestException(HttpStatus.NOT_FOUND, "없는 ID 입니다.")); } public List<User> getUserAll() { List<User> users = userRepository.findAll(); return users; } } Colored by Color Scripter |
cs |
- UserRepository의 findById로 Entity를 읽어옴
- 받은 Optional로 존재하지 않는다면 Exception
- 있을 경우, get으로 User 반환
- UserRepository의 findAll로 모든 Entity를 읽어오고 반환
3) 결과
- GetOne(성공)
- GetOne(실패)
- GetAll
5. Update(PUT)
1) Controller
1 2 3 4 5 6 7 8 9 10 11 |
@RestController @RequestMapping(value = "/springjpa") public class SpringJpaController { @Autowired private SpringJpaService springJpaService; @RequestMapping(value = "/", method = RequestMethod.PUT) public void updateUser(@RequestBody User user) { springJpaService.updateUser(user.getId(), user.getName()); } } Colored by Color Scripter |
cs |
- PUT 메소드로 요청을 받고 SpringJpaService의 updateUser 호출
2) Service
1 2 3 4 5 6 7 8 9 10 11 |
@Service public class SpringJpaService { @Autowired private UserRepository userRepository; public void updateUser(String id, String name) { User user = getUserOne(id); user.setName(name); userRepository.save(user); } } Colored by Color Scripter |
cs |
- getUserOne 메소드를 호출하여 User를 반환받음
- User가 존재할 시, name을 요청 받은 name으로 변환한 후 UserRepository의 save를 사용하여 update
3) 결과
- update
- update 내용 확인(get)
6. Delete(DELETE)
1) Controller
1 2 3 4 5 6 7 8 9 10 11 |
@RestController @RequestMapping(value = "/springjpa") public class SpringJpaController { @Autowired private SpringJpaService springJpaService; @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) public void deleteUser(@PathVariable String id) { springJpaService.deleteUser(id); } } Colored by Color Scripter |
cs |
- DELETE 메소드로 요청 받고 SpringJpaService의 deleteUser 호출
2) Service
1 2 3 4 5 6 7 8 9 10 |
@Service public class SpringJpaService { @Autowired private UserRepository userRepository; public void deleteUser(String id) { User user = getUserOne(id); userRepository.delete(user); } } Colored by Color Scripter |
cs |
- getUserOne 메소드를 호출하여 User를 반환받음
- User가 존재할 시, UserRepository의 delete를 사용하여 해당 Entity를 delete
3) 결과
- delete
- delete 결과 확인(get)
7. Controller(전체)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
@RestController @RequestMapping(value = "/springjpa") public class SpringJpaController { @Autowired private SpringJpaService springJpaService; @RequestMapping(value = "/", method = RequestMethod.POST) public void createUser(@RequestBody User user) { springJpaService.createUser(user); } @RequestMapping(value = "/{id}", method = RequestMethod.GET) public User getUserOne(@PathVariable String id) { return springJpaService.getUserOne(id); } @RequestMapping(value = "/", method = RequestMethod.GET) public List<User> getUserAll() { return springJpaService.getUserAll(); } @RequestMapping(value = "/", method = RequestMethod.PUT) public void updateUser(@RequestBody User user) { springJpaService.updateUser(user.getId(), user.getName()); } @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) public void deleteUser(@PathVariable String id) { springJpaService.deleteUser(id); } } Colored by Color Scripter |
cs |
8. Service(전체)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
@Service public class SpringJpaService { @Autowired private UserRepository userRepository; public void createUser(User user) { Optional<User> exist = userRepository.findById(user.getId()); if(exist.isPresent()) { throw new TestException(HttpStatus.CONFLICT, "존재하는 ID 입니다."); } userRepository.save(user); } public User getUserOne(String id) { return userRepository.findById(id) .orElseThrow(() -> new TestException(HttpStatus.NOT_FOUND, "없는 ID 입니다.")) } public List<User> getUserAll() { List<User> users = userRepository.findAll(); return users; } public void updateUser(String id, String name) { User user = getUserOne(id); user.setName(name); userRepository.save(user); } public void deleteUser(String id) { User user = getUserOne(id); userRepository.delete(user); } } Colored by Color Scripter |
cs |
JPQL
- JPA에서 사용했던 것처럼 Spring Data JPA에서도 Query를 작성하여 사용할 수 있음
- 일반적인 Repository select 시, 반환 값이 Optional이지만 생성하여 사용할 경우 원하는대로 지정할 수 있음
1. @Query
- @Query annotation의 속성으로 query문을 입력하고 해당 query문이 동작하게 할 method 지정
- JPA와 달리 LIKE 절에 "%" 와일드카드 사용 가능
- Jpql에 SpEL을 포함할 수 있음
- 일반 쿼리 처럼 사용하고 싶은 경우, @Query의 속성에 "nativeQuery = true"를 설정
1 2 3 4 5 |
@Repository public interface UserRepository extends JpaRepository<User, String> { @Query("SELECT u FROM User u WHERE u.name = :name") List<User> getAllByName(@Param("name") String name); } Colored by Color Scripter |
cs |
- jpql을 @Query안에 작성하고 method에 지정
- 결과
2. Method name
- method의 이름을 보고 JPA가 판단하면 query를 생성
1 2 3 4 |
@Repository public interface UserRepository extends JpaRepository<User, String> { List<User> findByNameOrderByIdDesc(String name); } Colored by Color Scripter |
cs |
- find/read/query/count/get By으로 시작하는 method를 생성하는 경우, Spring JPA에서 select문의 jpql을 생성
- 일반 쿼리문처럼 여러 가지 설정 값을 줄 수 있음
- 결과
배타 제어
- JPA에서 사용했던 것과 마찬가지로 Spring JPA에서도 배타 제어를 사용할 수 있음
1 2 3 4 5 |
@Repository public interface UserRepository extends JpaRepository<User, String> { @Lock(LockModeType.OPTIMISTIC) List<User> findByNameOrderByIdDesc(String name); } Colored by Color Scripter |
cs |
- @Lock으로 설정한 modetype을 설정하고 method에 지정
Custom Repository
- jpql로 일일이 query문을 설정해도 되지만 custom repository를 생성하여 사용할 수 있음
- JpaRepository와 같은 custom interface 생성
- custom interface의 구현체를 생성하고 method 구현
- 해당 custom interface를 JpaRepostiory와 함께 상속받아 사용
Auditing
- Spring Data가 제공하는 Database의 Data에 대한 audit 정보를 남기기 위한 기능
1. Entity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Entity @Table(name = "user") @EntityListeners(AuditingEntityListener.class) public class User { @Id @Column(name = "id") private String id; @Column(name = "name") private String name; @CreatedDate @Column(name = "created_date") private LocalDate createdDate; @CreatedBy @Column(name = "created_by") private String createdBy; } Colored by Color Scripter |
cs |
- audit 기능을 사용하기 위해 Spring data가 제공하는 event listener 등록
- @EntityListener로 AuditingEntityListener를 등록
- @CreateBy, @CreatedDate, @LastModifiedBy, @LastModifiedDate와 같이 audit하고 싶은 데이터 항목을 변수에 지정
- audit가 적용되는 변수에 자동으로 값을 넣고 싶은 경우, AuditorAware interface의 구현체를 구현하고 Bean으로 등록
2. AuditorAware
- audit가 적용되는 변수에 임의의 값을 설정할 수 있음
1 2 3 4 5 6 |
public class UserAuditorAware implements AuditorAware<String> { @Override public Optional<String> getCurrentAuditor() { return Optional.of("tester"); } } Colored by Color Scripter |
cs |
- LocalDate의 경우, 자동으로 들어감
- CreateBy, LastModifiedBy는 auditoraware를 생성하여 어떤 값을 넣을 지 구현해야함
3. Config
1 2 3 4 5 6 7 8 9 10 11 12 |
@Configuration @EnableTransactionManagement @EnableJpaRepositories(basePackages = "com.repository") @EnableJpaAuditing public class JPAConfig { @Bean public AuditorAware<String> auditorAware() { return new UserAuditorAware(); } } Colored by Color Scripter |
cs |
- audit 기능을 사용하기 위해 @EnableJpaAuditing 지정
- 생성한 UserAuditorAware을 bean으로 등록
4. 결과
- create(POST)
- create 후 결과(GET)
[출처] https://dongdd.tistory.com/177
'Language > Java, Android' 카테고리의 다른 글
[ERROR] Can not issue data manipulation statements with executeQuery(). @Modifying 사용하기 (0) | 2021.10.21 |
---|---|
[JPA] 특정 칼럼을 제외하고 INSERT, UPDATE하는 방법 (0) | 2021.10.20 |
Spring Boot - static 변수에서 @Value Annotation 사용 (0) | 2021.10.12 |
[JPA] 다양한 연관관계 매핑 - @OneToMany @ManyToOne @OneToOne @ManyToOne (0) | 2021.10.12 |
Spring Boot profile 환경별 설정하기(YAML) (0) | 2021.09.30 |