2-1. 엔티티 매니저(EntityManager)
1) 엔티티 매니저란?
- 엔티티를 저장하는 메모리상의 데이터베이스
- 엔티티를 저장하고 수정하고 삭제하고 조회하는 등의 엔티티와 관련된 모든 일 수행
- 스레드세이프 하지 않기 때문에 동시성 문제 발생 가능 -> 스레드간 공유 X
- web의 경우 request scope와 일치
2) 엔티티 매니저 팩토리란?
- 엔티티 매니저를 생성할 수 있는 기능을 제공하는 팩토리 클래스
- 스레드 세이프 -> 서로 다른 스레드간 공유해서 재사용
- 요청 스코프마다 생성하기에는 비용 부담이 크기 때문에 application 스코프와 동일한 싱글톤으로 생성해서 관리
- 데이터 베이스를 사용하는 애플리케이션 당 한 개의 EntityManagerFactory 생성
3) 영속성 컨텍스트란?
- 엔티티를 영구 저장하는 환경
- 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리
- 영속성 엔티티를 key value 방식으로 저장하는 저장소 역할
- 엔티티 매니저를 생성할 때 하나 만들어짐
- 엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있고, 영속성 컨텍스트 관리 가능
2-2. CRUD test
private static EntityManagerFactory entityManagerFactory;
private static EntityManager entityManager;
@BeforeAll
public static void initFactory() {
entityManagerFactory = Persistence.createEntityManagerFactory("jpatest");
}
@BeforeEach
public void initManager() {
entityManager = entityManagerFactory.createEntityManager();
}
@AfterAll
public static void closeFactory() {
entityManagerFactory.close();
}
@AfterEach
public void closeManager() {
entityManager.close();
}
@Test
public void 메뉴코드로_메뉴_조회_테스트() {
int menuCode = 2;
Menu foundMenu = entityManager.find(Menu.class, menuCode);
assertNotNull(foundMenu);
assertEquals(menuCode, foundMenu.getMenuCode());
System.out.println("foundMenu = " + foundMenu);
}
@Test
public void 새로운_메뉴_추가_테스트() {
Menu menu = new Menu();
menu.setMenuName("JPA 테스트용 신규 메뉴");
menu.setMenuPrice(5000);
menu.setCategoryCode(4);
menu.setOrderableStatus("Y");
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
try {
entityManager.persist(menu);
entityTransaction.commit();
} catch (Exception e) {
entityTransaction.rollback();
e.printStackTrace();
}
assertTrue(entityManager.contains(menu));
}
@Test
public void 메뉴_이름_수정_테스트() {
Menu menu = entityManager.find(Menu.class, 2);
System.out.println("menu = " + menu);
String menuNameToChange = "우럭뽈살젤리";
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
try {
menu.setMenuName(menuNameToChange);
entityTransaction.commit();
}catch(Exception e) {
entityTransaction.rollback();
e.printStackTrace();
}
assertEquals(menuNameToChange, entityManager.find(Menu.class, 2).getMenuName());
}
@Test
public void 메뉴_삭제하기_테스트() {
Menu menuToRemove = entityManager.find(Menu.class, 1);
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
try {
entityManager.remove(menuToRemove);
entityTransaction.commit();
}catch(Exception e) {
entityTransaction.rollback();
e.printStackTrace();
}
Menu removedMenu = entityManager.find(Menu.class, 1);
assertEquals(null, removedMenu);
}
3) 영속성 컨텍스트
- 엔티티의 생명 주기
- 비영속성 테스트
- 객체만 생성하면 영속성 컨텍스트나 데이터 베이스와 관련 없는 비영속 상태
@Test
public void 비영속성_테스트() {
Menu foundMenu = entityManager.find(Menu.class, 11);
Menu newMenu = new Menu();
newMenu.setMenuCode(foundMenu.getMenuCode());
newMenu.setMenuName(foundMenu.getMenuName());
newMenu.setMenuPrice(foundMenu.getMenuPrice());
newMenu.setCategoryCode(foundMenu.getCategoryCode());
newMenu.setOrderableStatus(foundMenu.getOrderableStatus());
boolean isTrue = (foundMenu == newMenu);
assertTrue(isTrue);
}
- 영속성 연속 조회
- 엔티티 매니저가 영속성 컨텍스트 엔티티 객체를 저장하면 영속성 컨텍스트가 엔티티 객체를 관리하게 되고 이를 영속 상태라 함
- find(), JPQL을 사용한 조회도 영속 상태
@Test
public void 영속성_연속_조회_테스트() {
Menu foundMenu1 = entityManager.find(Menu.class, 11);
Menu foundMenu2 = entityManager.find(Menu.class, 11);
boolean isTrue = (foundMenu1 == foundMenu2);
assertTrue(isTrue);
}
@Test
public void 영속성_객체_추가_테스트() {
Menu menuToRegist = new Menu();
menuToRegist.setMenuCode(500);
menuToRegist.setMenuName("수박죽");
menuToRegist.setMenuPrice(10000);
menuToRegist.setCategoryCode(1);
menuToRegist.setOrderableStatus("Y");
entityManager.persist(menuToRegist);
Menu foundMenu = entityManager.find(Menu.class, 500);
boolean isTrue = (menuToRegist == foundMenu);
assertTrue(isTrue);
}
@Test
public void 영속성_객체_추가_값_변경_테스트() {
Menu menuToRegist = new Menu();
menuToRegist.setMenuCode(500);
menuToRegist.setMenuName("수박죽");
menuToRegist.setMenuPrice(10000);
menuToRegist.setCategoryCode(1);
menuToRegist.setOrderableStatus("Y");
entityManager.persist(menuToRegist);
menuToRegist.setMenuName("매론죽");
Menu foundMenu = entityManager.find(Menu.class, 500);
assertEquals("수박죽", foundMenu.getMenuName());
}
@Test
public void 준영속성_detach_테스트() {
Menu foundMenu1 = entityManager.find(Menu.class, 11);
Menu foundMenu2 = entityManager.find(Menu.class, 12);
entityManager.detach(foundMenu2);
foundMenu1.setMenuPrice(5000);
foundMenu2.setMenuPrice(5000);
assertEquals(5000, entityManager.find(Menu.class, entityManager.find(Menu.class, 11).getMenuPrice()));
assertEquals(5000, entityManager.find(Menu.class, entityManager.find(Menu.class, 12).getMenuPrice()));
}
@Test
public void 준영속성_clear_테스트() {
Menu foundMenu1 = entityManager.find(Menu.class, 11);
Menu foundMenu2 = entityManager.find(Menu.class, 12);
entityManager.clear();
foundMenu1.setMenuPrice(5000);
foundMenu2.setMenuPrice(5000);
assertEquals(5000, entityManager.find(Menu.class, entityManager.find(Menu.class, 11).getMenuPrice()));
assertEquals(5000, entityManager.find(Menu.class, entityManager.find(Menu.class, 12).getMenuPrice()));
}
@Test
public void close_테스트() {
Menu foundMenu1 = entityManager.find(Menu.class, 11);
Menu foundMenu2 = entityManager.find(Menu.class, 12);
entityManager.close();
foundMenu1.setMenuPrice(5000);
foundMenu2.setMenuPrice(5000);
assertEquals(5000, entityManager.find(Menu.class, entityManager.find(Menu.class, 11).getMenuPrice()));
assertEquals(5000, entityManager.find(Menu.class, entityManager.find(Menu.class, 12).getMenuPrice()));
}
- remove 테스트
- 엔티티를 영속성 컨텍스트 및 데이터 베이스에서 삭제
- 트랜잭션을 제어하지 않으면 영구 반영되지 않음
- 커밋하는 순간 영속성 컨텍스트에서 관리하는 엔티티 객체가 데이터 베이스에 반영
- flush : 영속성 컨텍스트의 변경 내용을 데이터 베이스에 동기화하는 작업
@Test
public void 삭제_remove_테스트() {
Menu foundMenu = entityManager.find(Menu.class, 2);
entityManager.remove(foundMenu);
Menu refoundMenu = entityManager.find(Menu.class, 2);
assertEquals(2, foundMenu.getMenuCode());
assertEquals(null, refoundMenu);
}
- 병합(merge)
- 파라미터로 넘어온 준영속 엔티티 객체의 식별자 값으로 1차 캐시에서 엔티티 객체 조회
- 만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회 후 1차 캐시에 저장
- 조회한 영속 엔티티 객체에 준영속 상태의 엔티티 객체의 값을 병합한 뒤 영속 엔티티 객체를 반환 혹은 조회할 수 없는 데이터의 경우 새로 생성해서 병합 (save or update)
@Test
public void 병합_merge_수정_테스트() {
Menu menuToDetach = entityManager.find(Menu.class, 2);
entityManager.detach(menuToDetach);
menuToDetach.setMenuName("수박죽");
Menu refoundMenu = entityManager.find(Menu.class, 2);
System.out.println(menuToDetach.hashCode());
System.out.println(refoundMenu.hashCode());
entityManager.merge(menuToDetach);
Menu mergedMenu = entityManager.find(Menu.class, 2);
assertEquals("수박죽", mergedMenu.getMenuName());
}
@Test
public void 병합_merge_삽입_테스트() {
Menu menuToDetach = entityManager.find(Menu.class, 2);
entityManager.detach(menuToDetach);
menuToDetach.setMenuCode(999);
menuToDetach.setMenuName("수박죽");
entityManager.merge(menuToDetach);
Menu mergedMenu = entityManager.find(Menu.class, 999);
assertEquals("수박죽", mergedMenu.getMenuName());
}