JPA 소개
- SQL, 데이터 중심의 설계에서 객체 중심의 설계로 전환할 수 있다.
- 기존의 반복 코드, 기본적인 SQL 문도 JPA 가 직접 만들어 실행한다.
- PK 기반이 아닌 SQL 문은 직접 작성해야 한다.
- 동적 SQL 을 해결하기 어렵다.
- EntityManger 을 주입받아 동작한다.
- JPA 를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.
> 참고 <
- 스프링 부트가 JPA 설정에 따라 EntityMangerFactory 를 생성하고, 이를 통해 EntityManager 를 스프링에 주입할 수 있도록 해준다.
초기 설정
build.gradle 파일에 다음 설정을 추가해 준다.
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
(JPA 라이브러리에 JDBC 라이브러리도 함께 포함되어 있기 때문에, JDBC 의존관계를 제거해도 된다.)
`main` 과 `test` 의 application.properties 에 다음 설정을 추가해 준다.
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
`org.hibernate.SQL=DEBUG` : 하이버네이트가 생성하고 실행하는 SQL 을 `logger` 를 통해 확인할 수 있다.
`logging.level.org.hibernate.orm.jdbc.bind=TRACE` : SQL 에 바인딩되는 파라미터를 확인할 수 있다.
(`spring.jpa.show-sql=true` 도 실행하는 SQL 을 확인할 수 있지만, `System.out` 을 통해 출력되기 때문에 권장하지 않는다.)
JPA 사용 예시
ORM 매핑
@Data
@Entity
public class Item {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "item_name", length = 10)
private String itemName;
private Integer price;
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
- `@Entity`
: JPA 가 사용하는 객체라는 뜻으로 엔티티라 불린다.
: 이 애노테이션이 있어야 JPA 가 인식할 수 있다.
- `@Id`
: 테이블의 PK 와 해당 필드를 매핑한다.
- `@GeneratedValue(strategy = GenerationType.IDENTITY)`
: PK 생성 값을 데이터베이스에서 생성하는 IDENTITY 방식을 사용한다.
- `@Column`
: 객체의 필드를 테이블의 칼럼과 매핑한다.
: length 속성은 JPA 매핑 정보로 DDL 을 생성할 때, 칼럼의 길이 값으로 활용한다.
: 생략한 경우, 필드의 이름을 자동으로 snake_case 표기법으로 변환하여 테이블의 칼럼 이름으로 사용한다.
- `@Table`
: 테이블 명을 지정할 수 있고, 객체명이랑 같으면 생략할 수 있다.
- `public` or `protected` 기본 생성자가 필수이므로 생략하면 안 된다.
리포지토리
@Repository
@Transactional
public class JpaItemRepository implements ItemRepository {
private final EntityManager em;
public JpaItemRepositoryV1(EntityManager em) {
this.em = em;
}
@Override
public Item save(Item item) {
em.persist(item);
return item;
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
Item findItem = em.find(Item.class, itemId);
findItem.setItemName(updateParam.getItemName());
findItem.setPrice(updateParam.getPrice());
findItem.setQuantity(updateParam.getQuantity());
}
@Override
public Optional<Item> findById(Long id) {
Item item = em.find(Item.class, id);
return Optional.ofNullable(item);
}
@Override
public List<Item> findAll(ItemSearchCond cond) {
String jpql = "select i from Item i";
// 동적 쿼리 생략
List<Item> result = em.createQuery(jpql, Item.class).getResultList();
return result;
}
스프링을 통해 EntityManager 를 주입받아 사용한다.
이때, JPA 의 모든 동작은 EntityManager 를 통해 이뤄지며, 내부에 DataSource 를 가지고 데이터베이스에 접근할 수 있다.
JPA 의 모든 데이터의 변경은 트랜잭션 안에서 이뤄져야 하기 때문에,
서비스 계층 또는 리포지토리에 `@Transactional` 트랜잭션을 걸어준다.
Insert
- `em.persist()` : EntityManager 가 제공하는 JPA 에서 객체를 테이블에 저장할 때 사용하는 메서드이다.
- PK 키 생성 전략을 IDENTITY 로 사용했기 때문에 JPA 가 실행한 Insert SQL 문을 보면 id 값이 빠져 있는 것을 알 수 있다.
쿼리 실행 이후 객체의 id 필드에 데이터베이스가 생성한 PK 값을 넣어준다.
Update
- 메서드를 호출하지 않아도 JPA 는 트랜잭션이 커밋되는 시점에 변경된 엔티티 객체를 확인하여 자동으로 SQL 을 실행한다.
따라서 커밋되는 시점에 Update SQL 문을 실행하고, 롤백되면 Update SQL 문을 실행하지 않는다.
Select
- 엔티티 객체를 PK 기준으로 조회할 때는 `find(조회 타입, PK 값)` 를 사용한다.
그러면 JPA 가 Select SQL 문을 실행하고 결과를 객체로 변환해 준다.
- PK 기준으로 조회하지 않고, 여러 복잡한 조건으로 조회하려면 JPQL 을 사용하면 된다.
(동적 쿼리 문제는 Querydsl 도 함께 사용하여 해결할 수 있다.)
JPQL
- Java Persistence Query Language 로 JPA 가 제공하는 객체 쿼리 언어이다.
- 여러 데이터를 복잡한 조건으로 조회할 때 사용한다.
- 테이블 대상이 아닌, 엔티티 객체를 대상으로 SQL 문을 실행한다.
- from 다음 테이블명이 아닌 엔티티 객체 이름을 사용하며, 엔티티 객체와 속성 이름은 대소문자를 구분한다.
- JPQL 을 실행하면 엔티티 객체와 매핑 정보를 활용하여 SQL 을 만들게 된다.
// JPQL
select i from Item i
where i.itemName like concat('%',:itemName,'%')
and i.price <= :maxPrice
// 실행된 SQL
select
item0_.id as id1_0_,
item0_.item_name as item_nam2_0_,
item0_.price as price3_0_,
item0_.quantity as quantity4_0_
from item item0_
where (item0_.item_name like ('%'||?||'%'))
and item0_.price<=?
`:` 를 사용하여 파라미터를 입력하고 `setParameter("itemName", itemName)` 을 통해 파라미터 바인딩한다.
EntityManager 예외 추상화
순수한 JPA 기술로 예외가 발생하면 JPA 표준 예외를 발생시킨다.
따라서 스프링이 @Repository가 붙은 클래스에 대해 JPA 예외를 스프링 예외 추상화인 `DataAccessException` 으로 변환해 준다.
1. 스프링과 JPA 를 함께 사용하는 경우 스프링이 JPA 예외 변환기인 `PersistenceExceptionTranslator` 인터페이스를 자동으로 등록한다.
2. 또한 @Repository 가 붙은 클래스에 AOP 프록시를 생성하고, 예외 변환 어드바이저를 등록하는 `PersistenceExceptionTranslationPostProcesser` 빈 후처리기를 자동으로 등록한다.
(따라서 단순히 @Repository 만 붙여도 예외 변환이 적용된다.)
3. EntityManager 사용 중 JPA 예외가 발생하면, AOP 프록시가 해당 예외를 감지해 스프링 예외 추상화로 변환한다.
출처 | 스프링 DB 2(김영한) - 인프런
'💠프로그래밍 언어 > Spring' 카테고리의 다른 글
[Spring] Querydsl 을 통해 동적 쿼리 해결하기 (1) | 2025.05.02 |
---|---|
[Spring] 스프링 데이터 JPA 사용하기 (0) | 2025.05.02 |
[Spring] MyBatis 사용법 익히기 ! (1) | 2025.05.01 |
[Spring] 데이터베이스 연동하여 테스트하기 ! (별도 DB, 임베디드 모드) (0) | 2025.05.01 |
[Spring] JdbcTemplate 상세 사용법 정리 (1) | 2025.04.30 |