경로 : src/main/resources/META-INF/persistence.xml 생성
<?xml version="1.0" encoding="UTF-8"?>
<!-- 스키마 및 jpa 2.0로 선언 -->
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<!-- JPA 설정 -->
<!-- name -> ntityManagerFactory를 생성할 때 해당이름 사용 -->
<persistence-unit name="lionPU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <!-- JPA 구현체로 Hibernate 사용 -->
<class>org.example.jpa.User</class> <!-- JPA 관리 대상 엔티티 클래스 등록 -->
<properties>
<!-- DB 접근 -->
<property name="jakarta.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/librarydb"/>
<property name="jakarta.persistence.jdbc.user" value="spring"/>
<property name="jakarta.persistence.jdbc.password" value="spring1234"/>
<!-- Hibernate 설정 -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> <!-- MySQL 문법 지정 -->
<property name="hibernate.hbm2ddl.auto" value="update"/> <!-- 자동 스키마 반영 -->
<!--
# value 옵션 설명
* create : 기존테이블 삭제 후 새로 생성
* create-drop : create와 동일하지만, EntityManagerFactory 종료 시점에 테이블 삭제
* update : 기존테이블 유지, 변경사항만 반영 (개발시)
* validate : 엔티티와 테이블이 정상 매핑되었는지 확인만 함 (운영시)
* none : 아무 작업도 하지 않음 (기본값, 운영시)
-->
<property name="hibernate.show_sql" value="true"/> <!-- SQL 출력 -->
<property name="hibernate.format_sql" value="true"/> <!-- SQL 포맷팅 출력 -->
</properties>
</persistence-unit>
</persistence>
persistence.xml 주요 설정값
설정
설명
값
persistence-unit name
설정 그룹 이름
UserPU
transaction-type
트랜잭션 관리 방식
RESOURCE_LOCAL (직접 관리)
provider
JPA 구현체
Hibernate
hibernate.dialect
데이터베이스 방언
MySQLDialect
hibernate.hbm2ddl.auto
DDL 자동 생성 정책
update
hibernate.show_sql
SQL 로그 출력
true
hibernate.format_sql
SQL 포맷팅
true
hibernate.hbm2ddl.auto 옵션 설명
value
설명
create
기존테이블 삭제 후 새로 생성
create-drop
create와 동일하지만, EntityManagerFactory 종료 시점에 테이블 삭제
update
기존테이블 유지, 변경사항만 반영 (개발시)
validate
엔티티와 테이블이 정상 매핑되었는지 확인만 함 (운영시)
none
아무 작업도 하지 않음 (기본값, 운영시)
4. JPA - EntityManager
EntityManager ? 엔티티의 생명주기 관리, 쿼리 실행, 트랜잭션 관리 등을 담당하는 JPA의 핵심 인터페이스
EntityManager 생성
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("lionPU");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
EntityManager 주요 메서드
EntityManager em = emf.createEntityManager();
em.getTransaction().begin(); // 트랜잭션 시작
em.persist(entity);
em.find(Entity.class, id);
em.remove(entity);
em.merge(entity);
em.getTransaction().commit(); // 트랜잭션 커밋
em.close(); // EntityManager 종료
persist(Object entity) : 엔티티를 영속성 컨텍스트에 저장 (INSERT)
find(Class entityClass, Object primaryKey) : 기본 키로 엔티티 조회 (SELECT)
비영속 (New) : 엔티티 객체가 생성되었지만, 아직 영속성 컨텍스트에 저장되지 않은 상태
즉, JPA와 전혀 관계없이 객체만 생성한 상태
영속 (Managed) : 엔티티 객체가 영속성 컨텍스트에 저장되어 관리되는 상태
생성한 객체를 em.persist()를 통해 영속성 컨텍스트에 저장하거나, em.find()로 조회한 경우
준영속 (Detached) : 영속성 컨텍스트에서 분리되어 더 이상 관리되지 않는 상태
em.detach() 또는 em.clear()로 분리하거나, EntityManager가 종료된 경우
삭제 (Removed) : 엔티티 객체가 영속성 컨텍스트에서 제거된 상태
em.remove() 메서드를 호출하여 삭제된 상태
5. User 엔티티 클래스
User 엔티티 클래스 작성
@Entity // JPA의 엔티티 클래스를 나타내는 어노테이션
@NoArgsConstructor
@Getter
@Setter
@ToString
@Table(name = "jpa_user") // 해당 'jpa_user' 테이블없으면 자동생성됨
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // pk 자동생성 전략 (1부터)
private Long id;
private String name;
@Column(nullable = false) // email 컬럼 not null 제약조건 추가
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
}
-- 생성된 jpa_user 형태는 ?
CREATE TABLE jpa_user (
id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(255),
email VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
) engine=InnoDB;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
public class JpaRun {
public static void main(String[] args) {
// EntityManagerFactory ? 설정정보를 바탕으로 EntityManager를 생성하는 팩토리
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("lionPU");
// EntityManager ? DB와의 연결 담당, 실제 데이터 처리는 EntityManager가 담당
EntityManager em = emf.createEntityManager();
// 트랜잭션 시작
em.getTransaction().begin();
// 입력 - User 엔티티 생성 (비영속 상태)
User user = new User("jung5", "jung5@example.com");
System.out.println("persist 전: " + user);
// 영속 상태로 전환 - DB에 저장 (INSERT)
em.persist(user); // new -> managed (영속)
System.out.println("persist 후: " + user);
// 트랜잭션 커밋 - 실제 DB에 반영
em.getTransaction().commit();
System.out.println("-".repeat(10));
// 조회
User findUser1 = em.find(User.class, 1L); // managed (영속)
User findUser2 = em.find(User.class, 1L);
User findUser3 = em.find(User.class, 1L);
if(findUser1.equals(findUser2)) System.out.println("같음");
else System.out.println("같지않음");
if(findUser1.equals(findUser3)) System.out.println("같음");
else System.out.println("같지않음");
}
}
# 실행 결과
persist 전: User(id=null, name=jung5, email=jung5@example.com)
Hibernate:
insert
into
jpa_user
(email, name)
values
(?, ?)
persist 후: User(id=1, name=jung5, email=jung5@example.com)
----------
Hibernate:
select
u1_0.id,
u1_0.email,
u1_0.name
from
jpa_user u1_0
where
u1_0.id=?
같음
같음
엔티티 시퀀스 사용
시퀀스(Sequence) 란?
시퀀스는 DB에서 고유한 숫자를 순차적으로 생성하는 독립적인 객체
주로 기본 키(primary key) 값을 자동으로 생성하는 데 사용
시퀀스는 데이터베이스 내에서 독립적으로 관리되며, 여러 테이블에서 공유 가능
실행 해보자면 ?
// User 엔티티 클래스 ...
public class User {
@Id
// @GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
}
// run 클래스 ...
public class JpaRunIns {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("lionPU");
EntityManager em = emf.createEntityManager();
// 트랜잭션 시작
em.getTransaction().begin();
System.out.println("커밋전!!!!!");
User user = new User("jung15", "jung15@example.com");
System.out.println("persist 전: " + user);
em.persist(user);
System.out.println("persist 후: " + user);
// 트랜잭션 커밋 - 실제 DB에 반영
em.getTransaction().commit();
System.out.println("커밋후!!!!!");
}
}
# 실행 결과
# -- jpa_user가 아닌 jpa_user_SEQ 으로 생성 왜 ?
# - MySQL은 시퀀스 자체 지원 안되서 별도 독립 테이블을 만듬.
Hibernate:
create table jpa_user_SEQ (
next_val bigint
) engine=InnoDB
Hibernate:
insert into jpa_user_SEQ values ( 1 )
커밋전!!!!!
persist 전: User(id=null, name=jung15, email=jung15@example.com)
Hibernate:
select
next_val as id_val
from
jpa_user_SEQ for update
Hibernate:
update
jpa_user_SEQ
set
next_val= ?
where
next_val=?
persist 후: User(id=1, name=jung15, email=jung15@example.com)
Hibernate:
insert
into
jpa_user
(email, name, id)
values
(?, ?, ?)
커밋후!!!!!
시퀀스랑 아이덴티티 차이점
생성 시점
IDENTITY : INSERT 시점에 값이 생성
SEQUENCE : INSERT 전에 미리 값이 생성
성능
IDENTITY : 매 INSERT마다 DB와 통신 필요 (성능 저하 가능)
SEQUENCE : 미리 값을 가져올 수 있어 성능 우수
호환성
IDENTITY : MySQL, SQL Server 등에서 지원
SEQUENCE : Oracle, PostgreSQL 등에서 주로 사용
사용 용도
IDENTITY : 간단한 기본 키 생성에 적합
SEQUENCE : 복잡한 키 생성 로직에 유리 (예: 여러 테이블에서 공유)
6. IDENTITY DML 및 merge 테스트
엔티티 - Product 클래스 생성
@Entity
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Table (name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
}
persistence.xml 에 수동 Product 엔티티 추가
<class>org.example.jpa.Product</class>
DML , merge 테스트 코드 작성
public class ProductRun {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("lionPU");
// 엔티티 매니저 1
EntityManager em1 = emf.createEntityManager();
em1.getTransaction().begin();
Product p1 = new Product("사과", 9900); // new (비영속)
Product p2 = new Product("바나나", 19800);
Product p3 = new Product("딸기", 29800);
// insert
em1.persist(p1); // 이 시점부터 p1은 managed (영속)
em1.persist(p2);
em1.persist(p3);
// select
Product getP1 = em1.find(Product.class, p1.getId()); // managed (영속)
Product getP2 = em1.find(Product.class, p2.getId());
Product getP3 = em1.find(Product.class, p3.getId());
// update
getP1.setPrice(100); // 꺼내서 가능
getP2.setPrice(200);
p3.setPrice(300); // p3 이미 영속 상태로 반영 가능
// deleate
em1.remove(getP1);
System.out.println("getP2: " + getP2);
System.out.println("getP3: " + getP3);
em1.getTransaction().commit();
em1.close(); // p1,p2,p3 준영속 변경됨
// ----------------------------------------
System.out.println("-".repeat(10));
// 엔티티 매니저 2
EntityManager em2 = emf.createEntityManager();
em2.getTransaction().begin();
// 이전 준영속 엔티티 값 수정
System.out.println("수정 전 p2: " + p2);
p2.setName("샤인머스켓");
p2.setPrice(1);
Product mergeP2 = em2.merge(p2); // 수정값 반영 -> 영속전환
System.out.println("merge 후 p2: " + mergeP2);
em2.getTransaction().commit();
em2.close();
}
}
# 실행 결과
Hibernate:
create table products (
id bigint not null auto_increment,
name varchar(255),
price integer not null,
primary key (id)
) engine=InnoDB
Hibernate: # ... // insert [p1]
Hibernate: # ... // insert [p2]
Hibernate: # ... // insert [p3]
getP2: Product(id=2, name=바나나, price=200)
getP3: Product(id=3, name=딸기, price=300)
Hibernate: # ... // delete [p1]
Hibernate: # ... // commit
----------
수정 전 p2: Product(id=2, name=바나나, price=200)
Hibernate: # ... // select [p2]
merge 후 p2: Product(id=2, name=샤인머스켓, price=1)
Hibernate: # ... // commit
6. JUnit 테스트
JUnit ? 자바 프로그래밍 언어용 단위 테스트 프레임워크
JUnit은 자바 애플리케이션의 개별 단위(메서드 또는 클래스)를 테스트하는 데 사용되는 오픈 소스 프레임워크
JUnit은 테스트 주도 개발(TDD)을 지원하며, 개발자가 코드를 작성하기 전에 테스트 케이스를 먼저 작성할 수 있도록 도와줌
JUnit 테스트 해보기
테스트 할 대상 Class
main/java/org/example/junitexam/Calculator.java
package org.example.junitexam;
public class Calculator {
public int add(int a, int b) {
return a + b + 1; // 일부러 틀리게
}
public int subtract(int a, int b) {
return a - b;
}
public int multiply(int a, int b) {
return a * b;
}
public int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("0으로 나눌 수 없습니다!");
}
return a / b;
}
}
package org.example.junitexam;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*; // 클래스빼고 스태틱 메서드만 사용
// 메서드 오더 적용 (순서 비보장, @Order 명시된 것 우선)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class CalculatorTest {
Calculator calc;
@BeforeAll // 테스트 처음 시작될 때 1번
static void beforeAll() { }
@AfterAll // 모든 테스트 종료될 때 1번
static void afterAll() { }
@BeforeEach // 각 @Test 별 실행될 때
public void setUp() {
calc = new Calculator();
}
@AfterEach // 각 @Test별 종료될 때
public void tearDown() { }
@Test // 테스트 선언
@DisplayName("add() 테스트") // 테스트 명 변경
public void add() {
int result = calc.add(1,2);
assertEquals(3, result); // 예측값, 결과값
}
@Test
@DisplayName("subtract() 테스트")
public void subtract() {
int result = calc.subtract(5,2);
assertEquals(3, result);
}
@Test
@DisplayName("multiply() 테스트")
@Order(1) // 테스트 실행 순서
public void multiply() {
assertEquals(10, calc.multiply(5,2));
assertEquals(20, calc.multiply(10,2));
}
@Test
@DisplayName("divide() 테스트")
public void divide() {
assertEquals(5, calc.divide(10,2));
// 예외처리 확인시
IllegalArgumentException e = assertThrows(
IllegalArgumentException.class,
() -> calc.divide(10, 0)
);
}
}