디렉토리 구조 및 공통 클래스 작성하기
1. 디렉토리 구조
2. EntityManagerGenerator 클래스 작성하기
package org.example.section03.entity;
public class EntityManagerGenerator {
private static EntityManagerFactory factory
= Persistence.createEntityManagerFactory("jpatest");
private EntityManagerGenerator() {}
public static EntityManager getInstance() { return factory.createEntityManager(); }
}
3. Menu 클래스 생성하기
package org.example.section03.entity;
@Entity(name = "Section03Menu") // 이름 생략 시 class명과 동일, 중복 entity 명칭 불가
@Table(name = "tbl_menu") // 매핑 될 테이블 설정
public class Menu {
@Id
@Column(name = "menu_code")
// @GeneratedValue(strategy = GenerationType.IDENTITY)
private int menuCode;
@Column(name = "menu_name")
private String menuName;
@Column(name = "menu_price")
private int menuPrice;
@Column(name = "category_code")
private int categoryCode;
@Column(name = "orderable_status")
private String orderableStatus;
public Menu() {}
public Menu(int menuCode, String menuName, int menuPrice, int categoryCode, String orderableStatus) {
this.menuCode = menuCode;
this.menuName = menuName;
this.menuPrice = menuPrice;
this.categoryCode = categoryCode;
this.orderableStatus = orderableStatus;
}
public int getMenuCode() {
return menuCode;
}
public String getMenuName() {
return menuName;
}
public int getMenuPrice() {
return menuPrice;
}
public int getCategoryCode() {
return categoryCode;
}
public String getOrderableStatus() {
return orderableStatus;
}
public void setMenuCode(int menuCode) {
this.menuCode = menuCode;
}
public void setMenuPrice(int menuPrice) {
this.menuPrice = menuPrice;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
@Override
public String toString() {
return "Menu{" +
"menuCode=" + menuCode +
", menuName='" + menuName + '\'' +
", menuPrice=" + menuPrice +
", categoryCode=" + categoryCode +
", orderableStatus='" + orderableStatus + '\'' +
'}';
}
}
4. EntityLifeCycle 클래스 작성하기
package org.example.section03.entity;
public class EntityLifeCycle {
private EntityManager entityManager;
public Menu findMenuByMenuCode(int menuCode) {
entityManager = EntityManagerGenerator.getInstance();
return entityManager.find(Menu.class, menuCode);
}
public EntityManager getManagerInstance() {
return entityManager;
}
}
EntityLifeCycle
1. 엔티티의 생명 주기
(1) 비영속 상태(New/Transient)
엔터티 객체가 생성되고 아직 영속성 컨텍스트에 저장되지 않은 상태\
* 테스트 코드
package org.example.section03.entity;
class EntityLifeCycleTest {
private EntityLifeCycle entityLifeCycle;
@BeforeEach
void init() {
this.entityLifeCycle = new EntityLifeCycle();
}
@DisplayName("비영속 테스트")
@ParameterizedTest
@ValueSource(ints = {1, 2})
void testTransient(int menuCode) {
// when
Menu foundMenu = entityLifeCycle.findMenuByMenuCode(menuCode);
Menu newMenu = new Menu(
foundMenu.getMenuCode(),
foundMenu.getMenuName(),
foundMenu.getMenuPrice(),
foundMenu.getCategoryCode(),
foundMenu.getOrderableStatus()
);
/*foundMenu: 영속 상태의 엔터티
newMenu: 비영속 상태의 엔터티
<구분 기준> 영속성 컨텍스트에서 관리가 되고 있냐 아니냐*/
EntityManager entityManager = entityLifeCycle.getManagerInstance();
//then
assertTrue(entityManager.contains(foundMenu)); // foundMenu는 영속성 컨텍스트에서 관리 되는 영속 상태의 객체
assertFalse(entityManager.contains(newMenu)); // newMenu는 영속성 컨텍스트에서 관리 되지 않는 비영속 상태의 객체
// contains 함수는 영속성 컨테이너에서 관리되고 있는 엔터티에 대해서 TRUE
}
}
(2) 영속 상태(Managed)
엔터티 객체가 영속성 컨텍스트에 저장된 상태로, 이 상태에서 엔터티를 수정하면 자동으로 DB에 반영된다.
* 테스트 코드
package org.example.section03.entity;
class EntityLifeCycleTest {
private EntityLifeCycle entityLifeCycle;
@BeforeEach
void init() {
this.entityLifeCycle = new EntityLifeCycle();
}
@DisplayName("다른 엔터티 매니저가 관리하는 엔터티의 영속성 테스트")
@ParameterizedTest
@ValueSource(ints = {1, 2})
void testManagedOtherEntityManager(int menuCode) {
//when
Menu menu1 = entityLifeCycle.findMenuByMenuCode(menuCode);
Menu menu2 = entityLifeCycle.findMenuByMenuCode(menuCode);
//then
assertNotEquals(menu1, menu2); // menu1과 menu2은 다른 객체이다.
}
@DisplayName("같은 엔터티 매니저가 관리하는 엔터티의 영속성 테스트")
@ParameterizedTest
@ValueSource(ints = {1, 2})
void testManagedSameEntityManager(int menuCode) {
//when
EntityManager entityManager = EntityManagerGenerator.getInstance();
Menu menu1 = entityManager.find(Menu.class, menuCode);
Menu menu2 = entityManager.find(Menu.class, menuCode);
//then
assertEquals(menu1, menu2);
}
}
(3) 준영속 상태(Detached)
엔터티 객체가 영속성 컨텍스트와 분리된 상태로, 이 상태에서 엔티티를 수정하면 DB에는 반영되지 않는다.
분리된 엔터티를 다시 관리 상태로 만들어주기 위해서는 EntityManager 객체의 merge() 메소드를 사용한다.
1) 준영속성 detach 테스트
detach는 remove(삭제 상태)와 다르다.
package org.example.section03.entity;
class EntityLifeCycleTest {
private EntityLifeCycle entityLifeCycle;
@BeforeEach
void init() {
this.entityLifeCycle = new EntityLifeCycle();
}
@DisplayName("준영속화 detach 테스트")
@ParameterizedTest
@CsvSource({"11, 1000", "12, 1000"})
void testDetachEntity(int menuCode, int menuPrice) {
//given
EntityManager entityManager = EntityManagerGenerator.getInstance();
EntityTransaction entityTransaction = entityManager.getTransaction();
//when
entityTransaction.begin();
Menu foundMenu = entityManager.find(Menu.class, menuCode);
// detach : 특정 엔터티만 준영속 상태로 만든다.
entityManager.detach(foundMenu);
foundMenu.setMenuPrice(menuPrice);
// flush : 영속성 컨텍스트의 상태를 DB로 내보낸다. 단, commit 하지 않은 상태이므로 rollback 가능하다.
entityManager.flush();
// then
assertNotEquals(menuPrice, entityManager.find(Menu.class, menuCode).getMenuPrice());
entityTransaction.rollback();
}
@DisplayName("준영속화 detach 후 다시 영속화 테스트")
@ParameterizedTest
@CsvSource({"11, 1000", "12, 1000"})
void testDetachAndMerge(int menuCode, int menuPrice) {
//given
EntityManager entityManager = EntityManagerGenerator.getInstance();
EntityTransaction entityTransaction = entityManager.getTransaction();
//when
entityTransaction.begin();
Menu foundMenu = entityManager.find(Menu.class, menuCode);
entityManager.detach(foundMenu);
foundMenu.setMenuPrice(menuPrice);
/* merge 할 때는 1차적으로 1차 캐시 보고, DB보고(DB에서 조회해서 1차 캐시에 저장), 그래도 없으면 새로 생성
조회한 영속 엔터티 객체에 준영속 상태의 엔터티 객체의 값을 병합한 뒤 영속 엔터티 객체를 반환한다.
혹은 조회할 수 없는 데이터라면 새로 생성해서 병합한다. key 값이 같은 것끼리 merge 된다.*/
entityManager.merge(foundMenu);
entityManager.flush();
// then
assertEquals(menuPrice, entityManager.find(Menu.class, menuCode).getMenuPrice());
entityTransaction.rollback();
}
@DisplayName("detach 후 merge한 데이터 update 테스트")
@ParameterizedTest
@CsvSource({"11, 하양 민트초코죽", "12, 까만 딸기탕후루"})
void testMergeUpdate(int menuCode, String menuName) {
//given
EntityManager entityManager = EntityManagerGenerator.getInstance();
Menu foundMenu = entityManager.find(Menu.class, menuCode);
entityManager.detach(foundMenu);
// when
foundMenu.setMenuName(menuName);
Menu refoundMenu = entityManager.find(Menu.class, menuCode);
entityManager.merge(foundMenu);
// 두 객체의 이름은 모두 "하양 민트초코죽" 이 된다.
//then
assertEquals(menuName, refoundMenu.getMenuName());
}
@DisplayName("detach 후 merge한 데이터 save 테스트")
@Test
void testMergeSave() {
// given
EntityManager entityManager = EntityManagerGenerator.getInstance();
EntityTransaction entityTransaction = entityManager.getTransaction();
Menu foundMenu = entityManager.find(Menu.class, 20);
entityManager.detach(foundMenu);
//when
entityTransaction.begin();
foundMenu.setMenuName("치약맛 초코 아이스크림");
foundMenu.setMenuCode(999);
entityManager.merge(foundMenu);
entityTransaction.commit();
//then
assertEquals("치약맛 초코 아이스크림", entityManager.find(Menu.class, 999).getMenuName());
}
}
2) 준영속성 clear 테스트
package org.example.section03.entity;
class EntityLifeCycleTest {
private EntityLifeCycle entityLifeCycle;
@BeforeEach
void init() {
this.entityLifeCycle = new EntityLifeCycle();
}
@DisplayName("준영속화 clear 테스트")
@ParameterizedTest
@ValueSource(ints = {1, 3})
void testClearPersistenceContext(int menuCode) {
//given
EntityManager entityManager = EntityManagerGenerator.getInstance();
Menu foundMenu = entityManager.find(Menu.class, menuCode);
//when
// clear : 영속성 컨텍스트를 초기화 한다 -> 영속성 컨텍스트 내의 모든 엔터티는 준영속화 된다
entityManager.clear();
//then
Menu expectedMenu = entityManager.find(Menu.class, menuCode);
assertNotEquals(expectedMenu, foundMenu);
}
}
3) 준영속성 close 테스트
package org.example.section03.entity;
class EntityLifeCycleTest {
private EntityLifeCycle entityLifeCycle;
@BeforeEach
void init() {
this.entityLifeCycle = new EntityLifeCycle();
}
@DisplayName("준영속화 close 테스트")
@ParameterizedTest
@ValueSource(ints = {1, 3})
void testClosePersistenceContext(int menuCode) {
//given
EntityManager entityManager = EntityManagerGenerator.getInstance();
Menu foundMenu = entityManager.find(Menu.class, menuCode);
//when
// close : 영속성 컨텍스트 종료된다 -> 영속성 컨텍스트 내의 모든 엔터티는 준영속화 된다
entityManager.close();
// 해당 영속성 컨텍스트는 종료되었으므로 find을 할 시 에러가 나므로 에러 캐치
//then
assertThrows(
IllegalStateException.class,
() -> entityManager.find(Menu.class, menuCode)
);
}
}
(4) 삭제 상태(Removed)
엔터티 객체가 영속성 컨텍스트에서 제거된 상태로, 이 상태에서는 엔터티를 수정해도 DB에는 반영되지 않는다.
* 테스트 코드
package org.example.section03.entity;
class EntityLifeCycleTest {
private EntityLifeCycle entityLifeCycle;
@BeforeEach
void init() {
this.entityLifeCycle = new EntityLifeCycle();
}
@DisplayName("영속성 엔터티 삭제 remove 테스트")
@ParameterizedTest
@ValueSource(ints = {2})
void testRemoveEntity(int menuCode) {
//given
EntityManager entityManager = EntityManagerGenerator.getInstance();
EntityTransaction entityTransaction = entityManager.getTransaction();
Menu foundMenu = entityManager.find(Menu.class, menuCode);
//when
entityTransaction.begin();
// remove : 엔터티를 영속성 컨텍스트 및 데이터 베이스에서 삭제한다
// 단, 트랙잭션을 제어하지 않으면 데이터 베이스에 영구 반영 되지는 않는다.
entityManager.remove(foundMenu);
entityManager.flush();
//then
Menu refoundMenu = entityManager.find(Menu.class, menuCode);
assertNull(refoundMenu);
entityTransaction.rollback();
}
}
'Spring Framework > JPA' 카테고리의 다른 글
[JPA] 6. Association Mapping (6) | 2024.09.17 |
---|---|
[JPA] 5. Mapping (0) | 2024.09.16 |
[JPA] 3. CRUD 실습 (0) | 2024.09.13 |
[JPA] 2. Persistence Context - EntityManager 생성하기 (1) | 2024.09.13 |
[JPA] 1. 개요 및 개발환경 구축 (0) | 2024.09.13 |