`
Persistence Layer(영속성 계층)는 소프트웨어 아키텍처에서 애플리케이션 데이터가 데이터베이스 또는 파일 시스템과 같은 영구 저장소에 저장되고, 필요 시 이를 검색하거나 수정하는 계층을 말합니다. Persistence Layer는 비즈니스 로직과 데이터 저장소 간의 중간 다리 역할을 하며, 데이터 접근과 관리를 책임집니다.
Persistence Layer는 데이터 저장소와 애플리케이션의 다른 계층 간의 분리와 추상화를 제공합니다. 주요 역할은 다음과 같습니다:
Persistence Layer는 주로 다음 요소들로 구성됩니다:
DAO (Data Access Object)
예시:
public interface UserDao {
void saveUser(User user);
User getUserById(int id);
}
Repository
예시:
public interface UserRepository extends JpaRepository<User, Long> {}
ORM (Object-Relational Mapping)
예시 (JPA 사용):
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
}
Data Source
예시 (Spring Boot 설정):
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: user
password: pass
Transaction Management
@Transactional 어노테이션을 자주 사용.예시:
@Transactional
public void updateUser(User user) {
userRepository.save(user);
}
Persistence Layer는 다양한 기술 및 도구를 활용해 구현할 수 있습니다:
| 기술/도구 | 설명 |
|---|---|
| JDBC | Java에서 데이터베이스와 직접 상호작용하는 저수준 API. |
| Hibernate | Java의 대표적인 ORM 프레임워크. |
| JPA | Java에서 ORM을 위한 표준 인터페이스. Hibernate로 구현 가능. |
| Spring Data JPA | JPA를 더욱 간단하게 사용할 수 있는 Spring Framework 모듈. |
| MyBatis | SQL Mapper 프레임워크로, SQL과 매핑된 객체 간의 상호작용을 제공. |
| Dapper | .NET 환경에서 경량 ORM으로 사용. |
동시성 문제 해결.무결성 제약(Unique, Foreign Key 등)을 활용.public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
public UserDaoImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public User getUserById(int id) {
String sql = "SELECT * FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<>(User.class));
}
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);
}
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUserById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
}
}
데이터베이스 작업을 객체지향적으로 처리할 수 있도록 도와주며, SQL의 직접 작성 필요성을 줄이고 생산성을 높입니다.| 특징 | 설명 |
|---|---|
| ORM(Object-Relational Mapping) | 자바 객체와 데이터베이스 테이블 간의 자동 매핑을 제공. |
| 표준 인터페이스 제공 | Hibernate, EclipseLink 등 다양한 구현체에서 동작하도록 표준화된 API를 제공. |
| JPQL(Java Persistence Query Language) | SQL과 유사한 문법을 가진 객체 중심의 질의 언어. |
| 트랜잭션 관리 | 트랜잭션을 간단히 관리할 수 있는 기능을 제공. |
| 캐싱 지원 | 1차 캐시(EntityManager)와 2차 캐시(Cache Provider)를 통한 성능 최적화. |
| Lazy/Eager Loading | 데이터 로드 전략 설정 (지연 로딩 / 즉시 로딩). |
| DB 독립성 | 특정 DBMS에 종속되지 않고 다수의 데이터베이스에서 사용할 수 있음. |
EntityManager에서 관리하는 객체들의 집합.!https://velog.velcdn.com/images/gudonghee2000/post/03891a93-b797-4bd6-983a-89954382f449/image.png

1차 캐시 역할로, 동일 객체를 반복적으로 데이터베이스에서 조회하지 않음.
구현체 중 하나로, 객체지향 프로그래밍과 관계형 데이터베이스 간의 매핑을 지원하는 강력한 ORM(Object-Relational Mapping) 프레임워크입니다.| 특징 | 설명 |
|---|---|
| JPA 구현체 | JPA 표준 API를 구현하여 JPA 기능을 사용할 수 있음. |
| HQL (Hibernate Query Language) | 객체를 기반으로 한 SQL 유사 질의 언어 제공. |
| 캐싱 지원 | 1차 캐시(세션)와 2차 캐시(Ehcache, Hazelcast 등) 제공. |
| 데이터베이스 독립성 | SQL Dialect를 사용하여 다양한 데이터베이스 지원. |
| Lazy/Eager Loading | 연관된 데이터를 지연 로딩(Lazy Loading) 또는 즉시 로딩(Eager Loading) 설정 가능. |
| Auto Schema Generation | 애플리케이션 실행 시 데이터베이스 테이블 생성 및 업데이트 자동화. |
| 추상화된 데이터 접근 | 데이터 접근 코드의 중복을 줄이고 객체 중심으로 설계 가능. |
| 항목 | JPA | Hibernate |
|---|---|---|
| 표준 여부 | Java EE 표준 API | JPA의 구현체 |
| 쿼리 언어 | JPQL(Java Persistence Query Language) | HQL(Hibernate Query Language) |
| 기능 | 표준화된 기능 제공 | 캐싱, 데이터베이스 다이얼렉트 등 고유 기능 제공 |
| 사용성 | 독립적인 추상화 계층 | JPA 기능 외의 확장된 기능 사용 가능 |
| 구성 요소 | 설명 |
|---|---|
| Configuration | Hibernate 설정을 담당하며, 데이터베이스 연결 정보 및 매핑 정보를 정의. |
| Session | 데이터베이스와의 연결을 관리하며, 객체의 영속성 상태를 유지하는 인터페이스. |
| SessionFactory | 세션을 생성하는 팩토리 역할, 애플리케이션당 하나만 생성. |
| Transaction | 트랜잭션 관리 및 작업의 원자성을 보장. |
| Query | HQL 또는 SQL을 실행하는 객체. |

@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(unique = true)
private String email;
}
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = new User();
user.setName("홍길동");
user.setEmail("hong@example.com");
session.save(user);
tx.commit();
session.close();
String hql = "FROM User WHERE email = :email";
User user = session.createQuery(hql, User.class)
.setParameter("email", "hong@example.com")
.uniqueResult();
persistence.xml 설정Spring Data JPA를 사용할 때 persistence.xml은 JPA 표준 방식의 설정 파일로 사용할 수 있지만, Spring Boot에서는 보통 application.properties 또는 application.yml을 사용해 설정을 간소화합니다. 그러나 프로젝트 환경에 따라 persistence.xml 파일이 필요할 때도 있습니다.
META-INF/persistence.xml 경로에 위치해야 합니다.<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.2">
<persistence-unit name="jpabegin" transaction-type="RESOURCE_LOCAL">
<class>com.example.jpa.domain.User</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<!-- 필수 속성 -->
<property name="jakarta.persistence.jdbc.driver" value="org.mariadb.jdbc.Driver"/>
<property name="jakarta.persistence.jdbc.url" value="jdbc:mariadb://localhost:3306/sample"/>
<property name="jakarta.persistence.jdbc.user" value="root"/>
<property name="jakarta.persistence.jdbc.password" value="!123456"/>
<!-- 옵션 속성 -->
<!-- hikari pooling 환경 설정 -->
<property name="hibernate.hikari.poolName" value="pool"/>
<property name="hibernate.hikari.maximumPoolSize" value="10"/>
<property name="hibernate.hikari.maximumIdle" value="10"/>
<property name="hibernate.hikari.connectionTimeout" value="1000"/>
<!-- JPA 기본 환경 : 기본값 -->
</properties>
</persistence-unit>
</persistence>
application.properties (Spring Boot 사용 시)spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/sample
spring.datasource.username=root
spring.datasource.password=!123456
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

Spring Data JPA는 Spring Framework의 데이터 접근 계층 모듈로, JPA(Java Persistence API)를 더 쉽고 간단하게 사용할 수 있도록 추상화한 도구입니다. 기존 JPA를 사용할 때 반복적인 코드를 줄이고, Repository 인터페이스와 간단한 메서드 이름만으로 CRUD, 페이징, 정렬 등을 쉽게 구현할 수 있습니다.
!https://kha0213.github.io/assets/images/study/spring-data-jpa-ex.png
| 특징 | 설명 |
|---|---|
| 간단한 데이터 접근 | JpaRepository 인터페이스를 통해 CRUD와 페이징, 정렬을 자동화. |
| 쿼리 메서드 | 메서드 이름만으로 동작하는 쿼리를 자동 생성. |
| JPQL 및 네이티브 SQL 지원 | 복잡한 쿼리의 경우 JPQL(Java Persistence Query Language) 또는 네이티브 SQL을 사용 가능. |
| 페이징 및 정렬 지원 | 페이징(Pageable)과 정렬(Sort)을 기본 제공. |
| JPA 구현체 독립성 | Hibernate, EclipseLink 등 다양한 JPA 구현체와 호환 가능. |
| 트랜잭션 관리 통합 | Spring의 @Transactional을 통해 트랜잭션을 간단히 관리 가능. |

# MariaDB setting
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/sample
spring.datasource.username=root
spring.datasource.password=!123456
# Hibernate setting
spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect
SQL 방언(Dialect)을 지정합니다.MariaDBDialect는 MariaDB 데이터베이스와 호환되는 SQL을 생성하기 위한 Hibernate 방언입니다.org.hibernate.dialect.MySQL8Dialectorg.hibernate.dialect.PostgreSQLDialectorg.hibernate.dialect.Oracle12cDialectspring.jpa.hibernate.ddl-auto=create데이터베이스 테이블과 엔티티 간의 매핑을 어떻게 처리할지를 결정합니다.spring.jpa.hibernate.ddl-auto:none: 아무 작업도 하지 않음. 운영 환경에서 사용 권장. 즉, 직접 table을 생성해서 사용함.validate: 스키마를 검증하지만 변경하지 않음.update: 기존 스키마를 유지하며 `필요한 경우 업데이트.create: 기존 테이블 삭제 후 새로 생성.create-drop: create처럼 테이블 생성 후, 애플리케이션 종료 시 삭제.update 또는 createvalidate 또는 none| 설정 값 | 설명 | 적용 시점 및 환경 |
|---|---|---|
none | Hibernate가 DDL 작업을 하지 않음. | - 운영 환경에서 추천. |
| - 스키마는 수동으로 관리되며, Hibernate가 변경하지 않음. | ||
validate | 엔티티와 데이터베이스 테이블의 스키마 일치 여부를 검증 (변경은 하지 않음). | - 운영 환경에서 권장. |
| - 애플리케이션 배포 전 스키마가 엔티티와 일치하는지 확인 가능. | ||
update | 기존 스키마를 유지하면서 필요한 경우 변경. | - 개발 환경에서 유용. |
| - 반복적으로 스키마를 초기화할 필요가 없는 경우. | ||
create | 기존 스키마를 삭제하고 새로 생성. | - 초기 개발 환경에서 추천. |
| - 데이터가 없어도 되는 경우. | ||
create-drop | 애플리케이션 종료 시 생성된 테이블을 삭제. | - 테스트 환경에서 유용. |
| - 데이터베이스 상태를 매번 초기화해야 할 때. |
create, create-drop 사용을 지양해야 합니다.validate나 변경을 하지 않는 none을 사용하는 것이 일반적입니다.spring.jpa.show-sql=truelogging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
spring.jpa.show-sql: SQL 쿼리를 출력.spring.jpa.properties.hibernate.format_sql=true: 출력되는 SQL을 포맷팅하여 보기 좋게 만듦.logging.level.org.hibernate.SQL=DEBUG: SQL 로그 레벨 설정.logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE: 쿼리 바인딩 값 출력.@GeneratedValue 전략@GeneratedValue는 JPA에서 기본 키 값을 자동으로 생성하는 데 사용되며, 키 생성 전략을 결정하는 4가지 옵션을 제공합니다:GenerationType.IDENTITYGenerationType.SEQUENCEGenerationType.TABLEGenerationType.AUTO@GeneratedValue(strategy = GenerationType.IDENTITY)MariaDB 기본 설정:
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Other fields and methods...
}
CREATE TABLE member (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
);
@GeneratedValue(strategy = GenerationType.SEQUENCE)MariaDB에서 Sequence 사용 설정:
CREATE SEQUENCE member_seq
START WITH 1
INCREMENT BY 1;
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_seq_gen")
@SequenceGenerator(name = "member_seq_gen", sequenceName = "member_seq", allocationSize = 1)
private Long id;
private String name;
// Other fields and methods...
}
설명:
@SequenceGenerator:name: 사용할 Sequence Generator의 이름.sequenceName: 실제 데이터베이스 Sequence의 이름.allocationSize: 키 값을 미리 생성해 메모리에 로드하는 크기(성능 최적화에 도움).INSERT INTO member (id, name) VALUES (NEXT VALUE FOR member_seq, '홍길동');
| 항목 | IDENTITY | SEQUENCE |
|---|---|---|
| MariaDB 지원 여부 | 기본 지원 (AUTO_INCREMENT) | 10.3 이상에서 수동으로 Sequence 생성 필요 |
| 키 생성 방식 | 테이블마다 AUTO_INCREMENT | 데이터베이스의 Sequence 객체 |
| 배치 처리 | 비효율적 (INSERT 후 키 값 생성됨) | 효율적 (키를 미리 생성 가능) |
| 트랜잭션과의 통합 | 트랜잭션과 독립적 | 트랜잭션과 통합 가능 |
| 성능 최적화 | 기본적이지만 확장성 부족 | allocationSize로 키 생성 성능 최적화 가능 |
@GeneratedValue(strategy = GenerationType.IDENTITY)를 선택하세요.설정이 간단하지만 배치 작업 성능이 떨어질 수 있습니다.@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
MariaDB에서 Sequence 생성 후:
CREATE SEQUENCE member_seq
START WITH 1
INCREMENT BY 1;
JPA 설정:
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_seq_gen")
@SequenceGenerator(name = "member_seq_gen", sequenceName = "member_seq", allocationSize = 1)
private Long id;
private String name;
}
@Column 애너테이션은 JPA 엔티티 필드를 데이터베이스 컬럼과 매핑할 때 사용하는 애너테이션입니다. 기본적으로 엔티티의 필드 이름과 동일한 컬럼 이름으로 매핑되지만, 세부적인 설정을 통해 컬럼 속성을 지정할 수 있습니다.
@Column의 주요 속성| 속성 | 설명 | 기본값 |
|---|---|---|
name | 매핑할 데이터베이스 컬럼 이름 | 필드 이름과 동일 |
nullable | 컬럼에 NULL 값 허용 여부 | true |
unique | 해당 컬럼에 유니크 제약 조건을 설정 | false |
length | 문자열 컬럼의 길이 설정 (VARCHAR와 CHAR에만 적용) | 255 |
precision | BigDecimal 타입에서 전체 자리수 설정 (숫자형에만 적용) | 0 |
scale | BigDecimal 타입에서 소수점 자리수 설정 | 0 |
insertable | INSERT 쿼리에 포함 여부 | true |
updatable | UPDATE 쿼리에 포함 여부 | true |
columnDefinition | DDL 생성 시 컬럼의 데이터 타입이나 제약 조건을 직접 명시 | 자동 생성된 값 |
table | 해당 컬럼이 매핑될 테이블 이름 (멀티 테이블 매핑에 사용) | 기본 테이블 이름 |
name컬럼 이름을 엔티티 필드 이름과 다르게 설정:
@Column(name = "user_name")
private String name;
name은 데이터베이스 컬럼 user_name으로 매핑됩니다.nullableNULL 허용 여부 설정:
@Column(nullable = false)
private String email;
nullable = false는 DDL 생성 시 NOT NULL 제약 조건을 추가합니다.unique유니크 제약 조건 설정:
@Column(unique = true)
private String username;
unique = true는 DDL에서 유니크 제약 조건을 생성하지만, 여러 컬럼을 조합한 유니크 제약 조건은 @Table의 uniqueConstraints를 사용해야 합니다.length문자열 필드 길이 제한:
@Column(length = 50)
private String firstName;
length = 50은 DDL에서 해당 컬럼의 길이를 VARCHAR(50)으로 생성합니다.precision과 scaleBigDecimal 또는 숫자형 필드의 전체 자리수와 소수점 자리수 설정:
@Column(precision = 10, scale = 2)
private BigDecimal price;
precision = 10, scale = 2는 최대 자리수 10, 소수점 아래 2자리로 제한합니다.12345678.91은 허용되지만 123456789.91은 허용되지 않습니다.insertable와 updatable컬럼이 INSERT 또는 UPDATE 쿼리에 포함될지 여부 설정:
@Column(insertable = false, updatable = false)
private String createdAt;
insertable = false: INSERT 쿼리에서 제외.updatable = false: UPDATE 쿼리에서 제외.columnDefinitionDDL 생성 시 데이터 타입과 제약 조건 명시:
@Column(columnDefinition = "TEXT NOT NULL")
private String description;
table컬럼이 특정 테이블에 매핑되도록 설정:
@Column(table = "user_details")
private String address;
@Column은 Hibernate가 DDL을 자동 생성할 때만 유효합니다.ddl-auto=none)하고, 스키마 관리 도구(Flyway, Liquibase)를 사용하는 것이 안전합니다.nullable과 애플리케이션 유효성 검사nullable = false는 데이터베이스 제약 조건만 추가하며, 애플리케이션의 입력값 유효성 검사는 별도로 구현해야 합니다.@NotNull 같은 애너테이션을 함께 사용하세요.unique = true로 설정 가능하지만, 다중 컬럼 조합 유니크 제약 조건은 @Table의 uniqueConstraints를 사용해야 합니다:@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"username", "email"}))
columnDefinition 사용 주의precision과 scaleTEXT, BLOB)는 인덱스 생성에 제약이 있을 수 있으므로 적절히 설계해야 합니다.@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_name", nullable = false, length = 50, unique = true)
private String username;
@Column(nullable = false)
private String email;
@Column(precision = 10, scale = 2)
private BigDecimal balance;
@Column(insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private LocalDateTime createdAt;
@Transient
private String tempData; // 매핑하지 않을 필드
}
_로 구분하며, 모든 문자를 소문자로 작성합니다.first_name, created_atfirstName, createdAtfirst_name → 컬럼 firstName(Camel Case)로 매핑 시 문제 발생.first_name(Snake Case)로 설정된 경우가 일반적.database와 enitity 사이를 hibernate가 mapping할 때 naming 전략을 설정해줄 수도 있습니다.
jpa:
hibernate:
naming:
physical-strategy:
JPA의 @Column 애너테이션으로 컬럼 이름을 명시하면 Snake Case와 Camel Case를 명확히 구분할 수 있습니다.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "created_at")
private LocalDateTime createdAt;
}
Column (Java(TM) EE 7 Specification APIs)
Column (Jakarta EE Platform API)
| 애너테이션 | 설명 | 주요 속성 | 예시 |
|---|---|---|---|
@Entity | 클래스를 JPA 엔티티로 선언. | 없음 | @Entity public class User { } |
@Table | 엔티티와 데이터베이스 테이블을 매핑. | name(테이블 이름), schema, uniqueConstraints | @Table(name = "users", schema = "public") |
@Entity기본 생성자(파라미터 없는 생성자)가 있어야 합니다.@Entity
public class User {
// 엔티티 클래스
}
@Tablename: 테이블 이름schema: 스키마 이름catalog: 카탈로그 이름uniqueConstraints: 테이블에 대한 유니크 제약 조건 설정@Entity
@Table(name = "users", schema = "public")
public class User {
// 테이블 users에 매핑
}
| 애너테이션 | 설명 | 주요 속성 | 예시 |
|---|---|---|---|
@Id | 기본 키(Primary Key)를 지정. | 없음 | @Id private Long id; |
@GeneratedValue | 기본 키 생성 전략 지정. | strategy(키 생성 방식) | @GeneratedValue(strategy = GenerationType.IDENTITY) |
@Column | 필드와 테이블 컬럼 매핑. | name, nullable, unique, length | @Column(name = "user_name", nullable = false, length = 100) |
@Lob | 대용량 데이터(BLOB, CLOB) 매핑. | 없음 | @Lob private String description; |
@Temporal | 날짜/시간 데이터 매핑. | TemporalType(DATE, TIME, TIMESTAMP) | @Temporal(TemporalType.TIMESTAMP) private Date createdAt; |
@Enumerated | Enum 타입 매핑. | EnumType(ORDINAL, STRING) | @Enumerated(EnumType.STRING) private UserRole role; |
@Transient | 필드를 데이터베이스에 매핑하지 않음. | 없음 | @Transient private String tempData; |
@Id@Id
private Long id;
@GeneratedValueGenerationType.IDENTITY: 데이터베이스에 기본 키 생성을 위임.GenerationType.SEQUENCE: 데이터베이스의 시퀀스를 사용.GenerationType.TABLE: 별도의 키 생성용 테이블을 사용.GenerationType.AUTO: 데이터베이스 방언에 맞는 전략을 자동으로 선택.@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Columnname: 컬럼 이름nullable: null 허용 여부unique: 유니크 제약 조건length: 문자열 길이precision, scale: 숫자 데이터 타입의 소수점 자릿수 정의@Column(name = "user_name", nullable = false, length = 100)
private String name;
@Lob@Lob
private String description;
@TemporalTemporalType.DATE: 날짜만 저장 (yyyy-MM-dd).TemporalType.TIME: 시간만 저장 (HH:mm:ss).TemporalType.TIMESTAMP: 날짜와 시간 저장 (yyyy-MM-dd HH:mm:ss).@Temporal(TemporalType.TIMESTAMP)
private Date createdAt;
@EnumeratedEnumType.ORDINAL: Enum의 순서값 저장.EnumType.STRING: Enum 이름을 문자열로 저장(권장).@Enumerated(EnumType.STRING)
private UserRole role;
@Transient@Transient
private String tempData;
| 애너테이션 | 설명 | 주요 속성 | 예시 |
|---|---|---|---|
@OneToOne | 엔티티 간 1:1 관계 매핑. | mappedBy, cascade, fetch | @OneToOne @JoinColumn(name = "profile_id") private UserProfile profile; |
@OneToMany | 엔티티 간 1:N 관계 매핑. | mappedBy, cascade, fetch | @OneToMany(mappedBy = "department", cascade = CascadeType.ALL) private List<Employee> employees; |
@ManyToOne | 엔티티 간 N:1 관계 매핑. | cascade, fetch | @ManyToOne @JoinColumn(name = "department_id") private Department department; |
@ManyToMany | 엔티티 간 N:M 관계 매핑. | mappedBy, cascade, fetch | @ManyToMany @JoinTable(name = "student_course") private List<Course> courses; |
@JoinColumn | 외래 키를 매핑. | name, referencedColumnName | @JoinColumn(name = "department_id") |
@JoinTable | 중간 테이블을 정의. | name, joinColumns, inverseJoinColumns | @JoinTable(name = "student_course") |
@OneToOne@JoinColumn으로 외래 키를 지정합니다.@OneToOne
@JoinColumn(name = "profile_id")
private UserProfile profile;
@OneToMany@OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
private List<Employee> employees;
@ManyToOne@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
@ManyToMany@JoinTable)을 통해 매핑합니다.@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private List<Course> courses;
| 애너테이션 | 설명 | 주요 속성 | 예시 |
|---|---|---|---|
@MappedSuperclass | 공통 엔티티 속성을 상속받도록 정의. | 없음 | @MappedSuperclass public abstract class BaseEntity { } |
@Embeddable | 값 타입 클래스를 정의. | 없음 | @Embeddable public class Address { } |
@Embedded | 값 타입 필드를 엔티티에 포함. | 없음 | @Embedded private Address address; |
@Inheritance | 상속 매핑 전략 정의. | strategy(SINGLE_TABLE, JOINED, TABLE_PER_CLASS) | @Inheritance(strategy = InheritanceType.JOINED) |
@MappedSuperclass별도의 테이블이 생성되지 않음.@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Temporal(TemporalType.TIMESTAMP)
private Date createdAt;
}
@Embeddable / @Embedded@Embeddable: 내장될 클래스에 사용.@Embedded: 내장 필드에 사용.@Embeddable
public class Address {
private String city;
private String street;
}
@Entity
public class User {
@Embedded
private Address address;
}
즉, Entity 내부의 속성 값을 더 응집시켜 객체로 데이터를 표현합니다. @Embeddable이 붙은 class 자체가 또 다른 entity를 생성하는 것이 아니라, 위 class를 예시로 들면, User entity에 존재하는 city와 street이라는 각각의 attribute 들을 spring 측에서 객체화해 사용할 수 있습니다. @Embeddable이 붙으면 database에 해당 class가 별도의 enity가 아님을 알릴 수 있습니다.
@InheritanceInheritanceType.SINGLE_TABLE: 단일 테이블 전략(기본값).InheritanceType.JOINED: 정규화된 테이블 전략.InheritanceType.TABLE_PER_CLASS: 클래스마다 테이블 생성.@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
| 애너테이션 | 설명 | 주요 속성 | 예시 |
|---|---|---|---|
@Version | 낙관적 락(Optimistic Lock)을 위한 버전 관리. | 없음 | @Version private int version; |
@Access | 필드 또는 프로퍼티 접근 방식 지정. | AccessType(FIELD, PROPERTY) | @Access(AccessType.FIELD) |
@Version@Version
private int version;
여러 개의 트랜잭션이 동시에 접근했을 때, 데이터의 일관성이 깨질 수 있기 때문에 락(잠금)을 걸고 이를 관리하는 것을 Locking이라고 합니다.
예를 들어, 3000원이 있는 계좌에 A와 B가 3000원을 입금했는데 이 과정이 동시에 이루어져서 A와 B가 동시에 3000원을 읽어서 3000원을 더해버리는 바람에 최종 금액이 9000원이 아니라 6000원이 되버리는 현상을 막기 위해 필요합니다.
실제 서비스를 운영하다 보면, 이러한 데이터베이스 고립에 있어서 서로 다른 트랜잭션 간 접근 문제로 데이터의 일관성 상호배제성이 위반되는 경유가 많이 있을 것 같습니다.
자원에 락을 걸지 않고, 동시성 문제가 발생하면 그때 처리 합니다.

낙관적인 락은 Version을 사용해 관리합니다.
낙관적 락은 데이터베이스가 제공하는 락이 아닌 애플리케이션 레벨에서 락을 구현하게 됩니다. JPA에서는 버전 관리 기능(@Version)을 통해 구현할 수 있습니다.
JPA는 낙관적 락을 위해 @Version 어노테이션을 제공하고 있습니다. 해당 어노테이션이 붙은 필드를 포함하는 엔티티를 정의하면, 해당 엔티티 테이블을 읽는 각 트랜잭션은 업데이트를 수행하기 전에 버전의 속성을 확인하게 됩니다. 만약 데이터를 읽고 업데이트를 하기 이전에 버전 값이 변경되어있다면 OptimisticLockException을 발생 시키며 해당 업데이트를 취소합니다.
@Version 필드를 선언할 때에는 아래의 규칙을 따라야 합니다.
비관적인 락은 데이터를 읽을 때부터 락을 걸고, 업데이트가 완료될 때까지 락을 유지하는 방식입니다. 기본적으로 데이터 갱신시 충돌이 발생할 것 으로 보고 미리 잠금을 하는 방식을 Pessimistic Lock(비관적인 락)이라고 합니다.
데이터를 읽을 때 락을 걸기 때문에 다른 트랜잭션이 해당 데이터를 읽거나 수정할 수 없습니다.
비관적인 락은 데이터 충돌을 최소화하지만, 동시성이 낮아질 수 있고, 락 경합이 발생할 경우 성능 저하가 발생할 수 있습니다.

무결성에 장점이 있지만 데드락의 위험성이 존재합니다.
@Accessjpa가 엔티티에 접근하는 방식을 지정해주는 어노테이션입니다.
객체에 접근하기 위한 방법으로 간단히 2가지 방법이 있습니다.
예를 들어 User라는 객체가 있고, name이라는 속성을 가지고 있다고 가정해보겠습니다.
1번 방법을 필드접근이라 하고, 2번 방법을 프로퍼티 접근이라고 합니다.
@Access가 설정되어 있지 않은 경우 @Id annotation의 위치를 보고 파악합니다.
@Id annotation이 필드에 붙어있으면 필드접근, getter method에 붙어있으면 프로퍼티 접근입니다.
@Access(AccessType.FIELD)
private String name;
@Access(AccessType.PROPERTY)
private String name;
1차 캐시로 동작하며, 데이터베이스에서 조회된 엔티티를 캐시에 저장합니다.캐시된 엔티티를 반환합니다.flush() 또는 commit 시점에 일괄 실행합니다.flush()는 영속성 컨텍스트의 내용을 데이터베이스와 동기화하는 역할을 합니다. 하지만 트랜잭션이 끝나는 시점에 commit과 함께 동작하므로, 즉시 데이터베이스에 저장하지는 않습니다.
commit 시점에 자동으로 호출됩니다.저장소에 쌓인 SQL을 데이터베이스에 전달하여 실행합니다.| 메서드 | 설명 | 실행 시점 |
|---|---|---|
persist() | 엔티티를 영속성 컨텍스트에 저장합니다. | 즉시 (쓰기 지연 저장소에 저장) |
flush() | 영속성 컨텍스트의 내용을 데이터베이스와 동기화합니다. | 명시적 호출 또는 트랜잭션 commit 시점 |
commit() | 트랜잭션을 커밋하고 데이터베이스 변경 사항을 확정합니다. | 트랜잭션 종료 시점 |
clear() | 영속성 컨텍스트를 초기화하여 관리 중인 모든 엔티티를 분리(detach)합니다. | 즉시 실행 |
detach() | 특정 엔티티를 영속성 컨텍스트에서 분리합니다. | 즉시 실행 |
merge() | 준영속(detached) 상태의 엔티티를 다시 영속성 컨텍스트에 병합합니다. | 즉시 실행 |
엔티티는 JPA 환경에서 4가지 상태로 구분됩니다.
| 상태 | 설명 | 특징 |
|---|---|---|
| 비영속(new) | - 영속성 컨텍스트와 관계없는 상태. | |
- 단순히 new로 생성된 객체. | 데이터베이스와 전혀 연결되지 않음. | |
| 영속(managed) | - 영속성 컨텍스트에 등록된 상태. | |
| - EntityManager가 관리 중. | 변경 사항이 자동으로 데이터베이스에 반영됨 (flush 시). | |
| 준영속(detached) | - 영속성 컨텍스트에서 분리된 상태. | |
| - EntityManager가 더 이상 관리하지 않음. | 변경 사항이 더 이상 데이터베이스에 반영되지 않음. | |
| 삭제(removed) | - 영속성 컨텍스트에서 제거된 상태. | |
| - 데이터베이스에서 삭제될 예정. | EntityManager.remove() 호출 시 이 상태로 전환됨. |
EntityManager.persist(entity) 호출 → 영속 상태로 전환.EntityManager.find() 또는 JPQL 실행 시 영속성 컨텍스트에서 먼저 조회.EntityManager.update() 대신 변경 감지(Dirty Checking)가 자동으로 수행.flush() 호출 → 데이터베이스에 UPDATE SQL 전송.EntityManager.remove(entity) 호출 → 삭제 상태로 전환.데이터베이스를 반복적으로 호출하지 않음.메모리에서 바로 데이터를 조회.일괄적으로 SQL 실행.영속성 컨텍스트를 JPA의 내부 메모리 저장소로 생각할 수 있습니다.
JPA에서 엔티티는 다음과 같은 네 가지 상태를 가집니다:
new 키워드로 생성된 엔티티 객체가 여기에 해당합니다.EntityManager.persist()find() 또는 JPQL 쿼리로 조회된 엔티티도 이 상태에 해당합니다.persist(), find(), JPQL, flush()detach(), clear(), close() 호출로 전환됩니다.merge()EntityManager.remove() 호출로 삭제 상태가 됩니다.flush()를 통해 데이터베이스에 반영됩니다.각 상태 간 전환을 다음과 같은 메서드로 나타냅니다:
EntityManager.persist(entity) 메서드 호출.EntityManager.detach(entity)를 호출하여 특정 엔티티를 영속성 컨텍스트에서 제거.EntityManager.clear()로 전체 컨텍스트 초기화.EntityManager.close()로 영속성 컨텍스트 종료.EntityManager.merge(entity) 호출.EntityManager.remove(entity) 호출.flush() 호출 시 데이터베이스에서 삭제.EntityManager.flush()를 호출하거나 트랜잭션 커밋 시점에 자동 호출.@Entity @Getter @Setter
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// 기본 생성자
public Member() {}
// 생성자
public Member(String name, String email) {
this.name = name;
this.email = email;
}
}
import jakarta.persistence.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MemberService {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void demonstrateEntityLifecycle() {
// 1. 비영속 상태 (Transient)
// 영속성 컨텍스트와 전혀 연관되지 않은 상태
Member member = new Member("홍길동", "hong@example.com");
System.out.println("1. 비영속 상태: " + member);
// 2. 영속 상태 (Managed)
// 엔티티를 persist()로 영속성 컨텍스트에 저장
entityManager.persist(member);
System.out.println("2. 영속 상태: ID가 아직 할당되지 않음 -> ID는 flush 시점에 할당됨");
// flush()는 SQL 저장소에 쌓인 쿼리를 실행 (INSERT 발생)
entityManager.flush();
System.out.println("영속 상태 이후: ID = " + member.getId());
// 3. 변경 감지 (Dirty Checking)
// 영속 상태의 엔티티 값을 변경 -> 자동으로 UPDATE 쿼리가 생성됨
member.setName("강감찬");
System.out.println("3. 변경 감지: 이름 변경 -> 강감찬");
entityManager.flush(); // UPDATE SQL 실행
// 4. 준영속 상태 (Detached)
// 엔티티를 영속성 컨텍스트에서 분리(detach)
entityManager.detach(member);
System.out.println("4. 준영속 상태: 더 이상 변경 감지되지 않음");
member.setName("이순신"); // 변경하더라도 UPDATE SQL 실행되지 않음
entityManager.flush(); // SQL 실행 안 됨
// 5. 삭제 상태 (Removed)
// 엔티티를 영속성 컨텍스트에서 제거
entityManager.merge(member); // 다시 영속 상태로 전환
entityManager.remove(member); // 삭제 예약
System.out.println("5. 삭제 상태: DELETE SQL 실행 예정");
entityManager.flush(); // DELETE SQL 실행
// 6. 트랜잭션 종료 // commit 시점에 flush()가 자동으로 호출됨
System.out.println("6. 트랜잭션 종료: 데이터베이스에 변경 사항 반영 완료");
}
}