JPA 기본
모든 걸 내가 다 할수는 없으니까, 같이해야지. 그렇게 하는 편이 더 재밌으니까.
하지만 아떤 사람을 곁에 둘지는 항상 고민해야지.
@Entity
@Entity 어노테이션은 데이터 베이스의 테이블과 일대일로 매칭되는 객체 단위이며 Entity 객체의 인스턴스 하나가 테이블에서
하나의 레코드 값을 의미합니다. 그래서 객체의 인스턴스를 구분하기 위한 유일한 키 값을 가지는데 이것은 테이블 상의 Primary Key 와 같은 의미를 가지며
@Id 어노테이션으로 표기됩니다.
Spring Boot를 설정할 때 spring.jpa.hibernate.ddl-auto 설정이 create 혹은 update로 되어 있을 경우 Spring 프로젝트가 시작될 때 EntityManager가 자동으로
DDL을 수행해 테이블을 생성해 줍니다.
이때 명시적으로 @Entity의 name 속성을 이용해 데이타베이스 상의 실제 테이블 명칭을 지정하지 않는다면 Entity 클래스의 이름 그대로 CamelCase 를 유지한 채 테이블이 생성이 되기 때문에
테이블 이름을 명시적으로 작성하는 것이 관례입니다. 왜냐하면 데이타베이스 상에서 보편적으로 사용되는 명명법은 UnderScore가 원칙이기 때문이다.
@Column
@Column 어노테이션은 데이타베이스의 테이블에 있는 컬럼과 동일하게 1:1로 매칭되기 때문에 Entity 클래스안에 내부변수로 정의됩니다.
만약 테이블에 a, b, c 컬럼이 있다면 각각 3개의 @Column 어노테이션을 작성하게 됩니다. 다만 의도적으로 필요 없는 컬럼들은 작성하지 않아도 되는데
데이터 베이스 테이블에 실제 a, b, c, d 총 4개의 컬럼이 있더라도 a,b,c 컬럼만 Entity 클래스에 작성해도 무방하다는 이야기 입니다.
spring.jpa.hibernate.ddl-auto 설정이 create 혹은 update
Create일 때는 최초로 한번 컬럼이 생성됩니다.
Update일때는 Entity 클래스에 있지만 해당 테이블에 존재하지 않는 컬럼을 추가로 생성해준다.
하지만 컬럼의 데이터 타입이 변경 되었거나 길이가 변경되었을 대 자동으로 데이터베이스에 반영을 해주지는 않기 때문에 속성이 변경되면 기존의 테이블을 drop 후 새롭게 create 하던지 개별로
alter table을 통해 직접 ddl 구문을 적용하는 것이 좋다.
시스템을 실제로 운영할 때는 create, update, create-drop 명령을 사용하지 않아야 합니다.
- precision
- scale
- name
@Id
데이터 베이스의 테이블은 기본적으로 유일한 값을 가집니다. 그것을 PK(Primary Key)라고 하는데 데이타베이스는 이 유일한 키값을 기준으로 질의한 데이터를 추출해 결과셋으로 반환해줍니다. 테이블 상에 PK가 없는 테이블도 있지만 대부분의 경우 반드시 PK가 존재합니다.
@GeneratedValue
PK 컬럼의 데이타 형식은 정해져 있지는 않으나 구분이 가능한 유일한 값을 가지고 있어야 하고 데이타 경합으로 인해 발생되는 데드락 현상을 방지하기 위해 대부분 BigInterger 즉 Java의 Long 을 주로 사용합니다. 물론 String 형태의 고정된 키 값을 직접 생성해서 관리하기도 합니다. 중요한 것은 대량의 요청이 유입되더라도 중복과 deadlock이 발생되지 않을 만큼 키값이 빨리 생성이 되고 안전하게 관리되어야 한다는 점입니다.
- GenerationType.IDENTITY : auto increment로 지정하면 자동으로 새로운 레코드가 생성이 될 때마다 마지막 PK 값에서 자동으로 + 1을 해주는 방식입니다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
- GenerationType.SEQUENCE : Oracle에서 사용되는 sequence 방식은 sequence Oracle 객체를 생성해 두고 해당 sequence를 호출할 때마다 기존 값의 + 1이 된 값을 반환해주는 방식입니다.
@Id
@SequenceGenerator(name="seq", sequenceName="jpa_sequence")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
@EmbeddedId
PK를 복합키로 하여 테이블의 PK를 정의하기 위해서 사용할 수 있다. 이와 같은 경우 별도의 Value를 사용해 복합키를 정의합니다. 먼저 Value를 생성한 다음 @Embeddable 어노테이션을 이용해 이 Value가 Entity에 사입이 가능함을 명시하고 Entity에서는 @EmbeddedId 어노테이션을 이용해 이 Entity 에 해당 Value를 PK로 사용한다고 지정합니다.
@Embeddable
public class LinesKey implements Serializable {
@Column(name = "code")
private String code;
@Column(name = "line_code")
private String lineCode;
}
@Entity(name = "company_organization")
public class CompanyOrganization {
@EmbeddedId
protected LinesKey lineKey;
}
@Enumerated
@Enumerated 어노테이션은 java의 enum 형태로 되어 있는 미리 정의되어 있는 코드 값이나 구분 값을 데이타 타입으로 사용하고자 할 때 사용됩니다. 속성으로 EnumType.ORDINAL,EnumType.STRING 이 있는데 ORDINAL은 enum 객체에 정의된 순서가 컬럼의 값으로 사용되고 STRING은 enum의 문자열 자체가 컬럼의 값으로 사용이 됩니다.
enum LinesCode {
A, B
}
@Enumerated(EnumType.ORDINAL)
@Column(name = "value1")
private LinesCode value1;
@Enumerated(EnumType.STRING)
@Column(name = "value2", length = 1)
private LinesCode value2;
@Transient
Entity 객체에 속성으로서 지정되어 있지만 데이타 베이스 상에 필요없는 속성이라면 @Transient 어노테이션을 이용해서 해당 속성을 데이타베이스에서 이용하지 않겠다라고 정의합니다.
@Transient
private String value;
데이터베이스 관계
관계형 데이터베이스의 데이터를 객체로 표현해 관리하는 것이 orm의 기본 방향. 관계형 데이터베이스 ( RDBMS )에서 사용하는 관계
- OneToOne
- OneToMany
- ManyToOne
- ManyToMany
위의 관계에 대해서 데이터베이스에서 물리적으로 지원하지는 않지만 jpa 상에서 논리적으로 지원합니다.
@OneToOne
@Entity(name = "parent")
public class LineParent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; //여기는 parent 의 id
}
@Entity(name = "child")
public class LineChild {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; //여기는 child 의 id
@OneToOne
@JoinColumn(name = "parent_id") //child 에 지정되어 있는 FK parent_id 기준으로 parent 조회
private LineParent parent;
}
@SecondaryTable
1:1 관계 중 자식 Entity 객체의 모든 값을 사용할 필요 없이 필요한 자식 Entity의 값을 부모 Entity에서 사용하게 하는 방법을 @SecondaryTable 이 제공합니다. @SecondaryTable 이 정의된 Entity를 조회할 때 내부적으로 데이터를 join 해서 parent와 child 의 데이터를 합쳐서 가지고 오기 때문에 속도면에서도 조금은 유리하다고 볼 수 있습니다.
@Entity(name = "parent")
@SecondaryTable(
name = "LineChild",
pkJoinColumns = @PrimaryKeyJoinColumn(name="parent_id", referencedColumnName="id")
)
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; //여기는 parent 의 id
@Column(name = "name", table = "LineChild")
private String LineChildName;
}
@Entity(name = "parent")
@SecondaryTables({
@SecondaryTable(
name = "LineChild",
pkJoinColumns =
@PrimaryKeyJoinColumn(name="parent_id", referencedColumnName="id")
),
@SecondaryTable(
name = "LineDetail",
pkJoinColumns =
@PrimaryKeyJoinColumn(name="parent_id", referencedColumnName="id")
)
})
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", table = "LineChild")
private String LineChildName;
@Column(name = "name", table = "LineDetail")
private String LineDetailName;
}
@ManyToOne
@ManyToOne 관계는 자식 Entity 객체에서 부모 Entity 객체를 바라볼대 사용하는 어노테이션 입니다. @OneToOne 과 다른점은 동일한 부모 Entity 데이터를 가지는 자식 Entity 데이타가 여러개 있을 수 있다는 점입니다. @ManyToOne 어노테이션이 위치하는 것은 FK. 가 있는 자식 Entity 객체쪽 입니다.
@Entity(name = "LineParent")
public class LineParent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
@Entity(name = "LineChild")
public class LineChild {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "LineParent_id")
private LineParent parent;
}
@OneToMany
Entity 간의 관계를 지정할 때 아마 가장 많이 사용하는 관계가 @OneToMany 일 것이다. @OneToMany는 데이터를 바라보는 주체가 부모 Entity이며 하나의 부모 Entity 데이터와 연관이 있는 여러개의 자식 Entity 데이터를 사용 하겠다는 의미입니다. 이때 부모 Entity 에서 @OneToMany 어노테이션을 지정하게 되며 JPA 관계 중 유일하게 FK가 위치한 자식 Entity 가 아닌 부모 Entity 에 어노테이션이 위치하게 됩니다.
@Entity(name = "LineParent")
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany
@JoinColumn(name = "LineParent_id") // LineChild 테이블에 있는 parent_id FK
private List<LineChild> childList;
}
@Entity(name = "LineChild")
public class LineChild {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
@ElementCollection
@ElementCollection 어노테이션은 OneToMany 관계에서 자식 Entity에 있는 모든 값을 가지고 오는 것이 아니라 필요한 속성의 값만 가지고 와서 Collection으로 구성할 수 있는 기능을 제공합니다. 또한 @Embeddable 로 지정된 Value 객체를 이용해 값을 가지고 올 수도 있습니다.
@Entity(name = "LineParent")
public class LineParent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ElementCollection
@CollectionTable(
name = "LineChild",
joinColumns = @JoinColumn(name = "LineParent_id")
)
@OrderColumn(name = "id")
@Column(name = "name")
private List<String> childNameList;
}
@Entity(name = "LineParent")
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ElementCollection
@CollectionTable(
name = "LineChild",
joinColumns = @JoinColumn(name = "LineParent_id")
)
@MapKeyColumn(name = "id")
@Column(name = "name")
private Map<Long, String> childNameMap;
}
@ManyToMany
JPA 상에서 논리적으로만 표현할 수 잇다. 이를 위해서 데이터 베이스 중간에 @ManytoMany 를 표현할 수 있는 매핑 테이블을 생성해 서로의 PK 가지고 관계를 설정 하게 되며 중간 매핑 테이블은 JPA 상에서 숨겨셔서 표현이 됩니다.
@Entity(name = "LineParent")
public class LineParent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToMany
@JoinTable(
name = "LineParent_child",
joinColumns = @JoinColumn(name = "LineParent_id"),
inverseJoinColumns = @JoinColumn(name = "LineChild_id")
)
private List<LineChild> childList;
}
위의 코드는 데이터베이스의 관계를 단방향 관계를 나타낸 것입니다. 테이블 간의 관계에서는 단방향과 양방향에 대한 구분이 없지만 JPA 상에서는 사용하는 Entity에 따른 차이가 존재합니다. 양방향 관계를 지정할 때 유의해야할 점은 서로의 Entity 데이타에 대한 동일한 사용 권한을 가지기 때문에 의도치 않은 데이터의 오염이 일어날 수 있다는 점입니다. Entity 의 값이 변경되었는데 이게 누구에 의한 데이타의 변경인지 파악하기 어려워 진다는 점입니다. 그래서 그로그램을 개발할 때 데이터의 변경에는 최대한 폐쇄적으로 접근하는게 좋기 때문에 가급적이면 양방향의 관계는 사용하지 않는 것이 좋습니다.