Persistence Context

MINIMI·2023년 4월 10일

JPA

목록 보기
2/9
post-thumbnail

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 메뉴코드로_메뉴_조회_테스트() {

		// given
		int menuCode = 2;

		// when
		Menu foundMenu = entityManager.find(Menu.class, menuCode);

		// then
		assertNotNull(foundMenu);
		assertEquals(menuCode, foundMenu.getMenuCode());
		System.out.println("foundMenu = " + foundMenu);
	}

	@Test
	public void 새로운_메뉴_추가_테스트() {

		// given
		Menu menu = new Menu();
		menu.setMenuName("JPA 테스트용 신규 메뉴");
		menu.setMenuPrice(5000);
		menu.setCategoryCode(4);
		menu.setOrderableStatus("Y");

		// when
		EntityTransaction entityTransaction = entityManager.getTransaction();
		entityTransaction.begin();
		try {
			entityManager.persist(menu);
			entityTransaction.commit();
		} catch (Exception e) {
			entityTransaction.rollback();
			e.printStackTrace();
		}
		
		//then
		assertTrue(entityManager.contains(menu));
	}
	
	@Test
	public void 메뉴_이름_수정_테스트() {
		
		//given
		Menu menu = entityManager.find(Menu.class, 2);
		System.out.println("menu = " + menu);
		
		String menuNameToChange = "우럭뽈살젤리";
		
		//when
		EntityTransaction entityTransaction = entityManager.getTransaction();
		entityTransaction.begin();
		
		try {
			menu.setMenuName(menuNameToChange);
			entityTransaction.commit();
		}catch(Exception e) {
			entityTransaction.rollback();
			e.printStackTrace();
		}
		
		//then
		assertEquals(menuNameToChange, entityManager.find(Menu.class, 2).getMenuName());
	}
	
	@Test
	public void 메뉴_삭제하기_테스트() {
		
		//given
		Menu menuToRemove = entityManager.find(Menu.class, 1);
		
		//when
		EntityTransaction entityTransaction = entityManager.getTransaction();
		entityTransaction.begin();
		
		try {
			entityManager.remove(menuToRemove);
			entityTransaction.commit();
		}catch(Exception e) {
			entityTransaction.rollback();
			e.printStackTrace();
		}
		
		//then
		Menu removedMenu = entityManager.find(Menu.class, 1);
		assertEquals(null, removedMenu);
	}

3) 영속성 컨텍스트

  • 엔티티의 생명 주기
    • 비영속, 영ㅇ속, 준영속, 삭제
  • 비영속성 테스트
    • 객체만 생성하면 영속성 컨텍스트나 데이터 베이스와 관련 없는 비영속 상태
@Test
	public void 비영속성_테스트() {
			
		//given
		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());
		
		//when
		boolean isTrue = (foundMenu == newMenu);
		
		//then
		assertTrue(isTrue);
	}
  • 영속성 연속 조회
    • 엔티티 매니저가 영속성 컨텍스트 엔티티 객체를 저장하면 영속성 컨텍스트가 엔티티 객체를 관리하게 되고 이를 영속 상태라 함
    • find(), JPQL을 사용한 조회도 영속 상태
@Test
	public void 영속성_연속_조회_테스트() {
		
		/* 엔티티 매니저가 영속성 컨텍스트 엔티티 객체를 저장(persist)하면 영속성 컨텍스트가 엔티티 객체를 관리하게 되고 이를 영속 상태라고 한다.
		 * find(), JPQL을 사용한 조회도 영속 상태가 된다. */
		
		//given
		Menu foundMenu1 = entityManager.find(Menu.class, 11);
		Menu foundMenu2 = entityManager.find(Menu.class, 11);
		
		//when
		boolean isTrue = (foundMenu1 == foundMenu2);
		
		//then
		assertTrue(isTrue);
	}
  • 영속성 객체 추가
@Test
	public void 영속성_객체_추가_테스트() {
		
		/* Menu Entity의 시퀀스 설정을 잠시 주석하고 테스트 진행 */
		
		//given
		Menu menuToRegist = new Menu();
		menuToRegist.setMenuCode(500);
		menuToRegist.setMenuName("수박죽");
		menuToRegist.setMenuPrice(10000);
		menuToRegist.setCategoryCode(1);
		menuToRegist.setOrderableStatus("Y");
		
		//when
		entityManager.persist(menuToRegist);
		Menu foundMenu = entityManager.find(Menu.class, 500);
		boolean isTrue = (menuToRegist == foundMenu);
		
		//then
		assertTrue(isTrue);
	}
	
	@Test
	public void 영속성_객체_추가_값_변경_테스트() {
		
		//given
		Menu menuToRegist = new Menu();
		menuToRegist.setMenuCode(500);
		menuToRegist.setMenuName("수박죽");
		menuToRegist.setMenuPrice(10000);
		menuToRegist.setCategoryCode(1);
		menuToRegist.setOrderableStatus("Y");
		
		//when
		entityManager.persist(menuToRegist);
		menuToRegist.setMenuName("매론죽");
		
		Menu foundMenu = entityManager.find(Menu.class, 500);
		
		//then
		assertEquals("수박죽", foundMenu.getMenuName());
	}
  • 준영속성(detach) 테스트
    • 특정 엔티티만 준영속 상태로 만듦
	@Test
	public void 준영속성_detach_테스트() {
		
		//given
		Menu foundMenu1 = entityManager.find(Menu.class, 11);
		Menu foundMenu2 = entityManager.find(Menu.class, 12);
//		
//		/* 1. 영속성 확인 */
//		//when
//		foundMenu1.setMenuPrice(500000);
//		foundMenu2.setMenuPrice(500000);
//		
//		//then
//		assertEquals(500000, entityManager.find(Menu.class, entityManager.find(Menu.class, 11).getMenuPrice()));
//		assertEquals(500000, entityManager.find(Menu.class, entityManager.find(Menu.class, 12).getMenuPrice()));

		/* 2. 준 영속성 확인 */
		/* 영속성 컨텍스트가 관리하던 엔티티 객체를 관리하지 않는 상태가 된다면 준영속 상태라고 한다. 
		 * 그 중 detach는 특정 엔티티만 준영속 상태로 만든다. */
		//when
		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()));


	}
  • 준영속성(clear) 테스트
@Test
	public void 준영속성_clear_테스트() {
		
		//given
		Menu foundMenu1 = entityManager.find(Menu.class, 11);
		Menu foundMenu2 = entityManager.find(Menu.class, 12);
		
		//when
		/* clear()는 영속성 컨택스트를 초기화 한다. */
		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()));
	}
  • close 테스트
@Test
	public void close_테스트() {

		//given
		Menu foundMenu1 = entityManager.find(Menu.class, 11);
		Menu foundMenu2 = entityManager.find(Menu.class, 12);
		
		//when
		/* close()는 영속성 컨택스트를 종료 한다. */
		entityManager.close();
		
		foundMenu1.setMenuPrice(5000);
		foundMenu2.setMenuPrice(5000);
		
		//then
		/* 영속성 컨텍스트를 닫았기 때문에 다시 만들기 전에는 사용할 수 없다. 
		 * java.lang.IllegalstateException : Session/EntityManager is closed */
		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_테스트() {
		
		/* remove : 엔티티를 영속성 컨텍스트 및 데이터 베이스에서 삭제한다.
		 * 단,트랜잭션을 제어하지 않으면 영구 반영되지는 않는다.
		 * 트랜잭션을 커밋하는 순간 영속성 컨텍스트에서 관리하는 엔티티 객체가 데이터 베이스에 반영되게 된다. (이를 flush라고 한다)
		 * flush : 영속성 컨텍스트의 변경 내용을 데이터 베이스에 동기화 하는 작업(등록, 수정, 삭제한 엔티티를 데이터 베이스에 반영) */
		
		//given
		Menu foundMenu = entityManager.find(Menu.class, 2);
		
		//when
		entityManager.remove(foundMenu);
		Menu refoundMenu = entityManager.find(Menu.class, 2);
		
		//then
		assertEquals(2, foundMenu.getMenuCode());
		assertEquals(null, refoundMenu);
	}
  • 병합(merge)
    • 파라미터로 넘어온 준영속 엔티티 객체의 식별자 값으로 1차 캐시에서 엔티티 객체 조회
    • 만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회 후 1차 캐시에 저장
    • 조회한 영속 엔티티 객체에 준영속 상태의 엔티티 객체의 값을 병합한 뒤 영속 엔티티 객체를 반환 혹은 조회할 수 없는 데이터의 경우 새로 생성해서 병합 (save or update)
@Test
	public void 병합_merge_수정_테스트() {
		
		//given
		Menu menuToDetach = entityManager.find(Menu.class, 2);
		entityManager.detach(menuToDetach);
		
		//when
		menuToDetach.setMenuName("수박죽");
		Menu refoundMenu = entityManager.find(Menu.class, 2);
		
		/* 준영속 엔티티와 영속 엔티티의 해쉬 코드는 다른 상태이다. */
		System.out.println(menuToDetach.hashCode());
		System.out.println(refoundMenu.hashCode());
		
		entityManager.merge(menuToDetach);
		
		//then
		Menu mergedMenu = entityManager.find(Menu.class, 2);
		assertEquals("수박죽", mergedMenu.getMenuName());
	}
	
	@Test
	public void 병합_merge_삽입_테스트() {
		
		//given
		Menu menuToDetach = entityManager.find(Menu.class, 2);
		entityManager.detach(menuToDetach);
		
		//when
		menuToDetach.setMenuCode(999);			//DB에서 조회할 수 없는 키 값으로 변
		menuToDetach.setMenuName("수박죽");
		
		entityManager.merge(menuToDetach);		//영속 상테의 엔티티와 병합(현재는 없기 때문에 삽입)
		
		//then
		Menu mergedMenu = entityManager.find(Menu.class, 999);
		assertEquals("수박죽", mergedMenu.getMenuName());
	}
profile
DREAM STARTER

0개의 댓글