[Spring Database] JPA 적용
JPA의 핵심은 객체와 관계형 데이터베이스간의 매핑이다.
애너테이션 기반으로 객체와 데이터베이스를 매핑해보자.
@Data
@Entity
@Table(name = "item")
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 : 테이블의 Primary Key와 해당 필드를 매핑한다.
@Column : 객체의 필드를 테이블의 컬럼과 매핑한다. 이름과 길이를 지정해 줄 수 있다. (길이는 테이블을 만들 때 사용)
JPA에서는 public 또는 protected 접근제한자를 가진 기본 생성자를 꼭 만들어야 한다.
이 생성자를 기반으로 프록시 기술을 사용할 수 있다.
@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;
if (StringUtils.hasText(itemName)) {
jpql += " i.itemName like concat('%',:itemName,'%')";
andFlag = true;
}
if (maxPrice != null) {
if (andFlag) {
jpql += " and";
}
jpql += " i.price <= :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();
}
}
JPA의 모든 작업은 EntityManager를 통해 이루어지고, 트랜잭션으로 묶여서 처리된다.
(서비스 계층에 @Transactional 애너테이션 사용)
EntityManager는 내부의 데이터소스를 통해 데이터베이스에 접근하고, 스프링 부트는 EntityManager를 포함한 JPA와 관련된 모든 설정을 자동화 해 준다.
save
EntityManager에서 persist 메서드를 사용해서 저장한다.
JPA는 insert into item (id, item_name, price, quantity) values (null, ?, ?, ?) SQL을 만들어서 실행한다.
PK 전략을 IDENTITY로 설정했기 때문에 id 값으로 null을 사용했다.
(쿼리 실행 이후 데이터베이스에서 생성한 id값이 들어간다)
update
메서드를 딱히 호출하지 않는다.
JPA는 트랜잭션이 커밋되는 시점에 변경된 엔티티 객체가 있으면 update sql을 실행한다.
어떻게 변경된 엔티티 객체를 찾는지 이해하려면 영속성 컨텍스트에 대해 공부해야 하는데..
일단은 변경되면 update sql을 작성해 준다 정도로 이해하자.
find
PK기준으로 조회 할 때는 find() 메서드를 사용한다.
findAll
여러 데이터를 복잡한 기준으로 조회하려면 JPQL(Java Persistence Query Language)을 사용한다.
SQL이 테이블을 대상으로 조회한다면 JPQL은 엔티티를 대상으로 SQL을 실행한다고 생각하자.
SQL과 문법이 거의 비슷해서 SQL을 잘 알고 있으면 쉽게 적응할 수 있다.
동적 쿼리 문제는 QueryDSL 기술을 사용해서 해결한다.
EntityManager는 스프링과 관계 없는 JPA 기술이다. 따라서 예외 발생 시 JPA 관련 예외를 발생시킨다.
EntityManager에는 JPA 관련 오류인 PersistenceException / IllegalStateException / IllegalArgumentException을 뱉는다.
발생한 오류가 서비스 계층까지 넘어가게 되면 서비스 계층이 JPA 기술에 종속적이게 되는데, 이를 방지하려면 JPA 예외를 스프링이 제공하는 예외인 DataAccessException으로 변환해야 한다.
@Repository 애너테이션이 붙은 클래스는 컴포넌트 스캔의 대상이 되기도 하고, 예외 변환 AOP의 적용 대상이 되기도 한다.
스프링과 JPA를 함께 사용할 경우 스프링은 JPA 예외 변환기(PersistenceExceptionTranslator)를 등록하고, 예외 변환 AOP 프록시는 JPA 관련 예외가 발생하면 JPA 예외 변환기를 통해 해당 예외를 스프링 데이터 접근 예외로 변환한다.
EntityManager가 JPA 기술이기 때문에 @Repository가 위의 작업을 처리해 줘야 한다.
'Spring > Spring Database' 카테고리의 다른 글
[Spring Database] QueryDSL (1) | 2022.09.11 |
---|---|
[Spring Database] Spring Data JPA (0) | 2022.09.11 |
[Spring Database] JPA (1) | 2022.09.09 |
[Spring Database] MyBatis (0) | 2022.09.09 |
[Spring Database] 데이터베이스 테스트 (2) | 2022.09.07 |
댓글
이 글 공유하기
다른 글
-
[Spring Database] QueryDSL
[Spring Database] QueryDSL
2022.09.11 -
[Spring Database] Spring Data JPA
[Spring Database] Spring Data JPA
2022.09.11 -
[Spring Database] JPA
[Spring Database] JPA
2022.09.09 -
[Spring Database] MyBatis
[Spring Database] MyBatis
2022.09.09