SW 개발/Spring
Spring Boot, JPA, @MappedSuperClass로 코드 중복 최소화하기
지단로보트
2019. 10. 17. 16:59
개요
- 물리적으로 서로 다른 데이터베이스인데, 테이블 스키마는 완전히 동일하게 적용하여 운영 중인 2개의 레거시 애플리케이션이 있다고 가정하자. 이러한 2개의 서로 다른 데이터베이스를 한 애플리케이션에서 JPA를 이용하여 동시에 접근해야할 경우, 코드 중복을 최소화하는 방법을 고민하고 정리하였다.
사전지식
- Spring Boot 환경에서 JPA를 적용하는 방법은 본 블로그의 이 글을 참고한다.
@MappedSuperClass 적용
@MappedSuperClass
은 테이블 구조에 있어 많은 컬럼을 공통적으로 공유하면서 부분적으로 다른 컬럼이 존재하는 서로 다른 테이블을 엔티티로 구현하기 위해 고안된 JPA 어노테이션이다. 따라서 완전히 동일한 구조의 서로 다른 테이블에도 적용할 수 있다. 적용 예는 아래와 같다.
// 공통 클래스를 정의
// JPA의 패키지 탐색 대상에서 제외되어야 오류가 발생하지 않음
@MappedSuperClass
abstract class User {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0
...
}
- 실제 물리 테이블에 맵핑될 엔티티의 작성 예는 아래와 같다. 이미 존재하는 공통 클래스를 통해 코드의 중복을 상당히 아낄 수 있다.
// foo 데이터베이스의 user 테이블에 대한 엔티티를 정의
// JPA의 패키지 탐색 대상에 포함되어 있어야 함
@Entity
@Table(name = "foo.user")
class FooUser : User()
// bar 데이터베이스의 user 테이블에 대한 엔티티를 정의
// JPA의 패키지 탐색 대상에 포함되어 있어야 함
@Entity
@Table(name = "bar.user")
class BarUser : User()
- 위를 통해 엔티티에 대한 코드 중복은 최소화했다. 이 것으로 끝이 아니다. Spring Data JPA에서의 리파지터리에서도 코드 중복을 고려해야 한다. 작성 예는 아래와 같다.
// 공통 리파지터리를 정의
// JPA의 패키지 탐색 대상에서 제외되어야 오류가 발생하지 않음
interface UserRepository<T, ID> : JpaRepository<T, ID> {
fun findByIdOrderByIdDesc(id: ID): T?
}
// foo 데이터베이스의 user 테이블에 대한 리파지터리를 정의
// JPA의 패키지 탐색 대상에 포함되어 있어야 함
interface FooUserAuthRepository : UserRepository<FooUser, Long>
// bar 데이터베이스의 user 테이블에 대한 리파지터리를 정의
// JPA의 패키지 탐색 대상에 포함되어 있어야 함
interface BarUserAuthRepository : UserRepository<BarUser, Long>
@MappedSuperClass 적용의 한계
- @MappedSuperClass로 코드 중복은 최소화했지만, 엔티티와 리파지터리도 물리적으로 구분되면서 서비스 레이어에서 공통 로직을 처리하려면 구조가 상당히 복잡해진다. 이런 특수한 경우라면 차라리 애플리케이션을 분리하여 같은 소스지만
application.yml
에 차이를 두고, 각각 지정된 데이터베이스의 테이블을 바라보는 것이 적절하다.