오늘 다뤄볼 내용은 지난주에 프로젝트에 적용하게 된 JPA Auditing에 대해서 얘기해보려고 한다.
JAVA의 ORM(Object Relational Mapping) 기술에 대한 표준 명세를 의미하는 JPA ( Java Persistent API )에서 Auditing 이라는 기술을 제공한다. jpa auditing은 도메인에서 공통적으로 쓰이는 필드나 컬럼들에 대해 자동으로 값을 넣어주는 기능이며, 대표적으로는 생성일, 수정일, 생성자, 수정자가 있을 것이다. jpa auditing을 사용하지 않는다면, 도메인을 생성 혹은 업데이트할 때마다 생성 일과 생성자, 수정일과 수정자를 매번 set 해줘야 하는 번거로움이 있는데 jpa auditing을 사용한다면 굳이 그러지 않아도 자동으로 해당 값을 세팅해서 넣어주게 된다. 그리하여 코드의 중복을 줄이고 조금 더 깔끔한 코딩을 할 수 있다. 그중에서도 오늘 포스팅은 생성 일과 수정일 즉 Date값을 자동으로 넣는 로직을 구현해 볼 것이다.
기본적으로 jpa 대한 세팅이 되어 있다는 가정 하에 설명하겠다.
우선 간단하게 아래와 같은 두개의 도메인이 있다고 가정해보자.
Company.java (대충 회사 정보)
@Data
@Entity
@Builder
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String cid;
private String name;
@Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime createDate;
@Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime updateDate;
}
User.java(대충 회사원 정보)
@Data
@Entity
@Builder
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String uid;
private String name;
@Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime createDate;
@Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime updateDate;
}
위의 두 테이블에는 공통으로 createDate(생성일), updateDate(수정일) 컬럼이 들어가 있고, createDate는 생성 시점(insert)에 한 번만 기록되고, updateDate는 업데이트 시점(update) 마다 업데이트가 된 시점의 시간 값으로 업데이트가 되어야 한다. 기본적으로 auditing을 사용하지 않는다면 생성 시점과 업데이트 시점에 아래와 같은 로직이 들어가야 할 것이다.
@RestController
@RequestMapping("/company")
@RequiredArgsConstructor
public class CompanyController {
private final CompanyRepository companyRepository;
@PostMapping(value = "")
public ResponseEntity<String> insert() {
Company company = Company.builder()
.name("첫번째회사")
.createDate(LocalDateTime.now())
.updateDate(LocalDateTime.now())
.build();
this.companyRepository.save(company);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@PutMapping(value = "/{cid}")
public ResponseEntity<String> update(@PathVariable(value = "cid") Long cid) {
Company company = this.companyRepository.findByCid(cid);
company.setName("이름개명회사");
company.setUpdateDate(LocalDateTime.now());
companyRepository.save(company);
return new ResponseEntity<>(HttpStatus.OK);
}
}
위의 코드를 보면 builder 패턴으로 inser와 update 시점에서 date 값을 직업 LocalDateTime.now()로 세팅해주고 있다.
(업데이트 시점에 entity의 값을 저런식으로 직접 set 해서 바꾸는 방법은 좋은 방법이 아니나.. 쉽고 보기 쉽게 설명하기 위해 작성한 부분이므로 참고 부탁드립니다)
들어간 실제 값을 DB에서 확인해보면 문제 없이 들어간 것을 확인할 수 있다.
여기까지가 일반적인 방법일 것이다.
하지만 테이블이 늘어나고 코드마다 매번 createDate와 updateDate를 신경 쓰지 않아도 자동으로 값이 업데이트된다면 중복 코드도 줄이고 훨씬 깔끔해질 것이다.
이제 auditing을 적용해 보자.
Company.java 클래스를 아래와 같이 수정하였다.
@Data
@Entity
@Builder
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EntityListeners(AuditingEntityListener.class)
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long cid;
private String name;
@CreatedDate
@Column(updatable = false, nullable = false)
@Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime createDate;
@LastModifiedDate
@Column(nullable = false)
@Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime updateDate;
}
auditing을 위해 추가된 부분은 아래와 같다.
- @EntityListeners(AuditingEntityListener.class)
@EntityListeners 는 엔티티를 적용하기 전 후로 커스텀 콜백을 요청할 수 있는 어노테이션이며, Auditing을 적용하기 위해서 AuditingEntityListener.class를 넘기면 된다. - @CreatedDate
엔티디가 생성되어 저장될때 자동으로 시간이 들어간다. - @LastModifiedDate
엔티디의 값이 변경되어 저장될때 자동으로 시간이 들어간다.
따라서 생성일을 저장할 컬럼인 createDate위에 @CreatedDate을 넣어주고,
수정일을 저장할 컬럼인 updateDate 컬럼 위에 @LastModifiedDate를 넣어주면 된다.
추가로 @Column 부분은 auditing과는 상관없이 넣어준 값으로 해당 컬럼을 설정하는 어노테이션이라고 보면 된다.
@Column(updatable = false, nullable = false)의 의미는 해당 컬럼은 update가 일어나지 않고 null값을 허용하지 않는다라고 해석하면 될 것이다. 해당 어노테이션은 넣지 않아도 되지만 조금 더 명확하게 하기 위해 넣어주었다.
해당 엔티티 클래스에 대한 설정이 끝났다면 sptring boot 실행 파일에 @EnableJpaAuditing만 추가해주면 된다.
@SpringBootApplication
@EnableJpaAuditing
public class BlogApplication {
public static void main(String[] args) {
SpringApplication.run(BlogApplication.class, args);
}
}
정상적으로 작동하는지 확인하기 위해 createDate, updateDate 를 set 하는 로직을 빼고 다시 실행해보자.
@RestController
@RequestMapping("/company")
@RequiredArgsConstructor
public class CompanyController {
private final CompanyRepository companyRepository;
@PostMapping(value = "")
public ResponseEntity<String> insert() {
Company company = Company.builder()
.name("두번째회사")
.build();
this.companyRepository.save(company);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@PutMapping(value = "/{cid}")
public ResponseEntity<String> update(@PathVariable(value = "cid") Long cid) {
Company company = this.companyRepository.findByCid(cid);
company.setName("이름개명한두번째회사");
companyRepository.save(company);
return new ResponseEntity<>(HttpStatus.OK);
}
}
insert와 update 시점에 createDate와 updateDate를 LocalDateTime.now()로 세팅하는 부분이 제거되었다.
insert를 실행해보면 위와 같이 두 번째 회사의 createDate, updateDate가 모두 입력되어 들어간 것을 확인할 수 있다.
여기서 나는 만약 생성 시점에서 createDate만 넣고 updateDate는 넣고 싶지 않은데?라고 한다면 그렇게도 가능하게 설정할 수 있다. 하지만 이번 포스팅에서는 다루지 않을 예정이니 해당 방법에 대해서는 따로 찾아보면 될 듯하다.
update도 진행하여 값을 확인해보면 아래와 같이 정상적으로 값이 들어간 것을 확인할 수 있다.
자 이렇게 기본적인 auditing에 대한 설정이 끝났다. 그럼 createDate와 updateDate가 필요한 도메인마다 auditing을 설정해주면 될 것이다. 하지만 한발 더 나아가서... 동일하게 동작하는 같은 이름의 칼럼에 대해서 상위 클래스로 만들어 두고 관리하면 더 깔끔하고 간결해질 것이다. 처음에 예시로 설명한 도메인이 두 개였으며 두 도메인 모두 createDate, updateDate가 필요하다.
그렇다면 아래와 같이 공통으로 쓸 수 있는 상위 클래스를 만들어 볼 수 있을 것이다.
@MappedSuperclass
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EntityListeners(AuditingEntityListener.class)
public abstract class CommonEntity {
@CreatedDate
@Column(updatable = false, nullable = false)
@Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime createDate;
@LastModifiedDate
@Column(nullable = false)
@Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime updateDate;
}
CommonEntity라는 추상 클래스를 만들고 해당 클래스는 createDate와 updateDate만 가지고 있다.
다른 부분이 있다면, @MappedSuperclass 어노테이션이 추가되었다.
- @MappedSuperclass
해당 어노테이션이 있는 클래스는 엔티티로 간주되지 않고 테이블과 매핑되지도 않는다
단순하게 자식 클래스에 매핑 정보만 제공하며, 엔티티가 아니기에 조회 및 검색이 불가능하다.
직접적으로 해당 클래스만 사용할 일이 없으므로 추상 클래스(abstract)로 만드는 것이 좋다.
그리하여 해당 어노테이션을 추가해주고 추상 클래스로 선언해놓은 상태이다.
그럼 이제 공통의 클래스를 상속받아 사용하여 보자.
@Data
@Entity
@Builder
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EntityListeners(AuditingEntityListener.class)
public class Company extends CommonEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long cid;
private String name;
}
위 코드는 처음부터 계속 사용하던 ComPany 클래스이다.
extends로 방금 만든 CommonEntity를 상속받고, 기존에 있던 createDate와 updateDate가 사라졌다.
CommonEntity에 createDate와 updateDate 컬럼 정보가 있기에 필요 없는 것이다.
이 상태로 만들어놓고 다시 insert를 해보면 아래와 같이 date 정보가 정상적으로 들어가 있는 것을 확인할 수 있다.
그리고 맨 처음 만들어 두었던 User 클래스도 CommonEntity를 상속 받음으로써 createDate와 updateDate가 사라졌으며,
동일하게 User를 insert 하면 createDate와 updateDate 값이 들어가 있는 것을 확인할 수 있다.
User.java
@Data
@Entity
@Builder
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class User extends CommonEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String uid;
private String name;
}
UserController.java
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserRepository userRepository;
@PostMapping(value = "")
public ResponseEntity<String> insert() {
User user = User.builder()
.name("김개발")
.build();
this.userRepository.save(user);
return new ResponseEntity<>(HttpStatus.CREATED);
}
}
이렇게 JPA Auditing (생성일 / 수정일)과 JPA 상속 관계에 대해 알아보았다.
다음 포스팅에서는 JPA Auditing을 이용한 생성자, 수정자에 대해 알아보겠다.
2021.08.08 - [컴퓨터/java.spring] - [JPA Auditing] 생성자 / 수정자 자동화 하기
[JPA Auditing] 생성자 / 수정자 자동화 하기
오늘 다뤄볼 내용은 지난주에 포스팅한 JPA Auditing 생성일, 수정일에 이은 생성자와 수정자를 자동화하는 auditing에 대해서 포스팅 하려고한다. JAVA의 ORM(Object Relational Mapping) 기술에 대한 표준 명
compunication.tistory.com
'개발 이야기 > java' 카테고리의 다른 글
[JPA Auditing] 생성자 / 수정자 자동화 하기 (0) | 2021.08.08 |
---|---|
[Java][Spring Boot]어노테이션(Annotation) (0) | 2019.09.30 |
[Java]자료형간의 캐스팅 (0) | 2017.09.28 |
[Java][Spring]MongoDB 연동 및 사용 (0) | 2017.07.27 |
[Java][Spring]SMTP를 이용한 이메일 인증 기능 (6) | 2017.07.20 |