[SpringDB2] 데이터 접근 기술 - JPA
JPA에 대한 자세한 내용은 JPA 카테고리의 내용을 확인.
JPA 적용1 - 개발
JPA에서 가장 중요한 부분은 객체와 테이블을 매핑하는 것이다.
Item - 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가 인식할 수 있다.- 이렇게
@Entity
가 붙은 객체를 JPA에서는 엔티티라 한다.
- 이렇게
@Id
: 테이블의 PK와 해당 필드를 매핑한다.@GeneratedValue(strategy = GenerationType.IDENTITY)
: PK 생성 값을 데이터베이스에서 생성하는IDENTITY
방식을 사용한다.- Ex) MySQL auto increment
@Column
: 객체의 필드를 테이블의 컬럼과 매핑한다.- 객체는
itemName
이지만 테이블의 컬럼은item_name
이므로 이렇게 매핑했다. length = 10
: JPA의 매핑 정보로 DDL(create table
)도 생성할 수 있는데, 그때 컬럼의 길이 값으로 활용된다. (varchar 10
)@Column
을 생략할 경우 필드의 이름을 테이블 컬럼 이름으로 사용한다. 참고로 스프링 부트와 통합해서 사용하면 필드 이름을 테이블 컬럼 명으로 변경할 때 객체 필드의 카멜 케이스를 테이블 컬럼의 언터스코어로 자동으로 변환해준다.itemName
->item_name
- 따라서, 위
@Column(name = "item_name", length = 10)
코드는 생략해도 된다.
- 객체는
- JPA는
public
또는protected
의 기본 생성자가 필수이다.
JPAItemRepositoryV1
@Slf4j
@Repository
@Transactional
public class JpaItemRepositoryV1 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";
Integer maxPrice = cond.getMaxPrice();
String itemName = cond.getItemName();
if (StringUtils.hasText(itemName) || maxPrice != null) {
jpql += " where";
}
boolean andFlag = false;
List<Object> param = new ArrayList<>();
if (StringUtils.hasText(itemName)) {
jpql += " i.itemName like concat('%',:itemName,'%')";
param.add(itemName);
andFlag = true;
}
if (maxPrice != null) {
if (andFlag) {
jpql += " and";
}
jpql += " i.price <= :maxPrice";
param.add(maxPrice);
}
log.info("jpql={}", jpql);
TypedQuery<Item> query = em.createQuery(jpql, Item.class);
if (StringUtils.hasText(itemName)) {
query.setParameter("itemName", itemName);
}
if (maxPrice != null) {
query.setParameter("maxPrice", maxPrice);
}
return query.getResultList();
}
}
- 생성자를 보면 스프링을 통해
EntityManager
라는 것을 주입받는다.- JPA의 모든 동작은 엔티티 매니저를 통해서 이루어진다.
- 엔티티 매니저는 내부에 데이터소스를 가지고 있고, 데이터베이스에 접근할 수 있다.
@Transactional
: JPA의 모든 데이터 변경은 트랜잭션 안에서 이루어져야 한다.- 일반적으로는 비즈니스 로직을 시작하는 서비스 계층에 트랜잭션을 걸어주는 것이 맞다.
Repository 분석
save() - 저장
public Item save(Item item) {
em.persist(item);
return item;
}
em.persist(item)
: JPA에서 객체를 테이블에 저장할 때는 엔티티 매니저가 제공하는persist()
메서드를 사용하면 된다.
update() - 수정
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());
}
- JPA는 트랜잭션이 커밋되는 시점에 변경된 엔티티 객체가 있는지 확인하고, 특정 엔티티 객체가 변경된 경우에는 UPDATE SQL을 실행한다.
findAll() - 목록 조회
public List<Item> findAll(ItemSearchCond cond) {
String jpql = "select i from Item i";
//동적 쿼리 생략
TypedQuery<Item> query = em.createQuery(jpql, Item.class);
return query.getResultList();
}
JPQL
JPA는 JPQL(Java Persistence Query Language)이라는 객체지향 쿼리 언어를 제공하며, 주로 여러 데이터를 복잡한 조건으로 조회할 때 사용한다.
SQL이 테이블을 대상으로 한다면, JPQL은 엔티티 객체를 대상으로 SQL을 실행한다 생각하면 된다.
파라미터
- JPQL에서 파라미터는 다음과 같이 입력한다.
where price <= :maxPrice
- 파라미터 바인딩
query.setParameter("maxPrice", maxPrice)
동적 쿼리 문제
JPA를 사용해도 동적 쿼리 문제가 남아있다. 동적 쿼리는 Querydsl이라는 기술을 활용하면 매우 깔끔하게 사용할 수 있다. 실무에서는 동적 쿼리 문제 때문에, JPA를 사용할 때 Querydsl도 함께 선택하게 된다.
JPA 적용2 - 예외 변환
JPA의 경우 예외가 발생하면 JPA 예외가 발생하게 된다.
@Repository
@Transactional
public class JpaItemRepositoryV1 implements ItemRepository {
private final EntityManager em;
@Override
public Item save(Item item) {
em.persist(item);
return item;
}
}
EntityManager
는 순수한 JPA 기술이고, 스프링과는 관계가 없다. 따라서, 엔티티 매니저는 예외가 발생하면 JPA 관련 예외를 발생시킨다.- JPA는
PersistenceException
과 그 하위 예외를 발생시킨다.- 추가로 JPA는
IllegalStateException
,IllegalArgumentException
을 발생시킬 수 있다.
- 추가로 JPA는
- 그렇다면 JPA 예외를 스프링 예외 추상화(
DataAccessException
)로 어떻게 변환할 수 있을까?
예외 변환 전
@Repository의 기능
@Repository
가 붙은 클래스는 컴포넌트 스캔의 대상이 된다.@Repository
가 붙은 클래스는 예외 변환 AOP의 적용 대상이 된다.- 스프링과 JPA를 함께 사용하는 경우 스프링은 JPA 예외 변환기(
PersistenceExceptionTranslator
)를 등록한다. - 예외 변환 AOP 프록시는 JPA 관련 예외가 발생하면 JPA 예외 변환기를 통해 발생한 예외를 스프링 데이터 접근 예외로 변환한다.
- 스프링과 JPA를 함께 사용하는 경우 스프링은 JPA 예외 변환기(
예외 변환 후
결과적으로 리포지토리에 @Repository
애노테이션만 있으면 스프링이 예외 변환을 처리하는 AOP를 만들어준다.
댓글남기기