Persistence Context
- 프로그램의 모든 Entity instance들과 데이터베이스 사이의 관계를 나타낸다.
- Persistence Context Entity States
- Transient : 아직 ID를 부여받지 않고 Persistence context에 연관되지 않음
- Managed : Persistence context에 의해 관리되고 해당 entity의 변경은 데이터베이스에 반영됨
- Detached : 이전에 관리받았지만 현재는 관리되지 않음
- Removed : Database에서 곧 지워질 예정. 아직 객체와 ID는 존재
Entity Manager
- Entity의 Persistence State를 변경, 관리한다.
- 명령어
- Persist : 아직 Managed되지 않은 Entity를 Managed로 바꾼다.
- Find : id를 통해 Managed인 Entity를 찾는다.
- Merge : Detached인 Entity를 업데이트하고 Managed인 Entity를 반환한다. 만일 데이터베이스에 Entity를 못 찾으면 새로 만들어서 Persist한다.
- Remove : Entity를 detach하고 데이터베이스에서 지운다.
Lazy Loading
- fetch type을 수정해서 Entity가 불필요하게 연관된 데이터를 로딩하는 것을 방지할 수 있다.
- FetchType.Eager : 항상 Entity를 조회할 때 연관된 데이터를 모두 같이 가져온다.
- FetchType.Lazy : 연관된 데이터가 참조될 때까지 가져오지 않고 기다린다.
- 기본으로 @OneToMany, @ManyToMany는 FetchType.Lazy, @ManyToOne, @OneToOne은 FetchType.Eager이다.
- 아래는 FetchType.Lazy를 사용한 예시이다.
@Entity
public class Person {
@Id
@GeneratedValue
Long id;
@OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
List<Outfit> outfits;
private String name;
private int age;
private String favoriteComposer;
/* rest of class */
}
Cascading
- CascadeType을 지정해서 하나의 Entity를 변경할 때 Persistence 명령어가 연관된 다른 Entity에게도 전달되게 할 수 있다.
- 아래는 CascadeType.ALL을 사용한 예시이다.
@Entity
public class Person {
@Id
@GeneratedValue
Long id;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
List<Outfit> outfits;
private String name;
private int age;
private String favoriteComposer;
/* rest of class */
쿼리를 작성하는 방법
- JPQL
- EntityManager를 통해 쿼리를 전달하고 원하는 객체를 반환하게 해준다.
- 다음은 JPQL을 통해 쿼리를 생성하고 데이터를 가져오는 예시이다.
private static final String FIND_PERSON_BY_COMPOSER =
"select p from Person p " +
"where p.favoriteComposer like :favoriteComposer";
public Person findPersonByFavoriteComposer(String favoriteComposer){
TypedQuery<Person> query = entityManager.createQuery(FIND_PERSON_BY_COMPOSER, Person.class);
query.setParameter("favoriteComposer", favoriteComposer);
return query.getSingleResult();
}
- NamedQuery
- NamedQuery를 통해 미리 쿼리를 구성하고 컴파일 검사를 하게 할 수 있다.
- name은 전역적으로 유일해야하며 보통 각 Entity class 위에 정의해 둔다.
@NamedQueries({
@NamedQuery(
name = "Outfit.findByHat",
query = "select o from Outfit o where o.hat = :hat"),
@NamedQuery(
name = "Outfit.findBySock",
query = "select o from Outfit o where o.sock = :sock")
})
- criteria builder
- 다음과 같이 criteria builder를 활용할 수도 있다.
List<Humanoid> findHumanoidByOutfitCriteria(Outfit o) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Humanoid> criteria = cb.createQuery(Humanoid.class);
Root<Humanoid> root = criteria.from(Humanoid.class);
criteria.select(root).where(cb.isMember(o, root.get("outfits")));
return entityManager.createQuery(criteria).getResultList();
}
Repsitory Pattern
- Repository pattern은 데이터베이스를 collection으로 보는 방법이다.
- 다음은 코드 예시이다.
@Repository
public class HumanoidRepositoryImpl implements HumanoidRepository {
@Autowired
EntityManager entityManager;
@Override
public Humanoid save(Humanoid h) {
if(h.getId() == null || h.getId() <= 0) {
entityManager.persist(h);
} else {
h = entityManager.merge(h);
}
return h;
}
@Override
public Humanoid findById(Long id) {
return entityManager.find(Humanoid.class, id);
}
@Override
public void delete(Humanoid h) {
if (entityManager.contains(h)) {
entityManager.remove(h);
} else {
entityManager.remove(entityManager.merge(h));
}
}
Spring Data JPA
- JPA를 쉽게 사용하기 위해 함수 이름만으로 코드를 작성해준다.
- 단순히 Spring Data interface를 상속 받으면 사용할 수 있다.
- 아래는 예제 코드로 구현 없이 함수 이름만 있으면 된다.
@Repository
public interface HumanoidRepository extends JpaRepository<Humanoid, Long> {
//you can reference associations and attributes by chaining
//attribute names. Here we reference Humanoid.outfits.hat
List<Humanoid> findAllByOutfitsHat(String hat);
//you can provide specific JPQL Queries
@Query("select h from Humanoid h where :outfit member of h.outfits ")
List<Humanoid> findAllByOutfit(@Param("outfit") Outfit outfit);
//does the same as above
List<Humanoid> findAllByOutfitsContaining(Outfit outfit);
//automatically uses query named Humanoid.findAllNamedQuery
List<Humanoid> findAllNamedQuery(Outfit outfit);
}
Flushing and Transaction
- Flushing : Persistence Context의 상태를 데이터베이스에 동기화하는 과정
- Transaction : 일련의 실패 혹은 성공한 과정
- Flushing을 통해 Persistence Context는 Level 1 캐시의 역할을 한다.
- 다음과 같이 @Transactional을 통해 사용할 수 있다.
@Transactional
public void createOutfitForPerson(Outfit outfit, Long personId) {
outfitRepository.save(outfit);
//getOne throws EntityNotFoundException if personId doesn't exist!
humanoidRepository.getOne(personId).getOutfits().add(outfit);
}