JPA(Java Persistence API)는 자바 진영의 ORM 기술 표준으로, 자바 객체와 관계형 데이터베이스 간의 매핑을 처리하는 인터페이스 모음입니다. ORM(Object-Relational Mapping)은 객체 지향 패러다임과 관계형 데이터베이스 패러다임 간의 불일치를 해소하는 기술입니다.
JPA를 사용하면 개발자는 SQL 쿼리를 직접 작성하는 대신 자바 객체를 통해 데이터베이스를 조작할 수 있으며, 이를 통해 생산성을 높이고 유지보수성을 개선할 수 있습니다.
JPA에서는 자바 객체의 이름을 데이터베이스의 테이블과 컬럼 이름으로 변환하는 과정에서 두 가지 네이밍 전략을 사용합니다.
암시적 네이밍 전략은 명시적으로 이름을 지정하지 않은 경우 적용되는 전략입니다.
ImplicitNamingStrategyJpaCompliantImpl
SpringImplicitNamingStrategy
ImplicitNamingStrategyJpaCompliantImpl
을 상속받아 구현되어 있습니다.물리적 네이밍 전략은 암시적/명시적 네이밍 전략 이후에 적용되는 최종 변환 규칙입니다. 즉, 암시적/명시적으로 이름을 지정해도 마지막에는 물리적 네이밍 전략이 적용됩니다.
SpringPhysicalNamingStrategy
(Spring Boot 기본값)userId
→ user_id
PhysicalNamingStrategyStandardImpl
userId
→ userId
CamelCaseToUnderscoresNamingStrategy
userId
→ user_id
@Entity
어노테이션은 해당 클래스가 JPA 엔티티임을 나타냅니다.
Copy@Entity
public class User {
// 클래스 내용
}
@Table
어노테이션은 엔티티와 매핑할 테이블을 지정합니다.
Copy@Entity
@Table(name = "USERS")
public class User {
// 클래스 내용
}
주요 속성:
name
: 매핑할 테이블 이름catalog
: 데이터베이스 catalogschema
: 데이터베이스 schemauniqueConstraints
: DDL 생성 시 유니크 제약 조건 생성@Column
어노테이션은 필드와 테이블의 컬럼을 매핑합니다.
Copy@Entity
public class User {
@Id
private Long id;
@Column(name = "username", length = 100, nullable = false)
private String name;
}
주요 속성:
name
: 매핑할 컬럼 이름insertable
, updatable
: 등록, 변경 가능 여부nullable
: null 값 허용 여부unique
: 유니크 제약 조건columnDefinition
: 컬럼 정의를 직접 지정length
: 문자 길이 제약조건precision
, scale
: 숫자 자릿수 제약조건JPA에서는 기본키를 매핑하기 위해 @Id
와 @GeneratedValue
어노테이션을 사용합니다.
Copy@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 다른 필드들...
}
@GeneratedValue
의 주요 전략:
GenerationType.IDENTITY
GenerationType.SEQUENCE
@SequenceGenerator
와 함께 사용Copy@Entity
@SequenceGenerator(
name = "USER_SEQ_GENERATOR",
sequenceName = "USER_SEQ",
initialValue = 1, allocationSize = 1)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "USER_SEQ_GENERATOR")
private Long id;
}
GenerationType.TABLE
@TableGenerator
와 함께 사용GenerationType.AUTO
Enum 타입 매핑에 사용됩니다.
Copypublic enum Role {
ADMIN, USER
}
@Entity
public class Member {
@Enumerated(EnumType.STRING)
private Role role;
}
EnumType.ORDINAL
: enum 순서를 저장 (권장하지 않음)EnumType.STRING
: enum 이름을 저장 (권장)날짜 타입(Date, Calendar) 매핑에 사용됩니다. Java 8 이상의 LocalDate, LocalDateTime을 사용할 경우 생략 가능합니다.
Copy@Entity
public class Member {
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
}
TemporalType.DATE
: 날짜만 저장TemporalType.TIME
: 시간만 저장TemporalType.TIMESTAMP
: 날짜와 시간 저장데이터베이스에 저장하지 않을 필드에 사용합니다.
Copy@Entity
public class Member {
@Transient
private String tmpValue;
}
프로젝트 요구사항에 맞게 네이밍 전략을 커스터마이징할 수 있습니다. 예를 들어, 모든 테이블명과 컬럼명을 대문자 스네이크 케이스로 변환하고 싶다면 다음과 같이 구현할 수 있습니다.
Copypublic class UpperCaseNamingStrategy implements PhysicalNamingStrategy {
@Override
public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnvironment) {
return this.apply(name, jdbcEnvironment);
}
@Override
public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment) {
return this.apply(name, jdbcEnvironment);
}
@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
return this.apply(name, jdbcEnvironment);
}
@Override
public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment jdbcEnvironment) {
return this.apply(name, jdbcEnvironment);
}
@Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) {
return this.apply(name, jdbcEnvironment);
}
private Identifier apply(Identifier name, JdbcEnvironment jdbcEnvironment) {
if (name == null) {
return null;
}
StringBuilder builder = new StringBuilder(name.getText().replace('.', '_'));
for(int i = 1; i < builder.length() - 1; ++i) {
if (this.isUnderscoreRequired(builder.charAt(i - 1), builder.charAt(i), builder.charAt(i + 1))) {
builder.insert(i++, '_');
}
}
return this.getIdentifier(builder.toString(), name.isQuoted(), jdbcEnvironment);
}
protected Identifier getIdentifier(String name, boolean quoted, JdbcEnvironment jdbcEnvironment) {
if (this.isCaseInsensitive(jdbcEnvironment)) {
name = name.toUpperCase(Locale.ROOT);
}
return new Identifier(name, quoted);
}
protected boolean isCaseInsensitive(JdbcEnvironment jdbcEnvironment) {
return true;
}
private boolean isUnderscoreRequired(char before, char current, char after) {
return Character.isLowerCase(before) && Character.isUpperCase(current) && Character.isLowerCase(after);
}
}
application.yml 설정:
Copyspring:
jpa:
hibernate:
naming:
physical-strategy: com.example.config.UpperCaseNamingStrategy
JPA 네이밍 전략을 선택할 때 고려해야 할 사항들:
@Table
, @Column
으로 이름을 명시적으로 지정하는 경우를 최소화하세요.EnumType.STRING
을 사용하세요. ORDINAL
은 enum 순서 변경 시 데이터 불일치 문제가 발생할 수 있습니다.JPA의 네이밍 전략을 적절히 활용하면 자바 코드와 데이터베이스 간의 일관성을 유지하고, 개발자의 생산성을 향상시킬 수 있습니다. 프로젝트의 요구사항과 기존 데이터베이스 환경에 맞는 전략을 선택하는 것이 중요합니다.