테이블을 설계할때 항상 빠지지 않고 들어있는 컬럼이 있다. 바로 생성일자, 수정일자, 생성자, 수정자 컬럼이다.
거의 모든 테이블에 디폴트로 들어있는 컬럼이고 상당히 중요한(?) 정보이다. 그렇다 보니 도메인에도 항상 똑같은 컬럼이 존재하게 된다. 그런데 spring-data-jpa에 재미난 기능이 있다. 바로 생성일자, 수정일자, 생성자, 수정자 컬럼에 값을 자동으로 넣어주는 기능이다.
Audit : 감시하다.
바로 spring-data-jpa 에서는 audit 기능을 제공하고 있다. 방법은 여러가지가 있다. 각 도메인에 @PrePersist, @PreUpdate 등을 붙여서 할수도 있다. 그런데 이게 단점이 각각의 도메인에 같은 컬럼이 정의되어있다는 것은 변하지 않는다는 것이다. 모든 도메인에 똑같이 createDate, modifyDate, createUser, modifyUser가 있어야 한다. 그래서 이런 부분을 피하기 위해 다른 방법을 택했다.
1 2 3 4 5 | @MappedSuperclass @EntityListeners(value = {AuditingEntityListener.class}) public class AuditableDomain extends AbstractAuditable<Account, Long> { private static final long serialVersionUID = 1L; } | cs |
위 소스에 보면 @EntityListeners에 AuditingEntityListener를 넣어줬다. 그리고 지금 구현한 Class는 AbstractAuditable을 상속받고 있다. AbstractAuditable 클래스의 Account는 실제 User 정보를 담고 있는 Domain 이고 Long은 그 도메인의 Key 값을 의미한다.
AbstractAuditable 클래스의 필드를 보면 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @MappedSuperclass public abstract class AbstractAuditable<U, PK extends Serializable> extends AbstractPersistable<PK> implements Auditable<U, PK> { private static final long serialVersionUID = 141481953116476081L; @ManyToOne private U createdBy; @Temporal(TemporalType.TIMESTAMP) private Date createdDate; @ManyToOne private U lastModifiedBy; @Temporal(TemporalType.TIMESTAMP) private Date lastModifiedDate; .. 중략 } | cs |
이렇게 createBy, createDate, lastModifedBy, lastModifiedDate 필드를 가지고 있기 때문에 더이상 하위 Domain 에서는 이 필드들을 정의할 필요가 없어진다. 그리고 다시 AbstractAuditable 클래스는 AbstractPersistable을 상속받고 있다. 보면 PK 를 정의하고 있다.
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 | @MappedSuperclass public abstract class AbstractPersistable<PK extends Serializable> implements Persistable<PK> { private static final long serialVersionUID = -5554308939380869754L; @Id @GeneratedValue private PK id; /* * (non-Javadoc) * @see org.springframework.data.domain.Persistable#getId() */ public PK getId() { return id; } /** * Sets the id of the entity. * * @param id the id to set */ protected void setId(final PK id) { this.id = id; } .. 중략 } | cs |
결론적으로 이 AbstractPersistable 클래스에서 PK 를 정의하고 있기 때문에 각각의 도메인들은 더이상 id를 정의할 필요가 없어진다.
그런데 내가 이걸 테스트 하다보니 문제가 생겼다.
사용자 정보를 담은 도메인인 Account 에는 id를 String 으로 loginId 라는 이름으로 넣어놨다. AbstractPersistable 클래스에서 정의하는 ID 랑 다르기 때문에 @AttributeOverride 를 써서 재정의를 해보았는데 이상한 현상이 발생했다. DB 테이블에 loginId 말고 id라는 컬럼이 따로 생기고 각각의 필드들도 id랑 loginId랑 2개씩 생기는 현상이 발견됐다. createById, createByLoginId 이런 형태로 2개씩 생겼다.
그리고 두번째로 ID를 set을 할 수가 없다. 그렇다 보니 ID를 정의할 수 없어서 결국 domain의 Id는 @generateValue 가 가능한 Long 형태로 변경했다. 혹시라도 다시 방법을 찾게 된다면 다시 포스팅을 하겠다..
1 2 3 4 5 6 7 8 9 | @EnableJpaAuditing @Configuration public class AuditingConfig { @Bean CustomAuditorAware auditorAware() { return new CustomAuditorAware(); } } | cs |
1 2 3 4 5 6 7 8 9 10 | public class CustomAuditorAware implements AuditorAware<Account>{ @Override public Account getCurrentAuditor() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (null == authentication || !authentication.isAuthenticated()) { return null; } return (Account) authentication.getPrincipal(); } } | cs |
그래서 이렇게 getCurrentAuditor method를 재정의 해준다. 지금 작성하고 있는 프로젝트는 spring security 가 적용된 프로젝트이기 때문에 SecurityConextHolder에서 정보를 가져와서 보여준다.
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 36 37 38 | (SpringRunner.class) @SpringBootTest(classes = StudySpringApplication.class) public class AccountServiceTest { private static Logger logger = LoggerFactory.getLogger(AccountServiceTest.class); @MockBean private CustomAuditorAware customAuditorAware; @Autowired private AccountService accountService; @Autowired private RoleService roleService; @Before public void setup(){ Account sessionAccount = accountService.get("admin"); Mockito.when(customAuditorAware.getCurrentAuditor()).thenReturn(sessionAccount); } @Test public void createAccount(){ Account newAccount = new Account(); newAccount.setLoginId("admin1"); newAccount.setUsername("admin user"); newAccount.setPassword("admin1!"); newAccount.setEmail("admin@spring.com"); newAccount.setEnable(true); accountService.create(newAccount, null); Account account = accountService.get("admin1"); Assert.assertEquals("admin1", account.getLoginId()); Assert.assertNotNull(account.getCreatedBy()); } } | cs |
결과를 확인해보기 위해 Test 코드를 돌려보면 잘 적용이 되는것을 확인 할수 있다.
'Development > Java' 카테고리의 다른 글
[SpringBoot]Spring Boot Oauth login With Facebook 삽질기!!! (0) | 2018.03.04 |
---|---|
작성중-[SpringBoot] ClassPathResource 의 getFile() 사용시 FileNotFoundException 발생 (0) | 2018.01.12 |
[Spring Security] Max Session 적용하기 (0) | 2017.11.20 |
[Spring Boot]RabbitMQ 연동하기 (0) | 2017.10.27 |
[Spring Boot]Rest API 호출을 위한 Client Code (0) | 2017.09.14 |