-
JPA test - 1카테고리 없음 2024. 10. 30. 12:29
src/main/resources/META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"> <!-- EntityManagerFactory 생성 시 사용되는 persistence name --> <persistence-unit name="jpatest"> <properties> <!-- 필수 속성 --> <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/> <property name="javax.persistence.jdbc.user" value="root"/> <property name="javax.persistence.jdbc.password" value="1111"/> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpatest?characterEncoding=UTF-8"/> <!-- 하이버네이트 사용 시 다른 DB에서 MySQL 문법을 사용 가능하도록 변경.--> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect"/> <!-- 콘솔에 SQL 출력 여부 --> <property name="hibernate.show_sql" value="true"/> <!-- 가독성 높여주는 formatting 여부 --> <property name="hibernate.format_sql" value="true"/> <!-- Comment 확인 여부 --> <property name="hibernate.use_sql_comments" value="true"/> <!-- 테이블 생성 옵션 create / create-drop / validate / update / none --> <property name="hibernate.hbm2ddl.auto" value="create"/> </properties> </persistence-unit> </persistence>
- hibernate.hbm2ddl.auto 옵션
- create
- 애플리케이션 시작 시 기존 테이블을 삭제하고, 엔티티 매핑 정보에 따라 모든 테이블과 관계를 새로 생성함
- 기존 데이터가 삭제되므로 개발 초기 단계에서 주로 사용
- update
- 엔티티 매핑 정보를 기반으로 기존 테이블을 수정함
- 새로운 컬럼이 있으면 추가하고, 없으면 기존 상태를 유지함
- 데이터는 삭제되지 않으며, 테이블에 없는 새 컬럼만 추가
- create
src/main/java/domain/Employee.java
package domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "employee") public class Employee { @Id @Column(name = "emp_id", length = 6) private String empId; private String empName; private String deptId; private String joinDate; private long salary; public Employee() { } public Employee(String empId, String empName, String deptId, String joinDate, long salary) { this.empId = empId; this.empName = empName; this.deptId = deptId; this.joinDate = joinDate; this.salary = salary; } public String getEmpId() { return empId; } public void setEmpId(String empId) { this.empId = empId; } public String getEmpName() { return empName; } public void setEmpName(String empName) { this.empName = empName; } public String getDeptId() { return deptId; } public void setDeptId(String deptId) { this.deptId = deptId; } public String getJoinDate() { return joinDate; } public void setJoinDate(String joinDate) { this.joinDate = joinDate; } public long getSalary() { return salary; } public void setSalary(long salary) { this.salary = salary; } }
src/main/java/jpajava/EmployeeTest.java
package jpajava; import domain.Employee; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class EmployeeTest { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatest"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); System.out.println("TRANSACTION STARTED"); try { Employee employee = new Employee("202401", "홍길동", null, "2024-01-01", 600); em.persist(employee); tx.commit(); } catch (Exception e) { tx.rollback(); System.out.println("TRANSACTION ROLLBACK"); System.out.println("[Error] " + e.getMessage()); } finally { System.out.println("TRANSACTION ENDED"); } } }
EmployeeTest.java 실행 결과
EntityManager에서는 기본 생성자가 반드시 필요함
- JPA가 엔티티 객체를 생성할 때 리플렉션을 사용하기 때문
- EntityManager는 데이터베이스에서 데이터를 조회한 후 매핑된 엔티티 클래스를 통해 해당 데이터를 객체로 변환하는 과정을 거치는데, 이때 기본 생성자가 없으면 객체를 인스턴스화할 수 없음
- [ 프록시 객체 생성 ] JPA 구현체(예: Hibernate)는 엔티티의 프록시 객체를 생성해 지연 로딩(Lazy Loading)을 지원함. 프록시 객체는 실제 객체 대신 사용될 수 있어야 하므로 기본 생성자가 필요함
- [ 리플렉션 사용 ] JPA는 리플렉션을 사용해 엔티티를 생성함. 리플렉션으로 객체를 생성할 때는 기본 생성자가 필요하며, 이를 통해 매핑된 필드 값을 설정함
- [ 내부 동작 ] JPA는 엔티티 로드 시점에 기본 생성자를 사용해 인스턴스를 생성한 후 필드에 값을 세팅함. 이 과정에서 매개변수가 있는 생성자는 사용할 수 없기 때문에 기본 생성자가 요구
- 따라서 JPA 엔티티 클래스에는 public 또는 protected 접근자를 가진 기본 생성자가 반드시 있어야 함
Employee.java 에 기본 생성자를 주석처리 하고 EmployeeFindTest.java를 실행하면 ?!
기본 생성자가 없다는 에러가 나옴
JPA 영속성 테스트 - 저장
src/main/java/jpajava/EmployeeTest.java
package jpajava; import domain.Employee; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class EmployeeTest { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatest"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); System.out.println("TRANSACTION STARTED"); try { System.out.println("비영속 상태"); Employee employee = new Employee("202402", "김연아", null, "2024-01-01", 600); em.persist(employee); System.out.println("영속 상태"); em.find(Employee.class, "202402"); System.out.println("1차 캐시에서 가져옴"); System.out.println("commit 전"); tx.commit(); System.out.println("commit 후"); } catch (Exception e) { tx.rollback(); System.out.println("TRANSACTION ROLLBACK"); System.out.println("[Error] " + e.getMessage()); } finally { System.out.println("TRANSACTION ENDED"); } } }
실행 결과
JPA 영속성 테스트 - 조회
src/main/java/jpajava/EmployeeFindTest.java
package jpajava; import domain.Employee; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class EmployeeFindTest { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatest"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); System.out.println("TRANSACTION STARTED"); try { System.out.println("비영속 상태"); System.out.println("DB에서 가져옴"); Employee emp1 = em.find(Employee.class, "202402"); System.out.println("employee.getEmpName() : " + emp1.getEmpName()); System.out.println("commit 전"); tx.commit(); System.out.println("commit 후"); System.out.println("1차 캐시에서 가져옴"); Employee emp2 = em.find(Employee.class, "202402"); System.out.println("employee.getEmpName() : " + emp2.getEmpName()); } catch (Exception e) { tx.rollback(); System.out.println("TRANSACTION ROLLBACK"); System.out.println("[Error] " + e.getMessage()); } finally { System.out.println("TRANSACTION ENDED"); } } }
실행 결과
JPA 영속성 테스트 - 삭제
src/main/java/jpajava/EmployeeDeleteTest.java
package jpajava; import domain.Employee; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class EmployeeDeleteTest { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatest"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); System.out.println("TRANSACTION STARTED"); try { System.out.println("비영속 상태"); System.out.println("DB에서 가져옴"); Employee emp = em.find(Employee.class, "202402"); System.out.println("employee.getEmpName() : " + emp.getEmpName()); System.out.println("1차 캐시에서 삭제함"); em.remove(emp); System.out.println("commit 전"); tx.commit(); System.out.println("commit 후"); } catch (Exception e) { tx.rollback(); System.out.println("TRANSACTION ROLLBACK"); System.out.println("[Error] " + e.getMessage()); } finally { System.out.println("TRANSACTION ENDED"); } } }
실행 결과
JPA 영속성 테스트 - 수정
src/main/java/jpajava/EmployeeUpdateTest.java
package jpajava; import domain.Employee; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class EmployeeUpdateTest { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatest"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); System.out.println("TRANSACTION STARTED"); try { System.out.println("비영속 상태"); Employee emp = new Employee("202402", "김연아", null, "2024-01-01", 600); em.persist(emp); System.out.println("영속 상태"); emp = em.find(Employee.class, "202402"); System.out.println("emp.getEmpName() : " + emp.getEmpName()); System.out.println("1차 캐시에서 가져옴"); emp.setSalary(200L); em.persist(emp); System.out.println("commit 전"); tx.commit(); System.out.println("commit 후"); } catch (Exception e) { tx.rollback(); System.out.println("TRANSACTION ROLLBACK"); System.out.println("[Error] " + e.getMessage()); } finally { System.out.println("TRANSACTION ENDED"); } } }
실행 결과
src/main/java/domain/Department.java
package domain; import javax.persistence.*; @Entity @Table(name = "dept") public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "dept_id") private int deptId; @Column(name = "dept_name", length = 10, nullable = false) private String deptName; public int getDeptId() { return deptId; } public void setDeptId(int deptId) { this.deptId = deptId; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } }
@GeneratedValue(strategy = GenerationType.IDENTITY) 전략
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "dept_id") private int deptId;
- GenerationType.IDENTITY: 주로 MySQL과 같은 데이터베이스에서 AUTO_INCREMENT를 통해 기본 키를 자동 생성하는 방식
- 이 방식은 데이터베이스가 엔티티의 기본 키 값을 할당하기 때문에, JPA는 엔티티를 persist할 때 곧바로 INSERT SQL을 실행하여 데이터베이스에 저장하고 기본 키를 가져옴
- 따라서 트랜잭션이 아직 commit되지 않았더라도, persist 호출 시점에 INSERT가 발생하여 기본 키 값이 생성됨
- 1차 캐시에 쌓아 두었다가 한 번에 INSERT하는 방식이 아닌, persist 호출 시 바로 DB에 반영하는 방식
src/main/java/jpajava/DepartmentTest.java
package jpajava; import domain.Department; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class DepartmentTest { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatest"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); try { System.out.println("비영속 상태"); Department dept = new Department(); dept.setDeptName("IT"); em.persist(dept); System.out.println("영속 상태"); Department deptFound = em.find(Department.class, dept.getDeptId()); System.out.println("deptFound.getDeptId() = " + deptFound.getDeptId() + ", deptFound.getDeptName() = " + deptFound.getDeptName()); System.out.println("1차 캐시에서 가져옴"); System.out.println("commit 전"); tx.commit(); System.out.println("commit 후"); } catch (Exception e) { tx.rollback(); System.out.println("TRANSACTION ROLLBACK"); System.out.println("[Error] " + e.getMessage()); } finally { } } }
실행 결과
src/main/java/jpajava/DepartmentFindTest.java
package jpajava; import domain.Department; import domain.Employee; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class DepartmentFindTest { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatest"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); try { System.out.println("비영속 상태"); System.out.println("DB에서 가져옴"); Department deptFound = em.find(Department.class, 1); System.out.println("deptFound.getDeptId() = " + deptFound.getDeptId() + ", deptFound.getDeptName() = " + deptFound.getDeptName()); System.out.println("commit 전"); tx.commit(); System.out.println("commit 후"); System.out.println("1차 캐시에서 가져옴"); Department deptFound2 = em.find(Department.class, 1); System.out.println("deptFound2.getDeptId() = " + deptFound2.getDeptId() + ", deptFound2.getDeptName() = " + deptFound2.getDeptName()); } catch (Exception e) { tx.rollback(); System.out.println("TRANSACTION ROLLBACK"); System.out.println("[Error] " + e.getMessage()); } finally { } } }
실행 결과
src/main/java/jpajava/DepartmentDeleteTest.java
package jpajava; import domain.Department; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class DepartmentDeleteTest { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatest"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); try { System.out.println("비영속 상태"); System.out.println("DB에서 가져옴"); Department deptFound = em.find(Department.class, 1); System.out.println("deptFound.getDeptId() = " + deptFound.getDeptId() + ", deptFound.getDeptName() = " + deptFound.getDeptName()); System.out.println("1차 캐시에서 삭제함"); em.remove(deptFound); System.out.println("commit 전"); tx.commit(); System.out.println("commit 후"); } catch (Exception e) { tx.rollback(); System.out.println("TRANSACTION ROLLBACK"); System.out.println("[Error] " + e.getMessage()); } finally { } } }
실행 결과
src/main/java/jpajava/DepartmentUpdateTest.java
package jpajava; import domain.Department; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class DepartmentUpdateTest { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatest"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); try { System.out.println("비영속 상태"); Department dept = new Department(); dept.setDeptName("HR"); em.persist(dept); System.out.println("영속 상태"); dept = em.find(Department.class, 2); System.out.println("dept.getDeptId() = " + dept.getDeptId() + ", dept.getDeptName() = " + dept.getDeptName()); System.out.println("1차 캐시에서 가져옴"); dept.setDeptName("Sales"); em.persist(dept); System.out.println("commit 전"); tx.commit(); System.out.println("commit 후"); } catch (Exception e) { tx.rollback(); System.out.println("TRANSACTION ROLLBACK"); System.out.println("[Error] " + e.getMessage()); } finally { } } }
실행 결과
- hibernate.hbm2ddl.auto 옵션