JPA는 ORM 데이터 접근 기술을 제공
JPA 설정
spring-boot-starter-data-jpa
라이브러리를 사용하면 JPA와 스프링 데이터 JPA를 스프링 부트와 통합하고 설정도 간단히 할 수 있다
application.properties
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
다음과 같이 로드를 설정해야 logger
를 통해 sql이 출력됨
Item
@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가 사용하는 객체라는 뜻이다. @Id
: 테이블의 PK와 해당 필드를 매핑@GeneratedValue(strategy=GenerationType.Indetity)
: PK 생성값을 데이터 베이스에서 생성하는 identity
방식 사용 (auto_increment
)@Column
: 객체의 필드를 테이블의 칼럼과 매핑name
: 객체는 itemName
이지만 테이블의 컬럼은item_name
이므로 이렇게 매핑length=10
: varchar(10)
@Column
을 생략하면 필드의 이름을 테이블 컬럼 이름으로 사용JpaRepositoryV1
@Slf4j
@Repository
@Transactional
public class JpaItemRepository implements ItemRepository {
private final EntityManager em;
public JpaItemRepository(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 = "selectxxx 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();
}
}
private final EntityManager em:
생성자를 보면 스프링을 통해 엔터티 매니저라는 것을 주입 받은 것을 확인할 수 있다. JPA의 모든 동작은 엔터티 매니저를 통해서 이루어진다. 엔타티 매니저는 내부에 데이터소스를 가지고 있고 데이터베이스에 접근할 수 있다
`@Transactional`: JPA의 모든 데이터 변경은 트랜잭션 안에서 이뤄져야 함
JpaConfig
Configuration
public class JpaConfig {
private final EntityManager em;
public JpaConfig(EntityManager em) {
this.em = em;
}
@Bean
public ItemService itemService() {
return new ItemServiceV1(itemRepository());
}
@Bean
public ItemRepository itemRepository() {
return new JpaItemRepository(em);
}
}
public Item save(Item item)
public Item save(Item item) {
em.persist(item);
return item;
}
persist()
메서드를 사용하면 됨 실행된 쿼리insert into item (id, item_name, price, quantity) values (null, ?, ?, ?)
insert into item (id, item_name, price, quantity) values (default, ?, ?, ?)
insert into item (item_name, price, quantity) values (?, ?, ?)
Item
entity를 만들때 PK 값 전략을 IDENTITY
로 사용했기 때문에 JPA가 이런 쿼리 생성Item
객체의 id
필드에 데이터베이스가 생성한 PK값이 들어가게 된다public void update(Long itemId, ItemUpdateDto updateParam)
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());
}
update item set item_name=?, price=?, quantity=? where id=?
em.update()
같은 값을 전혀 호출하지 않았다. JPA
는 트랜잭션이 커밋되는 시점에 변경된 엔티티 객체가 있는지 확인한다. 특정 엔터티 객체가 변경된 경우에는 UPDATE SQL을 실행public Optional findById(Long id)
public Optional<Item> findById(Long id) {
Item item = em.find(Item.class, id);
return Optional.ofNullable(item);
}
실행된 쿼리 값
select
item0_.id as id1_0_0_,
item0_.item_name as item_nam2_0_0_,
item0_.price as price3_0_0_,
item0_.quantity as quantity4_0_0_
from item item0_
where item0_.id=?
EntityManager
는 순수한 JPA 기술이고, 스프링과는 관계가 없다. 따라서 엔티티 매니저는 예외가 발생하면 JPA 관련 예외를 발생시킨다.
JPA는 PersistenceException
과 그 하위 예외를 발생시킨다.
추가로 JPA는 IllegalStateException
, IllegalArgumentException
을 발생시킬 수 있다. 그렇다면 JPA 예외를 스프링 예외 추상화( DataAccessException
)로 어떻게 변환할 수 있을까?
비밀은 바로 @Repository
에 있다.
출처:https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2