본문 바로가기

개발/쉽게 이해하고 사용하는 JPA

JPA 기본 사용 문법 파악하기

@Entity

@Entity 어노테이션은 데이타베이스의 테이블과 일대일로 매칭되는 객체 단위이며 Entity 객체의 인스턴스 하나가 테이블에서 하나의 레코드 값을 의미합니다. 그래서 객체의 인스턴스를 구분하기 위한 유일한 키값을 가지는데 이것은 테이블 상의 Primary Key 와 같은 의미를 가지며 @Id 어노테이션으로 표기 됩니다.

먼저 Spring Boot 를 설정할때 spring.jpa.hibernate.ddl-auto 설정이 create 혹은 update 로 되어 있을 경우 Spring 프로젝트가 시작될때 EntityManager 가 자동으로 DDL 을 수행해 테이블을 생성해 줍니다.

이때 명시적으로 @Entityname 속성을 이용해 데이타베이스상의 실제 테이블 명칭을 지정하지 않는다면 Entity 클래스의 이름 그대로 CamelCase 를 유지한채 테이블이 생성이 되기 때문에 테이블 이름을 명시적으로 작성하는 것이 관례입니다. 왜냐하면 데이타베이스상에서 보편적으로 사용 되는 명명법은 UnderScore 가 원칙이기 때문입니다.

신규로 구축되는 사이트의 경우에는 CamelCase 형태의 이름을 가지는 테이블을 사용해도 무방 하겠지만 기존에 이미 테이블이 설계되어 있는 사이트에 JPA 를 적용하거나 다른 개발자 혹은 DBA 와 커뮤니케이션을 위해서라도 가급적이면 UnderScore 를 이용한 테이블 명명 규칙을 이용하는 것이 좋습니다.

@Entity
public class Organization {
    ...
}

@Entity(name = "employee")
public class Employee {
    ...
}

@Entity(name = "employee_address")
public class EmployeeAddress {
    ...
}

 

@Column

@Column 어노테이션은 데이타베이스의 테이블에 있는 컬럼과 동일하게 1:1로 매칭되기 때문에 Entity 클래스안에 내부변수로 정의 됩니다. 만약 테이블에 a, b, c 컬럼이 있다면 각각 3개의 @Column 어노테이션을 작성 하게 됩니다. 다만 이때 의도적으로 필요없는 컬럼들은 작성하지 않아도 되는데 데이타베이스 테이블에 실제 a, b, c, d 총 4개의 컬럼이 있더라도 a,b,c 컬럼만 Entity 클래스에 작성해도 무방 하다는 이야기 입니다.

이때 @Column 어노테이션은 별다른 옵션을 설정하지 않는다면 생략이 가능합니다. 즉 Entity 클래스에 정의된 모든 내부변수는기본적으로 @Column 어노테이션을 포함한다고 볼 수 있습니다.

Spring Boot 의 spring.jpa.hibernate.ddl-auto 설정이 create 혹은 update 로 되어 있을 경우 create 일때는 최초에 한번 컬럼이 생성이 되고, update 일때는 Entity 클래스에 있지만 해당 테이블에 존재하지 않는 컬럼을 추가로 생성해 줍니다. 하지만 컬럼의 데이타 타입이 변경 되었거나 길이가 변경 되었을때 자동으로 데이타베이스에 반영을 해주지는 않기 때문에 속성이 변경되면 기존 테이블을 drop 후 새롭게 create 하던지 개별로 alter table 을 통해 직접 ddl 구문을 적용하는 것이 좋습니다.

spring.jpa.hibernate.ddl-auto 설정이 create-drop 로 되어 있으면 프로젝트가 시작될때 자동으로 기존 테이블을 drop 한 후 create 를 해줍니다. 하지만 기존 스키마가 전부 삭제 되기 때문에 시스템 설계와 개발 시점에만 사용해야 하며 운영 시점에 create, update, create-drop 을 사용 하지 않아야 합니다.

@Column@Entity 어노테이션과 동일하게 name 속성을 명시하지 않으면 Entity 클래스에 정의한 컬럼 변수의 이름으로 생성이 됩니다. 그렇기 때문에 CamelCase 로 작성된 컬럼 변수가 있다면 UnderScore 형식으로 name 을 명시적으로 작성 합니다.

데이타베이스상에서 컬럼은 실제 데이타가 가질 수 있는 최대 길이를 가지게 되는데 이것은 데이타베이스에 데이타를 효율적으로 관리하기 위해서 입니다. @Column 에도 이처럼 length 속성으로 길이를 명시 할 수 있습니다. 만약 length 속성이 없다면 기본 길이인 255가 지정 됩니다. 이것은 문자열 형태인 데이타 속성에만 해당 되며 큰 숫자를 표현하는 BigDecimal 일 경우 precision, scale 로 최대 길이를 지정 할 수 있습니다.

@Column 
private String code;

@Column(length = 100)
private String name;

//@Column 은 생략이 가능합니다.
private String description; 

@Column(precision = 11, scale = 2)
private BigDecimal amount;

@Column
private Integer size;

@Column(name ="register_date")
private LocalDateTime registerDate;

 

@Id

데이타베이스의 테이블은 기본적으로 유일한 값을 가집니다. 그것을 PK (Primary Key) 라고 하는데 데이타베이스는 이 유일한 키값을 기준으로 질의한 데이타를 추출해 결과셋으로 반환해 줍니다. 테이블 상에 PK 가 없는 테이블도 있지만 대부분의 경우 반드시 PK 가 존재합니다.

JPA 에서도 Entity 클래스 상에 해당 PK 를 명시적으로 표시를 해야 되는데 그것을 @Id 어노테이션을 이용해 이것이 PK 임을 지정 합니다.

만약 Spring Boot 의 spring.jpa.hibernate.ddl-auto 속성이 create 로 되어 있고 아직 해당 테이블이 데이타베이스상에 존재하지 않는다면 EntityManager 가 DDL 을 통해 테이블을 생성하면서 PK 를 같이 생성해 줍니다.

@Id
@Column
private String code;

 

@GeneratedValue

PK 컬럼의 데이타 형식은 정해져 있지는 않으나 구분이 가능한 유일한 값을 가지고 있어야 하고 데이타 경합으로 인해 발생되는 데드락 같은 현상을 방지 하기 위해 대부분 BigInterger 즉 Java 의 Long 을 주로 사용합니다.

물론 String 형태의 고정된 키값을 직접 생성해서 관리하기도 합니다. 중요한 것은 대량의 요청이 유입 되더라도 중복과 deadlock 데드락이 발생 되지 않을 만큼 키값이 빨리 생성이 되고 안전하게 관리 되어야 한다는 점입니다.

deadlock

동일한 시점에 요청이 유입 되었을때 데이타베이스는 테이블 혹은 레코드를 lock 을 걸어 데이타가 변경되지 않도록 막아 놓고 다른 작업을 진행합니다.

이때 1번째 요청이 A 테이블의 값을 변경하고 lock 을 걸어둔 상태에서 B 테이블을 사용하려고 하고, 2번째 요청이 B 테이블의 값을 변경하고 lock 을 걸어둔 상태에서 A 테이블을 사용하려고 할때 데이타베이스는 우선순위를 판단 할 수 없어 그대로 교착상태에 빠져 버립니다.

이때는 어쩔 수 없이 강제로 시스템을 재시작하여 데이타베이스 커낵션을 초기화 시켜줘야 합니다.

 

가장 보편적으로 사용이 되는 데이타베이스인 MySQL, ORACLE 에는 Long 타입의 키값을 생성하는 방식이 서로 조금 다른데요. MySQL 은 auto increment 방식을 ORACLE 은 sequence 방식을 사용합니다.

- auto increment

먼저 MySQL 의 auto increment 방식은 숫자형의 PK 컬럼 속성을 auto increment 로 지정하면 자동으로 새로운 레코드가 생성이 될때마다 마지막 PK 값에서 자동으로 +1 을 해주는 방식입니다. 이를 위해 @GeneratedValue 어노테이션의 strategy 속성을 GenerationType.IDENTITY 로 지정해 auto increment 컬럼인 것을 EntityManager 에 알려 줍니다.

이때 자동으로 생성되는 값을 가지는 PK 컬럼의 이름은 명시적으로 id 로 지정하는 것이 관례 입니다.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

 

- sequence

ORACLE 에서 사용되는 sequence 방식은 sequence ORACLE 객체를 생성해 두고 해당 sequence 를 호출할때마다 기존 값의 +1 이 된 값을 반환해 주는 방식입니다. 이를 위해 @GeneratedValue 어노테이션의 strategy 속성을 GenerationType.SEQUENCE 로 지정해 sequence 를 사용해 PK 값을 사용하겠다고 지정합니다.

@Id
@SequenceGenerator(name="seq", sequenceName="jpa_sequence")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
private Long id;

 

@EmbeddedId

앞서 데이타베이스의 테이블은 기본적으로 유일한 값을 가지는데 그것을 PK 라고 이야기 한다고 설명 드렸습니다. 일반적인 경우에는 PK 를 단일 @Id 로 구성하지만 경우에 따라선 복합키로서 테이블의 PK 를 정의 하기도 합니다. 복합키는 두개 이상의 @Id 로 구성이 되는데 이를 직접 Entity 에 정의하는 것이 아니라 별도의 Value 를 사용해 복합키를 정의합니다.

먼저 Value 를 생성한 다음 @Embeddable 어노테이션을 이용해 이 Value 가 Entity 에 삽입이 가능함을 명시 하고 Entity 에서는 @EmbeddedId 어노테이션을 이용해 이 Entity 에 해당 ValuePK 로 사용한다고 지정합니다.

@Embeddable
public class CompanyOrganizationKey implements Serializable {
	@Column(name = "company_code")
    private String companyCode;
    
    @Column(name = "organization_code")
    private String organizationCode;
}

@Entity(name = "company_organization")
public class CompanyOrganization {
    @EmbeddedId
    protected CompanyOrganizationKey companyOrganizationKey;
}

 

@Enumerated

@Enumerated 어노테이션은 java 의 enum 형태로 되어 있는 미리 정의되어 있는 코드 값이나 구분값을 데이타 타입으로 사용하고자 할때 사용됩니다. 속성으로 EnumType.ORDINAL, EnumType.STRING 이 있는데 ORDINAL 은 enum 객체에 정의된 순서가 컬럼의 값으로 사용되고 STRING 은 enum 의 문자열 자체가 컬럼의 값으로 사용이 됩니다.

enum FlagYN {
    Y, N
}

@Enumerated(EnumType.ORDINAL)
@Column(name = "access_yn")
private FlagYN accessYn; //0, 1 이 값으로 저장

@Enumerated(EnumType.STRING)
@Column(name = "use_yn", length = 1)
private FlagYN useYn; //'Y', 'N' 이 값으로 저장

 

@Transient

만약 Entity 객체에 속성으로서 지정되어 있지만 데이타베이스상에 필요없는 속성이라면 @Transient 어노테이션을 이용해서 해당 속성을 데이타베이스에서 이용하지 않겠다 라고 정의합니다. 이렇게 하면 해당 속성을 Entity 객체에 임시로 값을 담는 용도로 사용이 가능해 집니다.

@Transient
private String tempValue;

 

다음글에서는 JPA 에서 Entity 간의 관계를 다루는 방법에 대해서 알아 보겠습니다.