Spring Data JPA 완벽 가이드 - Entity, Repository, Cache
Spring Data JPA는 데이터베이스 연동을 간편하게 해주는 프레임워크입니다. 이 글에서는 Entity 정의부터 다양한 Repository 인터페이스, 그리고 캐싱 전략까지 살펴보겠습니다.
데이터소스 설정
application.properties 설정
# Data source
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/MyDB?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=password
# JPA
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.ddl-auto=update # 테이블 자동 생성 및 업데이트
# SQL 로그 출력
spring.jpa.show-sql=true
ddl-auto=update는 개발 환경에서만 사용하세요. 운영 환경에서는validate나none을 권장합니다.
Entity 정의
기본 Entity
@Entity
data class Todo(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
var id: Int = 0,
@get:NotBlank
var todoDescription: String,
@get:NotBlank
var todoTargetDate: String,
@get:NotBlank
var status: String
) {
constructor() : this(0, "", "", "")
}
주요 어노테이션
| 어노테이션 | 설명 |
|---|---|
@Entity |
JPA Entity 클래스임을 선언 |
@Id |
Primary Key 필드 지정 |
@GeneratedValue |
자동 생성 전략 설정 |
@Column |
컬럼 세부 설정 |
@NotBlank |
유효성 검증 (null이 아니고 공백이 아님) |
테이블 이름 지정
클래스 이름과 다른 테이블 이름을 사용하려면:
@Entity(name = "mccmnc")
public class MccMnc {
// ...
}
복합 Primary Key
@Entity
@IdClass(MccMncId.class)
public class MccMnc {
@Id
private String mcc;
@Id
private String mnc;
// ...
}
// ID 클래스
public class MccMncId implements Serializable {
private String mcc;
private String mnc;
// equals, hashCode 구현 필요
}
Auto Increment 설정
@Id
@Column(name = "id", updatable = false, nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0
Timestamp 자동 관리
생성 시간 자동 설정:
@Temporal(TemporalType.TIMESTAMP)
@Column(
updatable = false,
insertable = false,
columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
nullable = false
)
lateinit var createDate: Timestamp
수정 시간 자동 업데이트:
@Column(
updatable = false,
insertable = false,
columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP",
nullable = false
)
lateinit var updateDate: Timestamp
Native Query 정의
@NamedNativeQueries({
@NamedNativeQuery(
name = "HistoryStatistics.getCollectTable",
query = "SELECT sequence_id, uid, DATE_FORMAT(registration_date,'%m-%d') as date, " +
"COUNT(DISTINCT id) as count " +
"FROM message_collect_user_history " +
"WHERE registration_date BETWEEN ?1 AND ?2 " +
"GROUP BY DATE_FORMAT(registration_date,'%Y-%m-%d'), uid",
resultClass = HistoryStatistics.class
)
})
@Entity
public class HistoryStatistics {
// ...
}
Repository 인터페이스
Spring Data JPA는 다양한 Repository 인터페이스를 제공합니다.
Repository (기본)
가장 기본적인 인터페이스로, 필요한 메서드만 직접 정의합니다:
public interface CountryRepository extends Repository<Country, Integer> {
// 메서드 이름으로 쿼리 생성
public Country findByName(String countryName);
// 직접 쿼리 작성
@Query("SELECT c FROM Country c WHERE c.name = ?1")
public Country findByNameQueryPositionalParam(String countryName);
}
CrudRepository
기본 CRUD 메서드를 제공합니다:
public interface UserRepository extends CrudRepository<UserRecord, String> {
// save, findById, findAll, delete 등 기본 제공
}
JpaRepository
페이징과 정렬 기능이 추가된 인터페이스입니다:
public interface HistoryStatisticsDao extends JpaRepository<HistoryStatistics, String> {
List<HistoryStatistics> getCollectTable(String startDate, String endDate);
}
Kotlin 예시:
@Repository
interface TodoRepository : JpaRepository<Todo, Int>
QueryDslPredicateExecutor
동적 쿼리를 위한 Predicate를 사용할 수 있습니다:
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>3.4.3</version>
</dependency>
public interface MccMncDao extends CrudRepository<MccMnc, MccMncId>,
QueryDslPredicateExecutor<MccMnc> {
}
QueryDslRepositorySupport
복잡한 쿼리를 위한 지원 클래스입니다:
@Repository
public class MccMncSupportDao extends QueryDslRepositorySupport {
public MccMncSupportDao() {
super(MccMnc.class);
}
public List<Tuple> getMccMncGroupByOperatorIdCircleId() {
QMccMnc qMccMnc = QMccMnc.mccMnc;
return from(qMccMnc)
.where(qMccMnc.support.eq(true))
.groupBy(qMccMnc.operatorId, qMccMnc.circleId)
.orderBy(qMccMnc.operatorId.asc(), qMccMnc.circleId.asc())
.list(qMccMnc.mcc, qMccMnc.mnc, qMccMnc.operatorId,
qMccMnc.circleId, qMccMnc.operatorName, qMccMnc.circleName);
}
}
// Tuple 사용
Tuple t;
int mcc = t.get(QMccMnc.mcc);
캐싱 (Cache)
반복적인 데이터베이스 조회를 줄이기 위해 캐싱을 활용할 수 있습니다.
캐싱 활성화
@SpringBootApplication
@EnableCaching
class MyApplication
Repository에 캐싱 적용
interface DictionaryDao : JpaRepository<Dictionary, Int> {
@Cacheable("dictionary")
override fun findAll(): MutableList<Dictionary>
@CacheEvict(value = "dictionary", allEntries = true)
override fun <S : Dictionary?> save(entity: S): S
@CacheEvict(value = "dictionary", allEntries = true)
override fun <S : Dictionary?> saveAll(entities: MutableIterable<S>): MutableList<S>
}
캐시 어노테이션 설명
| 어노테이션 | 설명 |
|---|---|
@Cacheable |
결과를 캐시에 저장, 이후 호출 시 캐시에서 반환 |
@CacheEvict |
캐시에서 항목 제거 |
@CachePut |
항상 메서드 실행 후 결과를 캐시에 저장 |
캐시 구현체 선택
기본적으로 ConcurrentHashMap 기반의 간단한 캐시를 사용하지만, 운영 환경에서는 다음을 고려하세요:
- Redis: 분산 캐시가 필요한 경우
- Ehcache: 로컬 캐시로 충분한 경우
- Caffeine: 고성능 로컬 캐시
# Redis 캐시 사용 예시
spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379
결론
Spring Data JPA는 데이터베이스 작업을 크게 단순화합니다. Entity 설계 시 적절한 어노테이션을 활용하고, 상황에 맞는 Repository 인터페이스를 선택하세요. 캐싱을 적용하면 성능을 더욱 향상시킬 수 있습니다. 다만, 캐시 무효화 전략을 잘 수립하여 데이터 정합성 문제가 발생하지 않도록 주의해야 합니다.
Comments