최근 프로젝트 시 회고 및 기록의 중요성을 느껴 오늘부터는 배운 내용들을 기록해보려고 한다. 이제 왕초보 단계는 벗어났고 사용법도 어느정도 익숙해졌으니 내부 동작 원리를 이해하는 단계로 나아가보자!💪
스프링 빈은 다음과 같은 이벤트 라이프사이클을 갖는다.
4번의 초기화 콜백 시 데이터베이스를 초기화하고 싶은데, 우선 InitializingBean 인터페이스를 구현하여 데이터베이스를 초기화해보았다.
package org.springframework.beans.factory;
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
이제 프로젝트 코드를 살펴보자!
@Service
public class DatabaseCleanup implements InitializingBean {
@PersistenceContext
private EntityManager entityManager;
private List<String> tableNames;
@Override
public void afterPropertiesSet() {
tableNames = entityManager.getMetamodel().getEntities().stream()
.filter(e -> e.getJavaType().getAnnotation(Entity.class) != null)
.map(e -> CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, e.getName()))
.collect(Collectors.toList());
}
먼저 InitializingBean을 구현한 뒤, entityManager에서 persistence unit의 메타모델을 가져왔다.
이후 @Entity
어노테이션이 붙은 클래스들의 이름을 Camel case에서 Snake case로 바꿔준 뒤, 테이블 이름의 리스트를 tableNames 배열에 담아준다.
@Transactional
public void execute() {
entityManager.flush();
entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();
이후 entityManager를 flush()해주고,
SET REFERENTIAL_INTEGRITY FALSE
를 통해 참조 무결성 제약조건으로 인해 테이블 삭제가 되지 않는 문제를 해결한다.
for (String tableName : tableNames) {
entityManager.createNativeQuery("TRUNCATE TABLE \"" + tableName + "\"").executeUpdate();
entityManager.createNativeQuery("ALTER TABLE \"" + tableName + "\" ALTER COLUMN \"id\" RESTART WITH 1").executeUpdate();
}
entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();
}
}
DROP TABLE을 하면 테이블 자체가 삭제되기 때문에 TRUNCATE TABLE을 통해 테이블의 row만 지워준 뒤,
ID 컬럼에 들어가는 시퀀스를 1부터 시작되도록 초기화해준다.
마지막으로 무효화했던 제약 조건을 SET REFERENTIAL_INTEGRITY TRUE
를 통해 다시 걸어주면 된다.
이제 AcceptanceTestBase 클래스에 DatabaseCleanup 빈을 주입받자!
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AcceptanceTestBase {
protected static final String BASE_URL = "http://localhost";
@LocalServerPort
protected int port;
@Autowired
private DatabaseCleanup databaseCleanup;
@BeforeEach
void cleanUpDatabase() {
databaseCleanup.execute();
}
}
빈이 생성된 후 의존 관계 주입이 완료되면 초기화 콜백 단계에서 afterPropertiesSet()이 실행되며, 그 이후 execute()가 실행되어 데이터베이스가 성공적으로 초기화된다.
InitializingBean 인터페이스를 구현하면 아래와 같은 단점이 존재한다.
따라서 InitializingBean 대신 @PostConstruct
어노테이션을 사용해보자!
@Service
public class DatabaseCleanup {
@PersistenceContext
private EntityManager entityManager;
private List<String> tableNames;
@PostConstruct
public void init() {
tableNames = entityManager.getMetamodel().getEntities().stream()
.filter(e -> e.getJavaType().getAnnotation(Entity.class) != null)
.map(e -> CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, e.getName()))
.collect(Collectors.toList());
}
}
그저 InitializingBean을 제거하고 afterPropertiesSet()
에 해당하는 초기화 메서드에 @PostConstruct
어노테이션을 붙이면 된다😄
Source
로고 여기 쓰셨군요 ㅋㅋ