JPA 탐구
JPA 정말 잘 알고 사용하고 있을까?
실무를 하고 Spring boot를 사용하면서 정말 많이 JPA를 사용해왔었다. 하지만 그때 그때 필요한 기능만 검색하면서 사용해서 깊이 있는 지식은 부족한것 같다는 생각이 들었다. 자주 사용하는 기술이니만큼 제대로 알고 사용하고 싶었기 때문에 기술 정리를 시작하게 되었다.
JPA란? 무엇일까.
JPA(Java persistence API)는 자바 진영에서 ORM기술 표준으로 사용되는 인터페이스의 모음이다.
JPA자체는.실제적으로 구현된 기능이 아니기 때문에 Hibernate같은 구현체를 통해서 기능으로서 동작하게 된다.
ORM(object-relational mapping)이란?
Class 와 RDB 의 테이블을 연결한다는 뜻이다, 어플리케이션의 객체를 자동으로 영속화 해주는 것.
JPA와 Hibernate의 관계를 비유
JPA는 규칙서(인터페이스)
→ “이런 기능이 필요하고, 이렇게 동작해야 해.“라고 정의함.
Hibernate는 실제 선수(구현체)
→ JPA가 규칙서로 제시한 기능을 실제로 구현하고 동작시킴.
JPA를 사용함으로서 개발자들은 sql 문이 아니라 method를 통해서 db를 조작할 수 있게되며, 이를 통해서 개발자는 객체 모델을 이용해서 비즈니스 로직을 구성하는데만 집중할수 있게됩니다.
영속화란?
메모리상 존재하는 객체 데이터를 데이터베이스에 영구적으로 저장하거나. 데이터베이스에서 조회한 데이터를 객체로 관리하는 과정을 말한다. 또한, 영속 상태의 객체는 JPA 영속성 컨텍스트에 의해 관리되며, 데이터의 변경 사항이 지속적으로 추적 및 동기화 될 수 있다.
이러한 영속화는 EntityManger 에 의해서 자동으로 관리를 해준다. 모든 객체가 자동으로 영속 상태가 되는건 아니며 아래의 방법을 통해서 영속 상태로 관리가 된다. (영속 상태의 객체는 변경이 발생하면 JPA가 자동으로 이를 데이터베이스에 반영해서 개발자가 명시적으로 save나 update를 하지 않아도 트랜젝션 커밋시점에 데이터가 저장되는 더티 체킹이 동작한다)
기본적으로 Spring boot에서 EntityManager를 사용하려면 JPA 구현체를 포함하는 의존성을 추가해야하는데
spring-boot-starter-data-jpa의존성이 Hibernate를 표함하고 있다. (Hibernate 말고. 다른 구현체도 있지만 제일 대표적으로 사용된다)
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
EntityManger로 영속화 관리
EntityManger는 JPA의 핵심 인터페이스로, 데이터베이스와의 상호작용을 담당 한다.
아래와 같은 메서드로 엔티티를 영속 상태로 관리 할 수 있다.
Entitymanger 주요 메서드
1. persist(entity) : 새로운 엔티티를 영속성 컨텍스트에 등록.
2. find(Entity.class, id) : 데이터베이스에서 엔티티를 조회하면 영속 상태로 반환.
3. getReference() : 프록시 객체로 반환되지만, 필요시 영속 형태로 반환.
4. save(entity) (JpaRepository) : 내부적으로는 persist() 또는 merge() 수행
5. merge(entity) : 준영속 상태의 엔티티를 영속 상태로 변경
6. remove(entity) : db에서 엔티티 삭제
7. detach(entity) : 특정 엔티티를 영속성 컨텍스트에서 제외
8. clear() : 영속성 컨텍스트 전체 초기화 (모든 객체 detach)
9. flush() : 영속성 컨텍스트 변경 사항 즉시 db 반영 (트랜잭션 유지된 상태)
프록시 객체란?
JPA가 실제 데이터베이스 조회를 지연(Lazy loading) 시키기 위해 만들어 놓은 가짜 객체로
실제로 데이터를 조회하지 않고, ID만 가진 가짜 엔티티 객체를 생성해서 반환한다.
객체가 실제로 필요한 시점에 데이터베이스에서 조회하여 영속상태가 된다.
JPA 엔티티 객체 상태
비영속(transient) : new로 생성했지만 아직 JPA가 관리하지 않는 상태
영속(persistent) : 영속성 컨텍스트가 관리하는 상태
준영속(detached) : 원래 영속 상태였지만, 영속성 컨텍스트에서 분리된 상태
영속화로 인해서 데이터가 관리되는건 트랜젝션 커밋시점에 데이터를 반영하기때문에.
@Transactional 을 통해서 처리하거나 transctional.commit을 통해서 처리할 수 있다.
추가로 영속상태의 객체는 트랜젝션 내에서 JPA가 스냅션을 만들어 해당 데이터를 관리하고 해당 객체의 값이 변경되면 트랜잭션 커밋 시점에 스냅샷과 현재 상태를 비교해서 변경사항이 있는지 확인하고 변경사항이 있으면 SQL 쿼리를 생성해서 데이터베이스에 반영한다.
@Transactional 을 사용하면 해당 메소드 내에서 트랜잭션이 자동으로 관리되면서
(트랜잭션의 시작, 커밋, 롤백을 개발자가 수동으로 처리 x) 좀 더 간결하게 사용할 수 있다.
@Service
public class UserService {
@PersistenceContext
private EntityManager entityManager;// @PersistenceContext를 붙이면 Spring이 자동으로 EntityManager를 주입
@Transactional
public void updateUser(Long id, String newName) {
User user = entityManager.find(User.class, id); // ✅ 영속 상태
user.setName(newName); // ✅ Dirty Checking 동작
} // ✅ 트랜잭션 커밋 시점에 UPDATE 자동 실행됨
}
위와 같은 경우는 직접 entitymanger를 불러와서 직접 관리해주는 케이스
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional
public void updateUser(Long id, String newName) {
User user = userRepository.findById(id).orElseThrow(); // ✅ find() → 영속 상태
user.setName(newName); // ✅ Dirty Checking 동작
} // ✅ @Transactional → 트랜잭션 커밋 → 자동으로 UPDATE 실행됨
}
위와 같은 경우는 entitymanager를 좀 더 쉽게 사용할 수 있도록한 JpaRepository 구현체를 통해서 처리한 케이스.
@Service
public class UserService {
@PersistenceContext
private EntityManager entityManager;
public void updateUser(Long id, String newName) {
User user = entityManager.find(User.class, 1L); // ✅ 영속 상태
user.setName("Updated Name"); // ✅ 변경 발생
entityManager.getTransaction().commit(); // ✅ Dirty Checking → UPDATE 실행됨!
}
}
위와 같은 경우는 @Transactional 없이 직접 transactional을 관리 해서 commit 해준 예시
ps. flush()는 영속성 컨텍스트의 변경사항을 즉시 db에 반영한다. 하지만 기본적으로 트랜젝션 커밋 시점에 자동으로 flush()가 호출되기 때문에 일반적으로는 사용 안해도 괜찮다.
JpaRepository 란?
사실상 jpa를 사용하다보면 직접 entitymanger를 사용해서 데이터를 처리해본 경험은 거의 없는것 같다. 보통은 JpaRepository를 extends 받아서 Repsitory를 만들어서 데이터를 사용하게 된다. 이때의 JpaRepository란 무었일까?
기본적으로 JpaRepository는 Spring Data JPA가 제공하는 리포지토리 인터페이스로, 내부적으로 EntityManager를 사용해서 JPA 작업을 수행한다(영속성 컨텍스트 자동 관리). 즉 EntityManager를 더 쉽게 사용할 수 있도록 고수준 API를 제공해준다.
즉 JPA의 표준 스펙은 아니지만 Spring이 개발자의 생산성을 높이기 위해만든 고유 인터페이스로 보면 된다.
ex)
save () -> 새 엔티티는 persist(), 기존 엔티티는 merge()
findById() -> EntityManager.find()
delete() -> EntityManger.remove()
해당 기능이 어떻게 구현된거는 아래의 flow와 같다.
추가로 개발을 하다보면. JpaRepository 말고 CrudRepository도 보게되는데 CrudRepository는 단순하게 CRUD 메소드만 제공을하고 JpaRepository는 페이징 정렬등 더 많은 기능을 제공해준다
기능에 대한 관계는 아래의 flow와 같다.
[Repository] - 최상위 인터페이스. 마커 인터페이스 역할만 함.
↓
[CrudRepository] - 기본적인 CRUD 기능 제공.
↓
[PagingAndSortingRepository] - CrudRepository를 확장. 페이징 및 정렬 기능 추가.
↓
[JpaRepository] - PagingAndSortingRepository를 확장. JPA 관련 고급 기능 추가.
영속성 컨텍스트로 관리되지 않는 상황
1. @Query를 통해서 custom query를 사용하는 특정 케이스
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findUsersByName(@Param("name") String name); // ✅ 영속 상태
@Query("SELECT u.id, u.name FROM User u WHERE u.name = :name")
List<Object[]> findUsersWithProjection(@Param("name") String name); // ❌ 비영속 상태 (영속성 컨텍스트 X)
}
- findUsersByName()은 엔티티(User)를 그대로 조회하므로 영속 상태
- findUsersWithProjection()은 필드 일부만 조회하므로 영속성 컨텍스트에서 관리되지 않음
- nativequery로 사용한다고 해도 동일함 결과가 엔티티면 영속성 컨텍스트에서 관리된다.
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT new com.example.dto.UserDto(u.id, u.name) FROM User u WHERE u.name = :name")
List<UserDto> findUsersAsDto(@Param("name") String name); // ❌ 비영속 상태 (DTO는 관리 대상 X)
}
new DTO()를 반환하면 JPA가 엔티티가 아니라 DTO 객체를 직접 생성해서 반환
DTO는 영속성 컨텍스트에서 관리되지 않음 → Dirty Checking X, 변경 반영 X
2. readOnly = true 트랜잭션을 설정한 경우.
@Service
public class UserService {
@Transactional(readOnly = true)
public User findUser(Long id) {
return entityManager.find(User.class, id); // ❌ 영속성 컨텍스트에서 관리되지 않음
}
}
readOnly를 true로 설정하는 경우는 읽기 전용 트랜잭션을 설정해서 불필요한 트랜잭션 비용을 줄이고 데이터 무결성을 보장하기 위해서 사용된다.
위의 예시에서도 더티체킹이 비활성화 되면서 불필요한 성능 낭지를 방지할 수 있었다.
3. createNativeQuery() 를 사용한 경우.
List<Object[]> users = entityManager.createNativeQuery("SELECT id, name FROM users").getResultList(); // ❌ 영속성 컨텍스트에서 관리되지 않음
createNativeQuery()는 엔티티가 아닌 DB 데이터를 직접 조회
결과가 엔티티가 아니므로 영속성 컨텍스트에서 관리되지 않음
추상화된 메소드를 제공한다, @Query로 custom query를 제공한다
영속성 컨텍스트란?
위에서 계속해서 나오는말은 JPA가 객체를 영속화해서 실제 데이터베이스와 데이터를 일치시킨다는 이야기를 계속 하고 있다.
그러면 결국 계속해서나오는 이 영속성 컨텍스트라는게 어떻게 사용될까?
영속성 컨텍스트(persistence context)는 JPA에서 엔티티 객체를 관리하는 메모리 공간이다.
즉 JPA가 데이터베이스와 연결된 엔티티 객체를 보관하고 관리하는 1차 캐시 역할을 하는 곳이다.
JPA는 영속성 컨텍스트를 통해 엔티티 객체를 관리하고, 변경사항을 감지하고 데이터베이스와의 동기화를 자동으로 처리해준다. 이러한 역할을 해주고 있기 때문에 직접 sql을 실행하지 않고도 객체 수정만으로 자동 DB 반영등의 처리가 가능해진 것이다.
1차 캐시
find()로 조회한 엔티티는 영속성 컨텍스트에 저장된다. 같은 트랜잭션 내에서 조회 시 DB 조회 없이 캐시에서 값을 가져온다.
어플리케이션이 시작을하면 EntityMangerFactory가 1개가 생성된다. (EntityManager를 생성하는 공장).
이후 새로운 트랜잭션이 생성될때마다 새로운 Entitymanger가 생성되며 해당 EntityManger에서 새로운 영속성 컨텍스트가 생성된다.
이후 트랜잭션이 종료되면 해당 EntityManger와 영속성 컨텍스트는 제거된다.
(로직은 EntityManger가 영속성 컨텍스트를 기반으로 생성, 수정, 조회. 삭제 등의 처리를 요청하는것이다)
그리고 트랜잭션에서 entityManger를생성할때 DB Connection pool 에서 커넥션 하나를 할당 받고 트랙잭션이 종료될때 반환한다.
(같은 트랜잭션 내에서는 여러개의 entityManger가 사용되어도 하나의 커넥션만 사용한다)
EntityManger가 직접 DB커넥션을 관리하는 것이 아니라 HikariCp등의 커넥션 풀을 통해 관리된다.
HikariCP란
java 기반의 고성능 데이터베이스 커넥션 풀로 spring boot에서 기본적으로 제공하는 DB 커넥션 풀 라이브러리 이다.
Join기능 사용하기
기능을 만들기위해서 테이블을 설계하다 보면 하나의 기능을 위해서 여러개의 관련된 테이블이 생성되게 된다. JPA에서는 이러한 테이블간의 관계를 설정해서 편하게 조회할 수 있도록 @OneToOne, @OneToMany등의 어노테이션을 제공한다.
Cascase (영속성 전이)
JPA에서 영속성 전이는 부모 엔티티의 변경을 자식 엔티티에도자동으로 적용하는 기능으로 부모엔티티를 저장할 때 자식 엔티티도 자동으로 저장되거나, 부모를 삭제하면 자식도 함께 삭제하도록 하는 설정이다. Cascade를 사용하면 save(), delete() 등을 부모 엔티티에서만 호출해도 자식 엔티티에 자동으로 반영된다. 반대로, Cascade를 사용하지 않으면 자식 엔티티를 별도로 저장하거나 삭제해야 한다.
- CascadeType.ALL: 모든 Cascade를 적용
- CascadeType.PERSIST: 엔티티를 영속화할 때, 연관된 엔티티도 함께 유지
- CascadeType.MERGE: 엔티티 상태를 병합(Merge)할 때, 연관된 엔티티도 모두 병합
- CascadeType.REMOVE: 엔티티를 제거할 때, 연관된 엔티티도 모두 제거
- CascadeType.DETACH: 부모 엔티티를 detach() 수행하면, 연관 엔티티도 detach()상태가 되어 변경 사항 반영 X
- CascadeType.REFRESH: 상위 엔티티를 새로고침(Refresh)할 때, 연관된 엔티티도 모두 새로고침
@OneToOne 사용하기
한 개의 테이블과 다른 하나의 테이블 두개의 테이블이. 1:1로 연결되는 상황
예를 들어서 유저 정보와 해당 유저의 profile 정보를 가진 테이블이 있다고 했을때 key를 어떻게 배치할지에 따라서 3가지 방법으로 처리가 가능하다.
1. 단방향 맵핑(외래키를 user에만 두는 경우).
이렇게 생성하는경우 user테이블에만 profile_id가 생성된다
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "profile_id") // ✅ Profile 테이블의 ID를 FK로 저장
private Profile profile;
}
@Entity
public class Profile {
@Id @GeneratedValue
private Long id;
private String bio;
private String imageUrl;
}
2. 양방향 맵핑(둘다 서로의 id 를 가지게 된다)
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "profile_id") // ✅ Profile의 ID를 FK로 저장 (주인)
private Profile profile;
}
@Entity
public class Profile {
@Id @GeneratedValue
private Long id;
private String bio;
private String imageUrl;
@OneToOne(mappedBy = "profile") // ✅ "주인이 아님"을 표시 (User가 주인), profile은 참조만함
private User user;
}
- @OneToOne 관계에서 양방향 매핑을 하면 서로 참조 가능
- FK를 가진 엔티티(User)가 관계의 “주인(Owner)“이 됨
- “주인이 아닌 엔티티(Profile)“에서는 mappedBy를 사용해야 함
대량의 데이터가 있을때 유의사항
기본적으로 @OneToOne은 Left Join으로 데이터를 가져오는데 대량의 데이터에서의 조인은 성능 저하를 유발 할 수 있음.
따라서. @OneToOne(fetch = FetchType.LAZY)로 설정할것 (oneToOnedms 기본이 EAGER) 그리고 그렇다고 하더라도 n+1 문제가 발생할 수 있음 그거는 @EntityGraph 등을 활용하면서 해결할 수 있다.
@OneToMany 사용하기
기준이 되는 테이블이 있고 그것에 맵핑되는 테이블이 있을때 1:N관계로 데이터가 조회 될때
예를 들어서 유저정보가 있는 테이블이있고 해당 유저가 발행한 post 정보가 있는 테이블이 있을때 이둘의 관계를 표현 하는 방법
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Post> posts = new ArrayList<>();
}
- mappedBy = "user" → Post 테이블에 user 필드가 FK를 가짐 (연관 관계의 주인은 Post)
- cascade = CascadeType.ALL
- orphanRemoval = true → User에서 Post를 제거하면 Post도 삭제된다. 다만 Cascade와는 별개로 봐야한다. 부모 엔티티와의 연관관계가 끊어진 고아 객체를 자동으로 삭제하는 역할. (부모는 그대로 두고 자식만 삭제하는 경우가 필요할때 true)
@Entity
public class Post {
@Id @GeneratedValue
private Long id;
private String title;
private String content;
@ManyToOne
@JoinColumn(name = "user_id") // ✅ Post 테이블이 user_id FK를 가짐
private User user;
}
1+N 문제
기본적으로 jpa에서 데이터를 조회할때는 lazy loading으로 데이터를 조회하는것이 권장된다.
그렇기 때문에 oneToMany로 조회를 할때도 부모 엔티티를 조회할때 연관된 자식 엔티티를 한번에 가져오지 않고 각각 개별적인 쿼리로 가져오게 된다.
만약 각 자식 엔티티에 대한 정보도 모두 필요로 하는 상황이라면 첫번째로 부모 엔티티가 조회되고.
그것과 연관된 N개의 자식 엔티티가 조회가 되야하기 때문에 총 1+N의 쿼리가 수행되어야한다.
(대규모 데이터를 처리할때 메모리 과부화 이슈가 발생할 수 있다) ex) 유저. 만명 각 유저 100개의 개시물
참고로 lazy안하고 그냥 eager로 설정하면 되는거 아니야? 이렇게 단순하게 생각하면 안된다
-> 이렇게하면 join으로 조회되는게 아니라 lazy처럼 하나 조회하고 그 뒤에 각각 쿼리문이 날라가서 1+n을 그냥 조회시점에 수행할뿐
쿼리가 많아질수록 DB서버와 애플리케이션 서버간의 통신 비용이 누적되어 성능이 저하되고
대규모 트래픽 상황에서 병목 현상이 발생할 가능성이 높기때문에 위의 문제는 해결이 필요하다!
해결방법1. @EntityGraph사용하기
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(attributePaths = "posts") // user 랑 post가 oneToMany관계로 설정한것
List<User> findAll(); // User를 조회할 때 Post도 한 번에 가져옴
}
@EntityGraph는 JPA에서 제공하는 기능으로 연관된 엔티티를 한번에 fetch join 할 수 있도록 설정하는 어노테이션이다
쿼리를 커스터마이징 하지 않더라도 간단하게 fetch join효과를 얻을 수 있어서 좋다. (기본이 left join임)
fetch join이란
JPA에서 제공하는 엔티티와 연관된 엔티티를 함께 로딩하기위한 특별한 JOIN방법이다.
일반적인 join 과의 차이점은. fetch join은 연관된 엔티티를 즉시로딩하여 영속성 컨텍스트에 함께 저장한다는 점이다.
SELECT u.*, p.*
FROM user u
LEFT OUTER JOIN post p ON u.id = p.user_id;
위와 같이 한번에 left join으로 조회 된다고 보면된다.
특정 메서드에 즉시 로딩이 필요할때 사용하며(기존 entity 설정은 lazy로 사용가능) 다중 fetch join은 불가능하다.
예를들어서 user 와 post 그리고 comment 까지 동시에 fetch join하는건 불가능 (이럴떄는 querydsl이나 jpql 사용하자)
해결방법2. Fetch Join 사용하기.
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u JOIN FETCH u.posts")
List<User> findAllWithPosts();
}
Jpql에서 join fetch를 사용하면 연관된 엔티티를 한번의 쿼리로 가져올 수 있다.
결국 해결방법 1이 해결방법 2의 jpql을 만들어주는것
해결방법3. Querydsl 사용하기.
public List<User> findAllWithPosts() {
return queryFactory
.selectFrom(user)
.leftJoin(user.posts, post).fetchJoin()
.fetch();
}
queryDsl을 사용하면 동적 쿼리를 만들면서도 Join fetch를 활용할 수 있다.
해결방법4. @BatchSize 사용하기
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@BatchSize(size = 10) // 한 번에 10개의 Post를 로드
private List<Post> posts = new ArrayList<>();
}
완벽하게 n+1을 해결하는 내용은 아니지만 1:N 관계에서 N의 수치가 크지 않은경우? 에 간단하게 해결하기 좋다고 생각한다.
기본적으로 지연로딩을 사용하지만 batchsize를 지정하면 일정 크기만큼은 일괄적으로 데이터를 로딩하도록 동작하기 때문이다.
실질적으로 join처럼 한번에 가져오는건 아니고 where 조건의 in절로 해당하는 batch size만큼은 한번에 데이터를 가져오도록 쿼리가 생성된다.
-- User 20명 조회
select user0_.id as id1_0_, user0_.name as name2_0_
from User user0_
-- Post 조회 (BatchSize 적용, 10개씩 묶어서 가져옴)
select post0_.id as id1_1_, post0_.title as title2_1_, post0_.user_id as user_id3_1_
from Post post0_
where post0_.user_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) -- 첫 번째 배치
select post0_.id as id1_1_, post0_.title as title2_1_, post0_.user_id as user_id3_1_
from Post post0_
where post0_.user_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) -- 두 번째 배치
JPA 설정
yml 파일 기본 설정
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver # ✅ MySQL 드라이버 클래스
url: jdbc:mysql://test-aa-northeast-2.rds.amazonaws.com:3306/test?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: root # ✅ 데이터베이스 접속 아이디
password: 1111 # ✅ 데이터베이스 접속 비밀번호
spring:
jpa:
hibernate:
ddl-auto: none # ✅ 스키마 자동 생성 방식
properties:
hibernate:
format_sql: true # ✅ SQL 예쁘게 출력
show_sql: true # ✅ 실행되는 SQL 콘솔에 출력 (운영환경에서는 사용 x)
dialect: org.hibernate.dialect.MySQL8Dialect # ✅ Hibernate가 사용할 DB 방언 설정
open-in-view: false # ✅ OSIV (Open Session In View) 비활성화
ddl-auto의 경우는 hibernate가 테이블을 어떻게 생성/관리할건지 설정하는 부분으로 기본값은 none(자동변경 없음) 이다.
일반적으로 운영환경에서 테이블이 갑자기 변경되면 안되기 때문에 none으로 설정해서 사용한다.
create : 실행시 기존 테이블 삭제 후 새로 생성.
update : 기존 테이블을 유지하면서 변경 사항만 반영.
validate : 스키마 변경 없이 검증만 수행.
open-in-view는 트랜잭션이 종료된 후에도 영속성 컨텍스트를 유지하는 기능이다.
기본값은 true로 컨트롤러 까지 영속성 컨텍스트를 유지한다. false로 하면 트랜잭션 범위 내에서만 사용가능하다
true로 사용하면 개발 편의성이 높지만 트랜잭션이 끝난 후에도 DB커넥션이 유지되기 때문에 성능 저하의 위험성이 있다.
Hibernate Dialect
hibernate가 사용하는 데이터베이스에 맞는 SQL 문법을 올바르게 생성하도록 도와주는 설정값
데이터베이스마다 SQL뭄법과 기능이 조금씩 다르기 떄문에.
(설정안해도 기본적으로 jdbc url을 기반으로 적절한값 선택 시도하지만 명확하게 설정하는게 좋다)
spring:
datasource:
hikari:
maximum-pool-size: 10 # ✅ 최대 커넥션 개수
minimum-idle: 2 # ✅ 최소한 유지할 커넥션 개수
idle-timeout: 30000 # ✅ 사용되지 않는 커넥션을 풀에서 제거할 시간 (30초)
connection-timeout: 5000 # ✅ 커넥션 요청 대기 시간 (5초)
기타 사용 팁
Enum 활용하기
데이터베이스에 데이터를 저장할때 정해진 enum에 해당 하는 값들만 넣을 수 있도록 하는 기능이 있다.
@Enumerated 를 사용하면되며 enum의 string 형태로 데이터를 저장할 수 도 있고(EnumType.STRING) enum데이터에 대응되는 순서에 따라서 데이터를 저장할 수도 있다(EnumType.ORDINAL).
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Movie {
@Id
@GeneratedValue
private int id;
@Column
private String title;
@Enumerated(EnumType.STRING)
private Genre genre;
}