You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@Entity : JPA 가 사용하는 객체라는 뜻. 이게 있어야 JPA 가 인식할 수 있음. @Id : 테이블의 PK와 해당 필드를 매핑한다. @GeneratedValue(strategy = GenerationType.IDENTITY) : PK 생성 값을 데이터베이스에서 생성하는 IDENTITY 방식을 사용한다. 예) MySQL auto increment @Column : 객체의 필드를 테이블의 컬럼과 매핑한다.
- 객체는 itemName 이지만 컬럼명은 item_name
- length = 10 :JPA의매핑정보로DDL(create table )도생성할수있는데,그때컬럼의길이값으 로활용된다.(varchar 10 )
- @Column 을 생략할 경우 필드의 이름을 테이블 컬럼 이름으로 사용한다. 참고로 지금처럼 스프링 부트와 통합해서 사용하면 필드 이름을 테이블 컬럼 명으로 변경할 때 객체 필드의 카멜 케이스를 테이블 컬럼의 언 더스코어로 자동으로 변환해준다. (사실 item_name 도 지정하지 않아도 괜찮음)
JPA는 public 또는 protected 의 기본 생성자가 필수이다. 기본 생성자를 꼭 넣어주자.
publicItem() {}
프록시 기술을 쓰기 위해서 필요하다?
이걸 활용해서 CRUD 하는 리포지토리 만들기.
JPA 의 모든 변경은 트렌젝션 안에서 이루어짐.
EntityManager 가 있어야 저장 조회, 가능
@Slf4j@Repository@TransactionalpublicclassJpaItemRepositoryimplementsItemRepository {
privatefinalEntityManagerem;
publicJpaItemRepository(EntityManagerem) {
this.em = em;
}
@OverridepublicItemsave(Itemitem) {
em.persist(item); // item 도메인에 있는 정보를 가지고 insert 쿼리를 만듦.returnitem;
}
@Overridepublicvoidupdate(LongitemId, ItemUpdateDtoupdateParam) {
ItemfindItem = em.find(Item.class, itemId);
findItem.setItemName(updateParam.getItemName());
findItem.setPrice(updateParam.getPrice());
findItem.setQuantity(updateParam.getQuantity());
// 업데이트 저장을 진행해야 할 것 같은데 하지 않아도 됌,// 엔티티가 변경된걸 캡쳐하고 있다가 트렌젝션 끝날 때 sql 만들어서 커밋을 알아서 날림
}
@OverridepublicOptional<Item> findById(Longid) {
Itemitem = em.find(Item.class, id);
returnOptional.ofNullable(item); // 널일수도 있으니
}
@OverridepublicList<Item> findAll(ItemSearchCondcond) {
Stringjpql = "select i from Item i"; // item 이 아니라 Item 임IntegermaxPrice = cond.getMaxPrice();
StringitemName = cond.getItemName();
if (StringUtils.hasText(itemName) || maxPrice != null) {
jpql += " where";
}
booleanandFlag = false;
List<Object> param = newArrayList<>();
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);
}
returnquery.getResultList();
}
}
private final EntityManager em :생성자를보면스프링을통해엔티티매니저(EntityManager ) 라는 것을 주입받은 것을 확인할 수 있다. JPA의 모든 동작은 엔티티 매니저를 통해서 이루어진다. 엔티티 매니저 는 내부에 데이터소스를 가지고 있고, 데이터베이스에 접근할 수 있다.
@Transactional : JPA의 모든 데이터 변경(등록, 수정, 삭제)은 트랜잭션 안에서 이루어져야 한다. 조회는 트 랜잭션이 없어도 가능하다. 변경의 경우 일반적으로 서비스 계층에서 트랜잭션을 시작하기 때문에 문제가 없다.
하지만 이번 예제에서는 복잡한 비즈니스 로직이 없어서 서비스 계층에서 트랜잭션을 걸지 않았다. JPA에서는 데 이터 변경시 트랜잭션이 필수다. 따라서 리포지토리에 트랜잭션을 걸어주었다. 다시한번 강조하지만 일반적으로 는 비즈니스 로직을 시작하는 서비스 계층에 트랜잭션을 걸어주는 것이 맞다.
참고: JPA를 설정하려면 EntityManagerFactory , JPA 트랜잭션 매니저( JpaTransactionManager ), 데이터 소스 등등 다양한 설정을 해야 한다. 스프링 부트는 이 과정을 모두 자동화 해준다. main() 메서드 부터 시작해서 JPA를 처음부터 어떻게 설정하는지는 JPA 기본편을 참고하자. 그리고 스프링 부트의 자동 설정은 JpaBaseConfiguration 를 참고하자.
테스트를 돌려 로그를 확인해보면, JPQL 을 읽어 SQL 생성하는 로그 확인 가능.
업데이트는 쿼리가 보이지 않는데, save 를 하면 캐시에 저장하고, update하면 그 캐시에 있는걸 업데이트하고, 찾을때도 그 캐시에 있는걸 찾아 쿼리가 보이지 않음. 커밋을 붙이면 업데이트 쿼리가 보임.
em.persist(item) : JPA에서 객체를 테이블에 저장할 때는 엔티티 매니저가 제공하는 persist() 메서드 를 사용하면 된다.
JPA가 만들어서 실행한 SQL
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 (?, ?, ?)
JPA가 만들어서 실행한 SQL을 보면 id 에 값이 빠져있는 것을 확인할 수 있다. PK 키 생성 전략을 IDENTITY 로 사용했기 때문에 JPA가 이런 쿼리를 만들어서 실행한 것이다. 물론 쿼리 실행 이후에 Item 객체의 id 필드 에 데이터베이스가 생성한 PK값이 들어가게 된다. (JPA가 INSERT SQL 실행 이후에 생성된 ID 결과를 받아서 객체에 셋팅 해줌 )
update item set item_name=?, price=?, quantity=? where id=?
em.update() 같은 메서드를 전혀 호출하지 않았다. 그런데 어떻게 UPDATE SQL이 실행되는 것일까?
JPA는 트랜잭션이 커밋되는 시점에, 변경된 엔티티 객체가 있는지 확인한다. 특정 엔티티 객체가 변경된 경우에 는 UPDATE SQL을 실행한다. (어떤게 변경됬는지 아는 방법: 처음 객체를 스냅샷처럼 찍어서 가지고있어 이를 체크해 확인함)
JPA가 어떻게 변경된 엔티티 객체를 찾는지 명확하게 이해하려면 영속성 컨텍스트라는 JPA 내부 원리를 이해해 야 한다. 이 부분은 JPA 기본편에서 자세히 다룬다. 지금은 트랜잭션 커밋 시점에 JPA가 변경된 엔티티 객체를 찾아서 UPDATE SQL을 수행한다고 이해하면 된다.
테스트의 경우 마지막에 트랜잭션이 롤백되기 때문에 JPA는 UPDATE SQL을 실행하지 않는다. 테스트에서 UPDATE SQL을 확인하려면 @Commit 을 붙이면 확인할 수 있다.
JPA(하이버네이트)가 만들어서 실행한 SQL은 별칭이 조금 복잡하다. 조인이 발생하거나 복잡한 조건에서도 문제 없도 록 기계적으로 만들다 보니 이런 결과가 나온 듯 하다.
JPA에서 단순히 PK를 기준으로 조회하는 것이 아닌, 여러 데이터를 복잡한 조건으로 데이터를 조회하려면 JPQL 사용해야함.
JPQL
JPA는 JPQL(Java Persistence Query Language)이라는 객체지향 쿼리 언어를 제공한다.
주로 여러 데이터를 복잡한 조건으로 조회할 때 사용한다.
SQL이 테이블을 대상으로 한다면, JPQL은 엔티티 객체를 대상으로 SQL을 실행한다 생각하면 된다.
엔티티 객체를 대상으로 하기 때문에 from 다음에 Item 엔티티 객체 이름이 들어간다. 엔티티 객체와 속성의 대소문 자는 구분해야 한다.
JPQL은 SQL과 문법이 거의 비슷하기 때문에 개발자들이 쉽게 적응할 수 있다.
결과적으로 JPQL을 실행하면 그 안에 포함된 엔티티 객체의 매핑 정보를 활용해서 SQL을 만들게 된다.
실행된 JPQL
select i from Item i
wherei.itemNamelike concat('%',:itemName,'%')
andi.price<= :maxPrice
JPQL을 통해 실행된 SQL
selectitem0_.idas id1_0_,
item0_.item_nameas item_nam2_0_,
item0_.priceas price3_0_,
item0_.quantityas quantity4_0_
from item item0_
where (item0_.item_namelike ('%'||?||'%')) -- 이름 기반 파라미터는 물음표로 바뀌어서 실행됨anditem0_.price<=?
파라미터
JPQL에서 파라미터는 다음과 같이 입력한다.
where price <= :maxPrice
파라미터 바인딩은 다음과 같이 사용한다.
query.setParameter("maxPrice", maxPrice)
동적 쿼리 문제
JPA를 사용해도 동적 쿼리 문제가 남아있다. 동적 쿼리는 뒤에서 설명하는 Querydsl이라는 기술을 활용하면 매우 깔 끔하게 사용할 수 있다. 실무에서는 동적 쿼리 문제 때문에, JPA 사용할 때 Querydsl도 함께 선택하게 된다.
JPA 적용 3 - 예외 변환
JPA의 경우 예외가 발생하면 JPA 예외가 발생하게 된다.
EntityManager 는 순수한 JPA 기술이고, 스프링과는 관계가 없다. 따라서 엔티티 매니저는 예외가 발생하면 JPA 관련 예외를 발생시킨다.
JPA는 PersistenceException 과 그 하위 예외를 발생시킨다.
추가로 JPA는 IllegalStateException , IllegalArgumentException 을 발생시킬 수 있다.
그렇다면 JPA 예외를 스프링 예외 추상화( DataAccessException )로 어떻게 변환할 수 있을까?
비밀은 바로 @Repository 에 있다.
리포지토리에서 @repository 를 빼고, 일부러 sql 문법을 틀리게 보내면 IllegalArgumentException 가 나는데, @repository 를 붙이면 스프링 예외 계층으로 변환됌.
@repository의 기능 @Repository 가 붙은 클래스는 컴포넌트 스캔의 대상이 된다. @Repository 가 붙은 클래스는 예외 변환 AOP의 적용 대상이 된다.
- 스프링과 JPA를 함께 사용하는 경우 스프링은 JPA 예외 변환기 ( PersistenceExceptionTranslator )를 등록한다.
- 예외 변환 AOP 프록시는 JPA 관련 예외가 발생하면 JPA 예외 변환기를 통해 발생한 예외를 스프링 데이 터 접근 예외로 변환한다.
결과적으로 리포지토리에 @Repository 애노테이션만 있으면 스프링이 예외 변환을 처리하는 AOP를 만들어준다.
참고
스프링 부트는 PersistenceExceptionTranslationPostProcessor 를 자동으로 등록하는데, 여기에 서 @Repository 를 AOP 프록시로 만드는 어드바이저가 등록된다.
참고
복잡한 과정을 거쳐서 실제 예외를 변환하는데, 실제 JPA 예외를 변환하는 코드는EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible() 이다.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
JPA 적용 1 - 개발
JPA 에서 가장 중요한 부분은 객체와 테이블 맵핑.
JPA 가 제공하는 애노테이션 사용해
Item객체와 테이블을 맵핑 해보자.@Entity: JPA 가 사용하는 객체라는 뜻. 이게 있어야 JPA 가 인식할 수 있음.@Id: 테이블의 PK와 해당 필드를 매핑한다.@GeneratedValue(strategy = GenerationType.IDENTITY): PK 생성 값을 데이터베이스에서 생성하는IDENTITY방식을 사용한다. 예) MySQL auto increment@Column: 객체의 필드를 테이블의 컬럼과 매핑한다.- 객체는 itemName 이지만 컬럼명은 item_name
-
length = 10:JPA의매핑정보로DDL(create table)도생성할수있는데,그때컬럼의길이값으 로활용된다.(varchar 10)-
@Column을 생략할 경우 필드의 이름을 테이블 컬럼 이름으로 사용한다. 참고로 지금처럼 스프링 부트와 통합해서 사용하면 필드 이름을 테이블 컬럼 명으로 변경할 때 객체 필드의 카멜 케이스를 테이블 컬럼의 언 더스코어로 자동으로 변환해준다. (사실 item_name 도 지정하지 않아도 괜찮음)JPA는
public또는protected의 기본 생성자가 필수이다. 기본 생성자를 꼭 넣어주자.프록시 기술을 쓰기 위해서 필요하다?
이걸 활용해서 CRUD 하는 리포지토리 만들기.
JPA 의 모든 변경은 트렌젝션 안에서 이루어짐.
EntityManager 가 있어야 저장 조회, 가능
private final EntityManager em:생성자를보면스프링을통해엔티티매니저(EntityManager) 라는 것을 주입받은 것을 확인할 수 있다. JPA의 모든 동작은 엔티티 매니저를 통해서 이루어진다. 엔티티 매니저 는 내부에 데이터소스를 가지고 있고, 데이터베이스에 접근할 수 있다.@Transactional: JPA의 모든 데이터 변경(등록, 수정, 삭제)은 트랜잭션 안에서 이루어져야 한다. 조회는 트 랜잭션이 없어도 가능하다. 변경의 경우 일반적으로 서비스 계층에서 트랜잭션을 시작하기 때문에 문제가 없다.참고: JPA를 설정하려면
EntityManagerFactory, JPA 트랜잭션 매니저(JpaTransactionManager), 데이터 소스 등등 다양한 설정을 해야 한다. 스프링 부트는 이 과정을 모두 자동화 해준다.main()메서드 부터 시작해서 JPA를 처음부터 어떻게 설정하는지는 JPA 기본편을 참고하자. 그리고 스프링 부트의 자동 설정은JpaBaseConfiguration를 참고하자.컨피그 변경하기
테스트를 돌려 로그를 확인해보면, JPQL 을 읽어 SQL 생성하는 로그 확인 가능.
업데이트는 쿼리가 보이지 않는데, save 를 하면 캐시에 저장하고, update하면 그 캐시에 있는걸 업데이트하고, 찾을때도 그 캐시에 있는걸 찾아 쿼리가 보이지 않음. 커밋을 붙이면 업데이트 쿼리가 보임.
JPA 적용 2 -리포지토리 분석
JpaItemRepositoryV1 코드를 분석해보자.
save() - 저장
em.persist(item): JPA에서 객체를 테이블에 저장할 때는 엔티티 매니저가 제공하는persist()메서드 를 사용하면 된다.JPA가 만들어서 실행한 SQL
JPA가 만들어서 실행한 SQL을 보면
id에 값이 빠져있는 것을 확인할 수 있다. PK 키 생성 전략을IDENTITY로 사용했기 때문에 JPA가 이런 쿼리를 만들어서 실행한 것이다. 물론 쿼리 실행 이후에Item객체의id필드 에 데이터베이스가 생성한 PK값이 들어가게 된다. (JPA가 INSERT SQL 실행 이후에 생성된 ID 결과를 받아서 객체에 셋팅 해줌 )update() - 수정
em.update() 같은게 있어야 할 것 같은데?? 없다!
JPA가 만들어서 실행한 SQL
em.update()같은 메서드를 전혀 호출하지 않았다. 그런데 어떻게 UPDATE SQL이 실행되는 것일까?@Commit을 붙이면 확인할 수 있다.findById() - 단건 조회
find()를 사용하고 조회 타입과, PK 값을 주면 된다. 그 러면 JPA가 다음과 같은 조회 SQL을 만들어서 실행하고, 결과를 객체로 바로 변환해준다.JPA가 만들어서 실행한 SQL
JPA(하이버네이트)가 만들어서 실행한 SQL은 별칭이 조금 복잡하다. 조인이 발생하거나 복잡한 조건에서도 문제 없도 록 기계적으로 만들다 보니 이런 결과가 나온 듯 하다.
JPA에서 단순히 PK를 기준으로 조회하는 것이 아닌, 여러 데이터를 복잡한 조건으로 데이터를 조회하려면 JPQL 사용해야함.
JPQL
JPA는 JPQL(Java Persistence Query Language)이라는 객체지향 쿼리 언어를 제공한다.
주로 여러 데이터를 복잡한 조건으로 조회할 때 사용한다.
SQL이 테이블을 대상으로 한다면, JPQL은 엔티티 객체를 대상으로 SQL을 실행한다 생각하면 된다.
엔티티 객체를 대상으로 하기 때문에
from다음에Item엔티티 객체 이름이 들어간다. 엔티티 객체와 속성의 대소문 자는 구분해야 한다.JPQL은 SQL과 문법이 거의 비슷하기 때문에 개발자들이 쉽게 적응할 수 있다.
결과적으로 JPQL을 실행하면 그 안에 포함된 엔티티 객체의 매핑 정보를 활용해서 SQL을 만들게 된다.
실행된 JPQL
JPQL을 통해 실행된 SQL
파라미터
where price <= :maxPricequery.setParameter("maxPrice", maxPrice)동적 쿼리 문제
JPA를 사용해도 동적 쿼리 문제가 남아있다. 동적 쿼리는 뒤에서 설명하는 Querydsl이라는 기술을 활용하면 매우 깔 끔하게 사용할 수 있다. 실무에서는 동적 쿼리 문제 때문에, JPA 사용할 때 Querydsl도 함께 선택하게 된다.
JPA 적용 3 - 예외 변환
JPA의 경우 예외가 발생하면 JPA 예외가 발생하게 된다.
EntityManager는 순수한 JPA 기술이고, 스프링과는 관계가 없다. 따라서 엔티티 매니저는 예외가 발생하면 JPA 관련 예외를 발생시킨다.PersistenceException과 그 하위 예외를 발생시킨다.IllegalStateException,IllegalArgumentException을 발생시킬 수 있다.DataAccessException)로 어떻게 변환할 수 있을까?@Repository에 있다.리포지토리에서 @repository 를 빼고, 일부러 sql 문법을 틀리게 보내면 IllegalArgumentException 가 나는데, @repository 를 붙이면 스프링 예외 계층으로 변환됌.
@repository의 기능
@Repository가 붙은 클래스는 컴포넌트 스캔의 대상이 된다.@Repository가 붙은 클래스는 예외 변환 AOP의 적용 대상이 된다.- 스프링과 JPA를 함께 사용하는 경우 스프링은 JPA 예외 변환기 (
PersistenceExceptionTranslator)를 등록한다.- 예외 변환 AOP 프록시는 JPA 관련 예외가 발생하면 JPA 예외 변환기를 통해 발생한 예외를 스프링 데이 터 접근 예외로 변환한다.
결과적으로 리포지토리에
@Repository애노테이션만 있으면 스프링이 예외 변환을 처리하는 AOP를 만들어준다.참고
스프링 부트는
PersistenceExceptionTranslationPostProcessor를 자동으로 등록하는데, 여기에 서@Repository를 AOP 프록시로 만드는 어드바이저가 등록된다.참고
복잡한 과정을 거쳐서 실제 예외를 변환하는데, 실제 JPA 예외를 변환하는 코드는
EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible()이다.리포지토리의 클래스 로그를 찍어보면 AOP 가 적용된것 확인 가능.
@transactional, @repository 를 빼면 그냥 클래스로 사용함.
All reactions