package com.example.bookmanager.domain;
import com.example.bookmanager.domain.listener.Auditable;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.time.LocalDateTime;
@Data
// 상속받은 BaseEntity를 사용하기 위해서 ToString, EqualsAndHashCode 재정의
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Entity
@NoArgsConstructor
// 자동으로 생성날짜, 수정날짜를 입력해주기 위해서 선언
//@EntityListeners(value = MyEntityListener.class)
// 공통 클래스 사용
//@EntityListeners(value = AuditingEntityListener.class)
// User객체에 History를 저장할 객체 생성
// BaseEntity를 상속받아 그 안에 있는 설정들을 사용할 수 있다.
public class UserHistory extends BaseEntity {
@Id
@GeneratedValue
// @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NonNull
private String name;
@NonNull
private String email;
// update 할 경우 값을 제외하도록 설정
// @Column(updatable = false)
// 자동으로 해당값을 넣어준다.
// @CreatedDate
// private LocalDateTime createdAt;
// 자동으로 해당값을 넣어준다.
// @LastModifiedDate
// private LocalDateTime updatedAt;
}
공통으로 수정한 날짜와 생성한 날짜를 처리해줄 BaseEntity를 생성
package com.example.bookmanager.domain;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
// 공통으로 사용할 수정날짜, 생성날짜를 자동으로 처리해줄 Entity 선언
@EntityListeners(value = AuditingEntityListener.class)
// get, set 선언
@Data
// 해당 클래스를 상속받는 클래스에 현재 변수를 사용할 수 있도록 해주는 어노테이션
@MappedSuperclass
public class BaseEntity {
@Column(updatable = false)
// jpa에서 기본적으로 선언된 기능을 사용
@CreatedDate
private LocalDateTime createdAt;
// jpa에서 기본적으로 선언된 기능을 사용
@LastModifiedDate
private LocalDateTime updatedAt;
}
package com.example.bookmanager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
@EnableJpaAuditing
public class BookmanagerApplication {
public static void main(String[] args) {
SpringApplication.run(BookmanagerApplication.class, args);
}
}
원본 DB에 데이터가 생성, 수정되었을때 처리해주기 위한 클래스 생성
package com.example.bookmanager.domain.listener;
import com.example.bookmanager.domain.User;
import com.example.bookmanager.domain.UserHistory;
import com.example.bookmanager.repository.UserHistoryRepository;
import com.example.bookmanager.support.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
// Autowired를 사용할 경우 spring bean 등록을 진행해야 하기 때문에 Component 어노테이션을 사용한다.
//@Component
public class UserEntityListener {
// @Autowired
// private UserHistoryRepository userHistoryRepository;
// 업데이트, 생성를 진행할 경우 자동으로 history에 저장되도록 설정
@PreUpdate
@PrePersist
public void prePersistAndPreUpdate(Object o) {
// UserHistoryRepository의 bean을 가져온다.
UserHistoryRepository userHistoryRepository = BeanUtils.getBean(UserHistoryRepository.class);
User user = (User) o;
UserHistory userHistory = new UserHistory();
userHistory.setId(user.getId());
userHistory.setName(user.getName());
userHistory.setEmail(user.getEmail());
userHistoryRepository.save(userHistory);
}
}
위 코드가 정상적으로 동작하기 위해서는 Repository에 bean값을 가져와야하기 때문에 그 기능을 사용하게 해 줄 클래스 생성
package com.example.bookmanager.support;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
// Bean 클래스를 생성해주기위한 클래스
@Component
public class BeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
BeanUtils.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
}
User 클래스에 history기능을 사용하기 위해서 @EntityListeners 어노테이션에 추가
//공통 Entity 사용
@EntityListeners(value = {AuditingEntityListener.class, UserEntityListener.class})
//@Table(name = "user", indexes = {@Index(columnList = "name")}, uniqueConstraints = {@UniqueConstraint(columnNames = {"email"})})
public class User {
테스트 코드
@Autowired
private UserHistoryRepository userHistoryRepository;
@Test
void userHistoryTest(){
User user = new User();
user.setEmail("martin-new@naver.com");
user.setName("martin-new");
userRepository.save(user);
// 업데이트 발생
user.setName("martin-new-new");
userRepository.save(user);
userHistoryRepository.findAll().forEach(System.out::println);
}
@PrePersist
public void prePersist(){
System.out.println(">>> prePersist");
// 사용자가 입력을 안해주어도 자동으로 현재 날짜를 입력하도록 설정
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PostPersist
public void postPersist(){
System.out.println(">>> postPersist");
}
@PreUpdate
public void preUpdate(){
System.out.println(">>> preUpdate");
// Update를 진행할 때 자동으로 update를 진행한 날짜를 입력하도록 설정
this.updatedAt = LocalDateTime.now();
}
@PostUpdate
public void postUpdate(){
System.out.println(">>> postUpdate");
}
@PreRemove
public void preRemove(){
System.out.println(">>> preRemove");
}
@PostRemove
public void postRemove(){
System.out.println(">>> postRemove");
}
@PostLoad
public void postLoad(){
System.out.println(">>> postLoad");
}
테스트 코드 작성
@Test
void listenerTest(){
Dto();
User user = new User();
user.setEmail("martin2@fastcapmus.com");
user.setName("martin");
userRepository.save(user);
User user2 = userRepository.findById(1L).orElseThrow(RuntimeException::new);
user2.setName("marrrrrrrtin");;
userRepository.save(user2);
userRepository.deleteById(4L);
}
확인
저장하기 전과 저장한 후에 호출
조회 후에 호출
업데이트 전과 후에 호출
삭제 전 후에 호출
주로 사용자가 프로그램상에서 새로 데이터를 생성하거나 수정을 하였을때 날짜를 자동으로 입력해줄 때 사용할 수 있다.
테스트 코드
@Test
void prePersistTest() {
User user = new User();
user.setEmail("martin2@fastcampus.com");
user.setName("martin");
// user.setCreatedAt(LocalDateTime.now());
// user.setUpdatedAt(LocalDateTime.now());
userRepository.save(user);
System.out.println(userRepository.findByEmail("martin2@fastcampus.com"));
}
@Test
void preUpdateTest() {
Dto();
User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);
System.out.println("as-is: " + user);
user.setName("martin22");
userRepository.save(user);
System.out.println("to-be: " + userRepository.findAll().get(0));
}
@Test
void insertAndUpdateTest(){
User user = new User();
user.setName("martin");
user.setEmail("martin2@fastcampus.com");
userRepository.save(user);
User user2 = userRepository.findById(1L).orElseThrow(RuntimeException::new);
user2.setName("marrrrrrrrrtin");
userRepository.save(user2);
}
insert 처리 할경우 false 처리했던 updatedAt 컬럼을 제외시킨다
update 처리 할경우 false 처리했던 createdAt 컬럼을 제외시킨다
그 외에도 다른 속성들을 지정해 줄 수 있다.
@Transient: 변수명에 선언하며 선언하게 되면 해당 컬럼은 DB에 반영되지 않고 프로그램 실행시에만 생성되었다가 프로그램 종료시 함께 종료되는 컬럼으로 선언 된다.
// 페이징 처리
System.out.println("findByName: " + userRepository.findByName("martin", PageRequest.of(0, 1, Sort.by(Sort.Order.desc("id")))));
확인
페이징 처리된 데이터를 확인하는 방법
테스트 코드
// 내부의 값을 가져오는 방법
System.out.println("findByName2: " + userRepository.findByName("martin", PageRequest.of(0, 1, Sort.by(Sort.Order.desc("id")))).getContent());
확인
총 페이징 처리된 갯수를 확인하는 방법
// 총 페이지 갯수를 확인하는 방법
System.out.println("findByName3: " + userRepository.findByName("martin", PageRequest.of(0, 1, Sort.by(Sort.Order.desc("id")))).getTotalElements());