JPA Programming Basic
JPA Programming Basic
์ํ๋์ ์๋ฐ ORM ํ์ค JPA ํ๋ก๊ทธ๋๋ฐ - ๊ธฐ๋ณธํธ ๊ฐ์๋ฅผ ์์ฝํ ๋ด์ฉ์ ๋๋ค.
Intro
JPA
Java Persistence API
์๋ฐ ์ง์์ ORM ๊ธฐ์ ํ์ค (์ธํฐํ์ด์ค์ ๋ชจ์)
๊ตฌํ์ฒด๋ก๋ Hibernate, EclipseLink, DataNucleus..
Application๊ณผ JDBC ์ฌ์ด์์ ๋์
ROM
Object-Relational Mapping
Object๋ Object๋๋ก, RDBMS๋ RDBMS๋๋ก ์ค๊ณ
ORM ํ๋ ์์ํฌ๊ฐ ์ค๊ฐ์์ ๋งคํ
EntityManagerFactory
persistence.xml ์ค์ ์ ๋ณด ํ์ธ ํ persistence-unit name ์ ๋ง๋ EntityManagerFactory ์์ฑ
Web Server ๊ฐ ์์ฑ๋๋ ์์ ์ ํ๋๋ง ์์ฑํด์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด์์ ๊ณต์
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager
์์ฒญ ๊ฑด๋ง๋ค ์์ฑ
์ฐ๋ ๋๊ฐ ๊ณต์ ํ๋ฉด ์ ๋๊ณ , ์ฌ์ฉ ํ ์ข ๋ฃ
EntityManager em = emf.createEntityManager();
EntityTransaction
JPA์ ๋ชจ๋ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ํธ๋์ ์ ์์์ ์คํ
EntityTransaction tx = em.getTransaction(); tx.begin();
์์์ฑ ๊ด๋ฆฌ
์์์ฑ ์ปจํ
์คํธ
PersistenceContext (์ํฐํฐ๋ฅผ ์๊ตฌ ์ ์ฅํ๋ ํ๊ฒฝ)
EntityManager ๋ฅผ ํตํด PersistenceContext ์ ์ ๊ทผ
EntityManager, PersistenceContext ๋
1:1
,N:1
๊ด๊ณ ์กด์ฌ
์ํฐํฐ ์๋ช ์ฃผ๊ธฐ
๋น์์
(new / transient)์์์ฑ ์ปจํ ์คํธ์ ์ ํ ๊ด๊ณ๊ฐ ์๋ ์๋ก์ด ์ํ
Member member = new Member(); member.setId(1L); member.setName("Aaron");
์์
(managed)์์์ฑ ์ปจํ ์คํธ์ ๊ด๋ฆฌ๋๋ ์ํ
entityManager.persist(member);
์ค์์
(detached)์์์ฑ ์ปจํ ์คํธ์ ์ ์ฅ๋์๋ค๊ฐ ๋ถ๋ฆฌ๋ ์ํ
entityManager.detach(member); entityManager.clear() entityManager.close()
์ญ์
(removed)์ญ์ ๋ ์ํ
entityManager.remove(member);
.
์์์ฑ ์ปจํ ์คํธ์ ์ด์
1์ฐจ ์บ์
์์์ ์กฐํ์ฌ์ฉ์์ ํ๋์ ์์ฒญ-์๋ต(ํ๋์ ํธ๋์ ์ ) ๋ด์์๋ง ํจ๊ณผ๊ฐ ์์ผ๋ฏ๋ก ์ฑ๋ฅ ์ด์ ์ ๊ธฐ๋ํ์ง๋ ์์.
์ํฐํฐ ์กฐํ ์ ๋จผ์ 1์ฐจ ์บ์์์ ์กฐํ ํ, ์์ ๊ฒฝ์ฐ DB์์ ์กฐํ
์์ ์ํฐํฐ์
๋์ผ์ฑ(identity) ๋ณด์ฅ
1์ฐจ ์บ์๋ก ๋ฐ๋ณต ๊ฐ๋ฅํ ์ฝ๊ธฐ(REPEATABLE READ) ๋ฑ๊ธ์ ํธ๋์ญ์ ๊ฒฉ๋ฆฌ ์์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ฐจ์์์ ์ ๊ณต
ํธ๋์ญ์ ์ ์ง์ํ๋
์ฐ๊ธฐ ์ง์ฐ
(transactional write-behind)Query๋ฅผ ์์ ๋๋ค๊ฐ transaction.commit() ์ ํ๋ ์๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ Query ์ ์ก
๋ณ๊ฒฝ ๊ฐ์ง
(Dirty Checking)transaction.commit() ์์ ์ ์ํฐํฐ์ ์ค๋ ์ท(์ฒ์ ์ฝ์ด ์จ ์ํฐํฐ ์ํ) ๋น๊ต ํ ๋ณ๊ฒฝ์ด ๊ฐ์ง๋๋ฉด Update Query ๋ฅผ ์ฐ๊ธฐ ์ง์ฐ SQL ์ ์ฅ์์ ์ ์ฅ
์ดํ DB์ Query ์ ์ก ๋ฐ Commit
์ง์ฐ ๋ก๋ฉ(Lazy Loading)
.
Flush
์์์ฑ ์ปจํ ์คํธ์ ๋ณ๊ฒฝ๋ด์ฉ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ํ๋ ์ญํ
๋ฐ์ ์์
๋ณ๊ฒฝ ๊ฐ์ง
์ฐ๊ธฐ ์ง์ฐ SQL ์ ์ฅ์์ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ก (CUD Query)
ํธ์ถ ๋ฐฉ๋ฒ
์ง์ ํธ์ถ : em.flush()
์๋ ํธ์ถ : ํธ๋์ญ์ ์ปค๋ฐ, JPQL ์ฟผ๋ฆฌ ์คํ
์ํฐํฐ ๋งคํ
๊ฐ์ฒด์ ํ ์ด๋ธ
๊ฐ์ฒด์ ํ ์ด๋ธ ๋งคํ
@Entity
: JPA๊ฐ ๊ด๋ฆฌํ๋ ํด๋์ค (๊ธฐ๋ณธ ์์ฑ์ ํ์)@Table
: ์ํฐํฐ์ ๋งคํํ ํ ์ด๋ธ ์ง์
ํ๋์ ์ปฌ๋ผ ๋งคํ
@Column
๊ธฐ๋ณธ ํค ๋งคํ
@Id
์ฐ๊ด๊ด๊ณ ๋งคํ
@ManyToOne
@JoinColumn
.
๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง ์๋ ์์ฑ
@Entity๊ฐ ์๋ ํด๋์ค DDL์ ์ ํ๋ฆฌ์ผ์ด์ ์คํ ์์ ์ ์๋ ์์ฑ (๊ฐ๋ฐ ํ๊ฒฝ์์๋ง ์ฌ์ฉ)
<property name="hibernate.hbm2ddl.auto" value="create" />
create
: ๊ธฐ์กดํ ์ด๋ธ ์ญ์ ํ ๋ค์ ์์ฑ (DROP + CREATE)create-drop
: create์ ๊ฐ์ผ๋ ์ข ๋ฃ์์ ์ ํ ์ด๋ธ DROPupdate
: ๋ณ๊ฒฝ๋ถ๋ง ๋ฐ์(์ด์DB์๋ ์ฌ์ฉํ๋ฉด ์๋จ)validate
์ํฐํฐ์ ํ ์ด๋ธ์ด ์ ์ ๋งคํ๋์๋์ง๋ง ํ์ธnone
: ์ฌ์ฉํ์ง ์์
์ฃผ์
์ด์ ์ฅ๋น์๋ ์ ๋ create, create-drop, update ์ฌ์ฉํ๋ฉด ์๋จ.
๊ฐ๋ฐ ์ด๊ธฐ ๋จ๊ณ๋ create ๋๋ update
ํ ์คํธ ์๋ฒ๋ update ๋๋ validate
์คํ ์ด์ง๊ณผ ์ด์ ์๋ฒ๋ validate ๋๋ none
.
ํ๋์ ์ปฌ๋ผ
@Entity
public class Member {
@Id
private Long id;
/**
* @Column : ์ปฌ๋ผ ๋งคํ
* - name : ํ๋์ ๋งคํํ ํ
์ด๋ธ ์ปฌ๋ผ๋ช
(default. ๊ฐ์ฒด ํ๋๋ช
)
* - insertable : ๋ฑ๋ก ๊ฐ๋ฅ ์ฌ๋ถ (default. TRUE)
* - updatable : ์์ ๊ฐ๋ฅ ์ฌ๋ถ (default. TRUE)
*
* ์๋๋ DDL ์กฐ๊ฑด
* - nullable : null ํ์ฉ ์ฌ๋ถ (default. TRUE)
* - unique : ์ ๋ํฌ ์ ์ฝ ์กฐ๊ฑด, ์ ์ฝ์กฐ๊ฑด๋ช
์ด ๋๋คํค๋ก ์์ฑ๋์ด ์ฃผ๋ก ์ฌ์ฉํ์ง๋ ์์ (default. FALSE)
* - columnDefinition : ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปฌ๋ผ ์ ๋ณด ์ง์ ์ค์ (ex. varchar(100) default โEMPTY')
* - length : ๋ฌธ์(String) ๊ธธ์ด ์ ์ฝ์กฐ๊ฑด (default. 255)
*
* BigDecimal, BigInteger ํ์
์ ์ฌ์ฉ
* - precision : ์์์ ์ ํฌํจํ ์ ์ฒด ์๋ฆฟ์ (default. 19)
* - scale : ์์ ์๋ฆฟ์ (default. 2)
*/
@Column(name = "name")
private String username;
private Integer age;
/**
* @Enumerated : enum ํ์
๋งคํ
* ์ฃผ์. ORDINAL ์ฌ์ฉ X! (enum ์์๋ฅผ ์ ์ฅ)
*/
@Enumerated(EnumType.STRING)
private RoleType roleType;
/**
* @Temporal : ๋ ์ง ํ์
๋งคํ
* DATE, TIME, TIMESTAMP
* (LocalDate, LocalDateTime ์ฌ์ฉํ ์ ์๋ต)
*/
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
/**
* @Lob : BLOB, CLOB ๋งคํ
* ๋งคํ ํ๋ ํ์
์ด ๋ฌธ์๋ฉด CLOB, ๋๋จธ์ง๋ BLOB์ผ๋ก ๋งคํ
* - CLOB: String, char[], java.sql.CLOB
* โข BLOB: byte[], java.sql. BLOB
*/
@Lob
private String description;
/**
* @Transient : ํด๋น ํ๋๋ฅผ ์ปฌ๋ผ์ ๋งคํํ์ง ์์
* ๋ฉ๋ชจ๋ฆฌ์์์๋ง ์์๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณด๊ดํ ๊ฒฝ์ฐ ์ฌ์ฉ
*/
@Transient
private String temp;
}
.
๊ธฐ๋ณธ ํค
@Id
: ์ง์ ํ ๋นํ ๊ฒฝ์ฐ@GeneratedValue
: ์๋ ์์ฑํ ๊ฒฝ์ฐAUTO : ๋ฐฉ์ธ์ ๋ฐ๋ผ ์๋ ์ง์ (default)
IDENTITY : ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์ (MYSQL)
์ฃผ๋ก MySQL, PostgreSQL, SQL Server, DB2 ์์ ์ฌ์ฉ
์ฐธ๊ณ ) DB INSERT Query ์คํ ํ์ ID ๊ฐ์ ์ ์ ์์ผ๋ฏ๋ก, em.persist() ์์ ์ ์ฆ์ INSERT Query ์คํ ๋ฐ DB ์๋ณ์ ์กฐํ
SEQUENCE : ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ์ค ์ค๋ธ์ ํธ ์ฌ์ฉ (ORACLE, @SequenceGenerator)
์ฃผ๋ก ์ค๋ผํด, PostgreSQL, DB2, H2 ์์ ์ฌ์ฉ
TABLE : ํค ์์ฑ์ฉ ํ ์ด๋ธ ์ฌ์ฉ, (๋ชจ๋ DB, @TableGenerator)
Long Type + ๋์ฒดํค + ํค ์์ฑ์ ๋ต ์ฌ์ฉ ๊ถ์ฅ
AUTO & IDENTITY
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
SEQUENCE
allocationSize
์ํ์ค๋ฅผ ํ ๋ฒ ํธ์ถํ ๋ ์ฆ๊ฐํ๋ ์ (์ฑ๋ฅ ์ต์ ํ์ ์ฌ์ฉ, default. 50)
์น ์๋ฒ๋ฅผ ๋ด๋ฆฌ๋ ์์ ์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅ๋์ด์๋ ์ํ์ค๋ค์ด ๋ ๋ผ๊ฐ์ ๊ตฌ๋ฉ์ด ์๊ธฐ๋ฏ๋ก, 50~100์ด ์ ์
DB ์ํ์ค ๊ฐ์ด ํ๋์ฉ ์ฆ๊ฐํ๋๋ก ์ค์ ๋์ด ์๋ค๋ฉด, ์ด ๊ฐ์ ๋ฐ๋์ 1๋ก ์ค์
ex) ์ด๊ธฐ 1 ~ 51 ๊น์ง ์กฐํ, ์ดํ ์ํ์ค๋ DB์์ ์กฐํํ์ง ์๊ณ ๋ฉ๋ชจ๋ฆฌ์์์ ์กฐํ
๋ฉ๋ชจ๋ฆฌ์์ ์ํ์ค 51์ ๋ง๋๋ ์๊ฐ ๋ค์ DB์์ ์กฐํ(next call)
๋ฏธ๋ฆฌ ์ํ์ค ๊ฐ์ ์ฌ๋ ค๋๋ฏ๋ก ๋์์ฑ ๋ฌธ์ ๊ฐ ๋์ง ์์
์ด ๋ถ๋ถ์ Table ์ ๋ต๋ ์ ์ฌ
@Entity
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR", //์์ฑ ์ด๋ฆ
sequenceName = "MEMBER_SEQ", //๋งคํํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ์ค ์ด๋ฆ
initialValue = 1, allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
}
์ฐ๊ด๊ด๊ณ ๋งคํ
JPA๋ ๊ฐ์ฒด์ ์ฐธ์กฐ์ ํ ์ด๋ธ์ ์ธ๋ ํค๋ฅผ ๋งคํ
๋ฐฉํฅ(Direction)
๋จ๋ฐฉํฅ
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
//...
//๋จ๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ ์ค์ (์ฐธ์กฐ ์ ์ฅ)
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);
์๋ฐฉํฅ
๊ฐ์ฒด์ ์๋ฐฉํฅ ๊ด๊ณ๋ ์ฌ์ค ์๋ก ๋ค๋ฅธ ๋จ๋ฑกํฅ ๊ด๊ณ 2๊ฐ๋ผ๋ ์ฌ์ค.
๊ฐ์ฒด๋ฅผ ์๋ฐฉํฅ์ผ๋ก ์ฐธ์กฐํ๋ ค๋ฉด ๋จ๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ฅผ 2๊ฐ ๋ง๋ค์ด์ผ ํจ
ํ ์ด๋ธ์ ์ธ๋ ํค ํ๋๋ก ๋ ํ ์ด๋ธ์ ์ฐ๊ด๊ด๊ณ๋ฅผ ๊ด๋ฆฌ
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
}
์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ(Owner)
์ธ๋ํค๋ฅผ ๊ด๋ฆฌํ๋ ์ฐธ์กฐ
์๋ฐฉํฅ ๋งคํ ๊ท์น
๊ด๊ณ๋ฅผ ๊ฐ๋ ๋ ๊ฐ์ฒด ์ค ํ๋์ ๊ฐ์ฒด๋ฅผ ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ผ๋ก ์ง์
์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ๋ง์ด ์ธ๋ ํค๋ฅผ ๊ด๋ฆฌ(๋ฑ๋ก, ์์ )ํ๊ณ , ์ฃผ์ธ์ด ์๋ ์ชฝ์ ์กฐํ๋ง ๊ฐ๋ฅ
์ฃผ์ธ์ด ์๋ ๊ฐ์ฒด์ ํ๋์ mappedBy ์์ฑ์ผ๋ก ์ฃผ์ธ ํ๋๋ฅผ ์ง์
์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ ๋ค(
N
:1)์ ํด๋นํ๋ ๊ฐ์ฒด์ชฝ์ด ๊ฐ๋๋ก(์ธ๋ํค๋ฅผ ๊ฐ๋ ํ ์ด๋ธ ๊ธฐ์ค)์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ ๊ฐ ์ค์ ํ๊ธฐ
Team team = new Team(); team.setName("TeamA"); em.persist(team); Member member = new Member(); member.setName("member1"); //์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ ๊ฐ ์ค์ member.setTeam(team); //์์ ๊ฐ์ฒด ์ํ๋ฅผ ๊ณ ๋ คํด์ ํญ์ ์์ชฝ์ ๊ฐ ์ค์ ํ๊ธฐ team.getMembers().add(member); em.persist(member);
์ฐ๊ด๊ด๊ณ ํธ์ ๋ฉ์๋๋ฅผ ์์ฑํ๋ ๊ฒ์ด ํธ๋ฆฌ
@Entity public class Member { //.. public void changeTeam(Team team) { this.tema = team; team.getMembers().add(this); } } // OR (๋ ๊ฐ์ฒด ์ค ํ ๊ฐ์ฒด๋ฅผ ์ ํ) @Entity public class Team { //... public void addMember(Member member) { member.setTeam(this); members.add(member); } }
์๋ฐฉํฅ ๋งคํ์์ ๋ฌดํ ๋ฃจํ๋ก ์ธํ StackOverflow ์กฐ์ฌํ๊ธฐ
toString(), lombok, JSON ์์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(=> Controller DTO ๋ฐํ์ผ๋ก ํด๊ฒฐ)
๋จ๋ฐฉํฅ ๋งคํ๋ง์ผ๋ก๋ ์ฐ๊ด๊ด๊ณ ๋งคํ์ ์๋ฃ๋ ์ํ.
์ถํ ์ญ๋ฐฉํฅ ํ์์ด ํ์ํ ๊ฒฝ์ฐ์ ์ถ๊ฐํ๊ธฐ!(ํ ์ด๋ธ์ ์ํฅ X)
๋ค์ค์ฑ(Multiplicity)
๋ค๋์ผ(N:1)
- @ManyToOne
ํ ์ด๋ธ ์ธ๋ํค ๊ธฐ์ค์ผ๋ก ์ฐ๊ด๋ ์ฐธ์กฐ๋ฅผ ์ค์ (N์ด ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ)
์๋ฐฉํฅ ์ฐ๊ฒฐ์ ํ ๊ฒฝ์ฐ, ๋ฐ๋ ๊ฐ์ฒด์๋ OneToMany ๋ฐฉํฅ ์ค์ ์ถ๊ฐ(์กฐํ๋ง ๊ฐ๋ฅ)
์ผ๋๋ค(1:N)
- @OneToMany
์ผ๋๋ค ๋จ๋ฐฉํฅ
์์ ๋ฐ๋ ์ผ์ด์ค๋ก ์ผ(1)์ด ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ด ๋ ๊ฒฝ์ฐ, A(1) ํ ์ด๋ธ ์ ๋ฐ์ดํธ๋ฅผ ์๋ํ์ง๋ง B(N) ํ ์ด๋ธ๋ ํจ๊ป ์ ๋ฐ์ดํธ๋ฅผ ํด์ผํ๋ ์ํฉ์ผ๋ก ์ฌ๋ฌ ์ด์ ๋ฐ์ ์์๊ฐ ์๊น
์ํฐํฐ๊ฐ ๊ด๋ฆฌํ๋ ์ธ๋ํค๊ฐ ๋ค๋ฅธ ํ ์ด๋ธ์ ์์ผ๋ฏ๋ก ์ฐ๊ด๊ด๊ณ ๊ด๋ฆฌ๋ฅผ ์ํด ์ถ๊ฐ ์ ๋ฐ์ดํธ ์ฟผ๋ฆฌ ๋ฐ์
@OneToMany @JoinColumn(name = "team_id") private List<Member> members = new ArrayList<>();
์ผ๋๋ค ์๋ฐฉํฅ ์ฐ๊ฒฐ์ ๊ณต์์ ์ผ๋ก ์กด์ฌํ์ง ์๋ ๋งคํ
์ผ๋๋ค ๋จ๋ฑกํฅ ๋งคํ๋ณด๋ค ๋ค๋์ผ ์๋ฐฉํฅ ๋งคํ์ ์ฌ์ฉํ์
์ผ๋์ผ(1:1)
- @OneToOne
ex) ํ์๊ณผ ๊ฐ์ธ ๋ฝ์ปค์ ๊ด๊ณ
์ธ๋ํค์ DB ์ ๋ํฌ(UNI) ์ ์ฝ์กฐ๊ฑด ํ์
์ค์ ์ ๋ค๋์ผ ๋งคํ๊ณผ ์ ์ฌ
์ฃผ/๋์ ํ ์ด๋ธ ์ค์ ์ธ๋ํค ์ ํ ๊ฐ๋ฅ
์ฃผ ํ ์ด๋ธ ์ ํ
JPA ๋งคํ์ด ํธ๋ฆฌํ์ฌ ๊ฐ์ฒด์งํฅ ๊ฐ๋ฐ์ ์ ํธ
์ฅ์ . ์ฃผ ํ ์ด๋ธ๋ง ์กฐํํด์ ๋์ ํ ์ด๋ธ ๋ฐ์ดํฐ ํ์ธ ๊ฐ๋ฅ
๋จ์ . ๊ฐ์ด ์์ผ๋ฉด ์ธ๋ํค์ null ํ์ฉ
๋์ ํ ์ด๋ธ ์ ํ
์ ํต DB ๊ฐ๋ฐ์ ์ ํธ
์ฅ์ . ์ฃผ/๋์ ํ ์ด๋ธ์ 1:1 ๊ด๊ณ์์ 1:N ๊ด๊ณ๋ก ๋ณ๊ฒฝ ์ ํ ์ด๋ธ ๊ตฌ์กฐ ์ ์ง ๊ฐ๋ฅ
๋จ์ . ํ๋ก์ ๊ธฐ๋ฅ์ ํ๊ณ๋ก ์ง์ฐ ๋ก๋ฉ์ผ๋ก ์ค์ ํด๋ ํญ์ ์ฆ์ ๋ก๋ฉ
๋จ๋ฐฉํฅ
@Entity
public class Member {
//..
@OneToOne
@JoinColumn(name = "locker_id")
private Locker locker;
}
์๋ฐฉํฅ
@Entity
public class Locker {
//..
@OneToOne(mappedBy = "Locker")
private Member member;
}
๋ค๋๋ค(N:M)
- @ManyToMany
RDB๋ ์ ๊ทํ๋ ํ ์ด๋ธ 2๊ฐ๋ก ๋ค๋๋ค ๊ด๊ณ๋ฅผ ํํํ ์ ์์ผ๋ฏ๋ก ์ค๊ฐ ํ ์ด๋ธ์ด ํ์
๊ฐ์ฒด๋ @ManyToMany, @JoinTable๋ก ๋ค๋๋ค ๊ด๊ณ๋ฅผ ํํํ ์ ์์ง๋ง, ๊ธฐํ ๋ฐ์ดํฐ๋ฅผ ํฌํจ์ํฌ ์ ์๋ ํ๊ณ๋ก ์ค๋ฌด์์๋ ์ฌ์ฉํ๊ธฐ ์ด๋ ค์ (๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ์ N:N ๊ด๊ณ ์ค๊ณ๊ฐ ๋ถ๊ฐ๋ฅํ๋ฏ๋ก ์ํฐํฐ์ ํ ์ด๋ธ ๋ถ์ผ์น ๋ฌธ์ ๋ ๋ฐ์)
์ฐ๊ฒฐ ํ ์ด๋ธ์ฉ ์ํฐํฐ๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ ์ฌ์ฉ
@ManyToMany -> @OneToMany, @ManyToOne ๋ก ํ์ด์ ์ฌ์ฉํ์.
Member.java
@Entity
public class Member {
//...
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts = new ArrayList()<>;
}
MemberProduct.java
id ๋์ (member, product)๋ฅผ ๋ฌถ์ด์ PK, FK๋ก ์ฌ์ฉํ ์๋ ์์ง๋ง,
ํฅํ ๋น์ฆ๋์ค์ ์ธ ์กฐ๊ฑด์ด ์ถ๊ฐ๋ ๊ฒฝ์ฐ๋ฅผ ๊ณ ๋ คํ๋ฉด, GeneratedValue ID๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ ์ฐํ๊ณ ๊ฐ๋ฐ์ด ์ฌ์์ง๋ ์ฅ์ ์ด ์์
@Entity
public class MemberProduct {
@Id @GeneratedValue
@Column(name = "member_product_id ")
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;
private int orderAmount;
private int price;
private LocalDataTime orderDateTime;
}
Product.java
@Entity
public class Product {
//...
@OneToMany(mappedBy = "product")
private List<MemberProduct> memberProducts = new ArrayList()<>;
}
๊ณ ๊ธ ๋งคํ
์์๊ด๊ณ ๋งคํ
๊ฐ์ฒด์ ์์ ๊ตฌ์กฐ์ DB์ ์ํผ/์๋ธํ์ ๊ด๊ณ๋ฅผ ๋งคํ
DB ์ํผ/์๋ธํ์ ๋ ผ๋ฆฌ ๋ชจ๋ธ์ ๋ฌผ๋ฆฌ ๋ชจ๋ธ๋ก ๊ตฌํํ๋ ๋ฐฉ๋ฒ
@DiscriminatorColumn(name=โDTYPEโ) / ์์ ํ์ ํ๋ ์ฌ์ฉ (default. DTYPE)
@DiscriminatorValue(โXXXโ) / ์์ ํ์ ๋ช ์์ ์ (default. entity name)
@Inheritance(strategy=InheritanceType.XXX) / ์์ ํ์
์กฐ์ธ ์ ๋ต JOINED
๊ธฐ๋ณธ ์ ์์ผ๋ก ์ฌ์ฉ
์ฅ์
ํ ์ด๋ธ ์ ๊ทํ (์ ์ฅ๊ณต๊ฐ ํจ์จํ)
์ธ๋ํค ์ฐธ์กฐ ๋ฌด๊ฒฐ์ฑ ์ ์ฝ์กฐ๊ฑด ํ์ฉ
๋จ์
์กฐํ ์ฟผ๋ฆฌ๊ฐ ๋ณต์กํด์ง๊ณ , ์กฐ์ธ์ ๋ง์ด ์ฌ์ฉํ๊ฒ ๋์ด ์ฑ๋ฅ ์ ํ
๋ฐ์ดํฐ ์ ์ฅ ์ INSERT Query ๋ ๋ฒ ํธ์ถ
//Item.java
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name=โDTYPEโ)
public class Item {
@Id @GeneratedValue
private Long id;
private String name;
private Integer price;
}
//Album.java
@Entity
@DiscriminatorValue("A")
public class Album extends Item {
private String artist;
}
//Movie.java
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {
private String director;
private String actor;
}
//Book.java
@Entity
@DiscriminatorValue("B")
public class Book extends Item {
private String author;
private String isbn;
}
๋จ์ผ ํ
์ด๋ธ ์ ๋ต SINGLE_TABLE
๋จ์ํ๊ณ ํ์ฅ ๊ฐ๋ฅ์ฑ์ด ์์ ๊ฒฝ์ฐ ์ฌ์ฉ
์ฅ์
์กฐํ ์ ์กฐ์ธ์ด ํ์ ์์ผ๋ฏ๋ก ์ผ๋ฐ์ ์ผ๋ก ์กฐํ ์ฑ๋ฅ์ด ๋น ๋ฅด๊ณ ๋จ์
๋จ์
์์ ์ํฐํฐ๊ฐ ๋งคํํ ์ปฌ๋ผ์ ๋ชจ๋ null ํ์ฉ
๋จ์ผ ํ ์ด๋ธ์ ๋ง์ ํ๋๋ฅผ ์ ์ฅํ๋ฏ๋ก ํ ์ด๋ธ์ด ์ปค์ง ์ ์๊ณ , ์ํฉ์ ๋ฐ๋ผ ์กฐํ ์ฑ๋ฅ์ด ์ ํ๋ ์ ์์
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
public class Item {
}
๊ตฌํ ํด๋์ค๋ง๋ค ํ
์ด๋ธ ์ ๋ต TABLE_PER_CLASS
๋ถ๋ชจ ํด๋์ค๋ ์ถ์(abstract) ํด๋์ค๋ก ์์ฑ
์ ์ง ๋ณด์ ๋ฐ ๊ด๋ฆฌ ์ต์ ์ผ๋ก ๋น์ถํ๋ ์ ๋ต..
์ฅ์
์๋ธ ํ์ ์ ๋ช ํํ๊ฒ ๊ตฌ๋ถํด์ ์ฒ๋ฆฌํ๊ธฐ ํจ๊ณผ์
not null ์ ์ฝ์กฐ๊ฑด ์ฌ์ฉ ๊ฐ๋ฅ
๋จ์
์์ ํ ์ด๋ธ์ ํจ๊ป ์กฐํํ ๋ ์ฑ๋ฅ ์ ํ(UNION Query)
์์ ํ ์ด๋ธ์ ํตํฉํด์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๊ธฐ ์ด๋ ค์
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {
}
.
@MappedSuperclass
๊ณตํต ๋งคํ ์ ๋ณด๊ฐ ํ์ํ ๊ฒฝ์ฐ ์ฌ์ฉ
์ถ์ ํด๋์ค ๊ถ์ฅ(์ง์ ์์ฑํด์ ์ฌ์ฉํ ์ผ์ด ์์)
ex. ๋ฑ๋ก์ผ, ์์ ์ผ, ๋ฑ๋ก์, ์์ ์ ๋ฑ..
ํท๊ฐ๋ฆฌ์ง ์๊ธฐ!
์์๊ด๊ณ ๋งคํX - ์์ ํด๋์ค์ ๋งคํ ์ ๋ณด๋ง ์ ๊ณต)
์ํฐํฐ/ํ ์ด๋ธ ๋งคํX - ์กฐํ, ๊ฒ์(em.find(BaseEntity)) ๋ถ๊ฐ
ํ๋ก์ & ์ฐ๊ด๊ด๊ณ ๊ด๋ฆฌ
ํ๋ก์
DB ์กฐํ๋ฅผ ๋ฏธ๋ฃจ๋(Lazy) ๊ฐ์ง(Proxy) ์ํฐํฐ ๊ฐ์ฒด ์กฐํ
em.getReference()
์ค์ ํด๋์ค๋ฅผ ์์ ๋ฐ์ ๋ง๋ค์ด์ง๊ณ , ์ค์ ์ ๊ฒ ๋ชจ์๋ง ๊ฐ์ ๋น ๊นกํต
์ค์ ์ํฐํฐ์ ์ฐธ์กฐ(target)๋ฅผ ๋ณด๊ดํ๊ณ , ํ๋ก์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ์ค์ ์ํฐํฐ ๋ฉ์๋๋ฅผ ํธ์ถ
ํ๋ก์ ๊ฐ์ฒด ํน์ง
ํ๋ก์ ๊ฐ์ฒด๋ ์ฒ์ ์ฌ์ฉํ ๋
ํ ๋ฒ๋ง
์ด๊ธฐํ์ด๊ธฐํ ์ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ํตํด ์ค์ ์ํฐํฐ์ ์ ๊ทผ ๊ฐ๋ฅ
ํ๋ก์ ๊ฐ์ฒด๋ ์๋ณธ ์ํฐํฐ๋ฅผ ์์๋ฐ์ผ๋ฏ๋ก, ํ์ ์ฒดํฌ ์ instance of ์ฌ์ฉ
ํ ์์์ฑ ์ปจํ ์คํธ ์์์ ๋์ผํ ID ์กฐํ ์,
JPA๋ ํญ์ ๊ฐ์ ํ์ ์ ์ํฐํฐ๋ฅผ ๋ฐํ
์์์ฑ ์ปจํ ์คํธ์ ์ฐพ๋ ์ํฐํฐ๊ฐ ์ด๋ฏธ ์๋ค๋ฉด, em.getReference()๋ฅผ ํธ์ถํด๋ ์ค์ ์ํฐํฐ๋ฅผ ๋ฐํ
๋ฐ๋๋ก ํ๋ก์ ์กฐํ ํ, ์ํฐํฐ ์กฐํ๋ฅผ ํด๋ ์ค์ ์ํฐํฐ๊ฐ ์๋ porxy ๋ฐํ
์ค์์ ์ํ์ผ ๋(em.clear() / em.close() / em.detach()), ํ๋ก์๋ฅผ ์ด๊ธฐํํ๋ฉด
LazyInitializationException
๋ฐ์
//ํ๋ก์ ์ธ์คํด์ค์ ์ด๊ธฐํ ์ฌ๋ถ ํ์ธ
emf.getPersistenceUnitUtil().isLoaded(entity);
//ํ๋ก์ ํด๋์ค ํ์ธ
entity.getClass();
//ํ๋ก์ ๊ฐ์ ์ด๊ธฐํ
org.hibernate.Hibernate.initialize(entity);
entity.getName() //JPA๋ ํธ์ถ ์ ์ด๊ธฐํ
์ฆ์ ๋ก๋ฉ๊ณผ ์ง์ฐ ๋ก๋ฉ
์ฆ์ ๋ก๋ฉ
@ManyToOne(fetch = FetchType.EAGER)
์ฐ๊ด ๊ฐ์ฒด๋ฅผ ์กฐ์ธ์ผ๋ก ํจ๊ป ์กฐํ
์ง์ฐ ๋ก๋ฉ
@ManyToOne(fetch = FetchType.LAZY)
์ฐ๊ด ๊ฐ์ฒด๋ฅผ ํ๋ก์๋ก ์กฐํ
ํ๋ก์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ์์ ์ ์ด๊ธฐํ(์กฐํ)
์ฆ์/์ง์ฐ ๋ก๋ฉ ์ฃผ์ ์ฌํญ
์ค๋ฌด์์๋
์ง์ฐ ๋ก๋ฉ๋ง ์ฌ์ฉ
ํ์์ฆ์ ๋ก๋ฉ ์ ์ฉ ์, ์ฐ๊ด ๊ด๊ณ๊ฐ ๋ง์์ง๊ฒ ๋๋ฉด ์์ํ์ง ๋ชปํ SQL ๋ฐ์
๋ํ, JPQL์์ N+1 ๋ฌธ์ ๋ฐ์
N+1 ๋ฌธ์ ํด๊ฒฐ์ JPQL fetch join ํน์ Entity Graph ๊ธฐ๋ฅ์ ์ฌ์ฉํ์.
@ManyToOne, @OneToOne์ default๋ ์ฆ์ ๋ก๋ฉ์ด๋ฏ๋ก LAZY ์ค์ ํ์
@OneToMany, @ManyToMany default : ์ง์ฐ ๋ก๋ฉ
์์์ฑ ์ ์ด
CASCADE
@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)
ํน์ ์ํฐํฐ๋ฅผ ์์ ์ํ๋ก ๋ง๋ค ๋, ์ฐ๊ด๋ ์ํฐํฐ๋ ํจ๊ป ์์ ์ํ๋ก ๋ง๋ค๊ณ ์ถ์ ๊ฒฝ์ฐ ์ฌ์ฉ
์ฐ๊ด๊ด๊ณ๋ฅผ ๋งคํํ๋ ๊ฒ๊ณผ๋ ์๋ฌด ๊ด๋ จ ์์.
์ํฐํฐ์ ์์ ์๊ฐ ํ๋์ผ ๋(๋จ์ผ ์ํฐํฐ์ ์ข ์์ , ๋ผ์ดํ ์ฌ์ดํด์ด ์ ์ฌ)๋ง ์ฌ์ฉํ๊ธฐ.
์ฌ๋ฌ ์ํฐํฐ์์ ๊ด๋ฆฌ๋๋ ๊ฒฝ์ฐ ์ฌ์ฉ X! (๊ด๋ฆฌ๊ฐ ํ๋ค์ด์ง๋ค..)
์ข ๋ฅ (๋ณดํต ALL, PERSIST, REMOVE ์์์ ์ฌ์ฉํ๋ฉฐ ๋ผ์ดํ ์ฌ์ดํด์ ๋์ผํ๊ฒ ์ ์ง)
ALL
: ๋ชจ๋ ์ ์ฉPERSIST
: ์์REMOVE
: ์ญ์ MERGE: ๋ณํฉ
REFRESH: REFRESH
DETACH: DETACH
๊ณ ์ ๊ฐ์ฒด
@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST, orphanRemoval = true)
๋ถ๋ชจ ์ํฐํฐ์ ์ฐ๊ด๊ด๊ณ๊ฐ ๋์ด์ง ์์ ์ํฐํฐ
์ฐธ์กฐ๊ฐ ์ ๊ฑฐ๋ ์ํฐํฐ๋ ๋ค๋ฅธ ๊ณณ์์ ์ฐธ์กฐํ์ง ์๋ ๊ณ ์ ๊ฐ์ฒด๋ก ๋ณด๊ณ ์ญ์
๊ณ ์ ๊ฐ์ฒด ์ ๊ฑฐ ์ค์ :
orphanRemoval = true
์์์ฑ ์ ์ด์ ๋์ผํ๊ฒ ํน์ ์ํฐํฐ๊ฐ ๊ฐ์ธ ์์ ํ ๋๋ง ์ฌ์ฉํ๊ธฐ
@OneToOne, @OneToMany๋ง ์ฌ์ฉ ๊ฐ๋ฅ
๋ถ๋ชจ ์ํฐํฐ๋ฅผ ์ ๊ฑฐํ ๋ CascadeType.REMOVE์ ๋์ผํ๊ฒ ์์๋ ํจ๊ป ์ ๊ฑฐ
์์์ฑ ์ ์ด์ ํจ๊ป ์ฌ์ฉํ ๊ฒฝ์ฐ (CascadeType.ALL + orphanRemovel=true)
๋ถ๋ชจ ์ํฐํฐ๋ฅผ ํตํด ์์์ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ ๊ฐ๋ฅ
DDD Aggregate Root ๊ฐ๋ ์ ๊ตฌํํ ๋ ์ ์ฉ
JPA Data Type
์ํฐํฐ ํ์
@Entity๋ก ์ ์ํ๋ ๊ฐ์ฒด
๋ฐ์ดํฐ๊ฐ ๋ณํด๋ ์๋ณ์๋ก ์ถ์ ๊ฐ๋ฅ
๊ฐ ํ์
์๋ณ์๊ฐ ์์ผ๋ฏ๋ก ๊ฐ ๋ณ๊ฒฝ ์ ์ถ์ ๋ถ๊ฐ
์๋ช ์ฃผ๊ธฐ๋ฅผ ์ํฐํฐ์ ์์กด
๋ฐ์ดํฐ ๊ณต์ X!
๊ธฐ๋ณธ๊ฐ ํ์
Java Basic Type : int, double..
Wrapper Class : Integer, Long..
String
์๋ฒ ๋๋ ํ์
์ปฌ๋ ์ ๊ฐ ํ์
์์ ํ๊ฒ ๋ถ๋ณ ๊ฐ์ฒด๋ก ๋ง๋ค๊ธฐ
Embedded Type
์๋ก์ด ๊ฐ ํ์ ์ ์ (๊ธฐ๋ณธ ๊ฐ ํ์ ์ ๋ชจ์์ ๋ง๋
๋ณตํฉ ๊ฐ ํ์
)@Embeddable
: ๊ฐ ํ์ ์ ์@Embedded
: ๊ฐ ํ์ ์ฌ์ฉ
์ฅ์
๊ฐ ํ์ ์ ๊ฐ์ฒด์งํฅ์ ์ผ๋ก ์ฌ์ฉ (์ฌ์ฌ์ฉ, ๋์ ์์ง๋ ..)
์๋ฒ ๋๋ ํ์ ํด๋์ค๋ง์ด ์ฌ์ฉํ๋ ์ ์ฉํ ๋ฉ์๋ ์์ฑ
์๋ฒ ๋๋ ํ์ ์ ์์ ํ ์ํฐํฐ์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์์กด
ํน์ง
์ ์ค๊ณ๋ ORM Application์ ๋งคํํ ํ ์ด๋ธ ์๋ณด๋ค ํด๋์ค ์๊ฐ ๋ ๋ง์
ํ ์ํฐํฐ์์ ๊ฐ์ ์๋ฒ ๋๋ ํ์ ์ ์ฌ์ฉํ์ฌ ์ปฌ๋ผ๋ช ์ด ์ค๋ณต๋ ๊ฒฝ์ฐ
@AttributeOverrides
,@AttributeOverride
๋ฅผ ์ฌ์ฉํด์ ์ปฌ๋ฌ๋ช ์์ฑ ์ฌ์ ์
์๋ฒ ๋๋ ํ์ ์ ๊ฐ์ด null์ด๋ฉด, ๋งคํ ์ปฌ๋ผ ๊ฐ ๋ชจ๋ null
@Embeddable
public class Address {
@Column(length = 10)
private String city;
@Column(length = 20)
private String street;
@Column(length = 5)
private String zipcode;
public Address() {}
private String fullAddress() {
return getCity() + " " + getStreet() + " " + getZipcode();
}
//..
}
@Entity
public class Member extends BaseEntity{
//..
@Embedded
private Address homeAddress;
@AttributeOverrides({
@AttributeOverride(name = "city",
column = @Column(name = "work_city")),
@AttributeOverride(name = "street",
column = @Column(name = "work_street")),
@AttributeOverride(name = "zipcode",
column = @Column(name = "work_zipcode")),
})
private Address workAddress;
}
๊ฐ ํ์
๋ถ๋ณ ๊ฐ์ฒด
๊ฐ ํ์ ์ ์ฌ๋ฌ ์ํฐํฐ์์ ๊ณต์ ํ๋ฉด Side Effect(๋ถ์์ฉ) ๋ฐ์
์ธ์คํด์ค ๊ฐ์ ๊ณต์ ํ๋ ๊ฒ์ ์ํํ๋ฏ๋ก
๊ฐ์ ๋ณต์ฌํด์ ์ฌ์ฉํ๊ธฐ
๊ฐ ํ์ ์ ๋ถ๋ณ ๊ฐ์ฒด๋ก ์ค๊ณ
ํ์ฌ ๊ฐ์ฒด ํ์ ์ ์์ ํ ์ ์๊ฒ ๋ง๋ค๊ธฐ๋ถ๋ณ ๊ฐ์ฒด: ์์ฑ ์์ ๋ฅผ ์ ์ธํ๊ณ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋ ๊ฐ์ฒด
์์ฑ์๋ก๋ง ๊ฐ์ ์ค์ ํ๊ณ , Setter๋ ์์ฑํ์ง ์๊ธฐ
Integer, String์ ์๋ฐ๊ฐ ์ ๊ณตํ๋ ๋ํ์ ์ธ ๋ถ๋ณ ๊ฐ์ฒด
Address address = new Address("city", "street", "10000"); Member member = new Member(); member.setUsername("member1"); member.setHomeAddress(address); em.persist(member); // ๊ฐ์ ๊ณต์ ํ์ง ์๊ณ ์๋ก ์์ฑ Address newAddress = new Address("NewCity", address.getStreet(), address.getZipcode()); member.setHomeAddress(newAddress);
๊ฐ ํ์ ๋น๊ต
๊ฐ ํ์ ๋น๊ต๋
equals
๋ฅผ ์ฌ์ฉํ ๋๋ฑ์ฑ ๋น๊ต๋ฅผ ์ฌ์ฉ๋์ผ์ฑ(identity) ๋น๊ต: ์ธ์คํด์ค ์ฐธ์กฐ ๊ฐ ๋น๊ต
==
๋๋ฑ์ฑ(equivalence) ๋น๊ต: ์ธ์คํด์ค ๊ฐ ๋น๊ต
equals()
๊ฐ ํ์ ์ equals() ๋ฉ์๋๋ฅผ ์ ์ ํ๊ฒ ์ฌ์ ์
ํ๋ก์ ์ฌ์ฉ์ ๊ณ ๋ คํ์ฌ getter() ์ฌ์ฉ ์ถ์ฒ
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Address address = (Address) o; return Objects.equals(getCity(), address.getCity()) && Objects.equals(getStreet(), address.getStreet()) && Objects.equals(getZipcode(), address.getZipcode()); } @Override public int hashCode() { return Objects.hash(getCity(), getStreet(), getZipcode()); }
๊ฐ ํ์ ์ปฌ๋ ์
๊ฐ ํ์ ์ ํ๋ ์ด์ ์ ์ฅ
ํ ๊ฒฝ์ฐ ์ฌ์ฉ์ ๋ ํธ ๋ฐ์ค์ ๊ฐ์ด ๊ฐ ๋ณ๊ฒฝ์ด ํ์ ์๋ ๋จ์ํ ๊ฒฝ์ฐ ์ฌ์ฉ
@ElementCollection
,@CollectionTable
๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์ปฌ๋ ์ ์ ๊ฐ์ ํ ์ด๋ธ์ ์ ์ฅํ ์ ์์ผ๋ฏ๋ก, ๋ณ๋์ ํ ์ด๋ธ์ด ํ์
๊ฐ ํ์ ์ปฌ๋ ์ ์ ์ํฐํฐ์ ์๋ช ์ฃผ๊ธฐ๊ฐ ๊ฐ์ (Casecade.ALL + orphanRemoval=true)
@Entity public class Member extends BaseEntity{ //... @ElementCollection @CollectionTable(name = "FAVORITE_FOOD", joinColumns = @JoinColumn(name = "member_id") ) @Column(name = "food_name") private Set<String> favoriteFoods = new HashSet<>(); @ElementCollection @CollectionTable(name = "ADDRESS", joinColumns = @JoinColumn(name = "member_id") ) private List<Address> addressHistory = new ArrayList<>(); }
์ ์ฅ
//.. member.getAddressHistory().add(new Address("city1", "street1", "zipCode1")); member.getAddressHistory().add(new Address("city2", "street2", "zipCode2"));
์กฐํ
default. FetchType.LAZY ์ ๋ต ์ฌ์ฉ
์์
findMember.getAddressHistory.remove(new Address("oldCity", "street", "12345")); findMember.getAddressHistory.add(new Address("newCity", "street", "12345")); fineMember.getFavoriteFoods().remove("์นํจ"); fineMember.getFavoriteFoods().add("ํ๋ฒ๊ฑฐ");
๊ฐ ํ์ ์ปฌ๋ ์ ์ ์ ์ฝ
๊ฐ ํ์ ์ปฌ๋ ์ ์ ์ํฐํฐ์ ๋ค๋ฅด๊ฒ
์๋ณ์ ๊ฐ๋ ์ด ์์ผ๋ฏ๋ก ๋ณ๊ฒฝ ์ ์ถ์ ์ด ์ด๋ ค์ด ํฐ ๋จ์
์กด์ฌ๋ณ๊ฒฝ ์ฌํญ์ด ๋ฐ์ํ๋ฉด, ์ฃผ์ธ ์ํฐํฐ์ ์ฐ๊ด๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๊ณ , ๊ฐ ํ์ ์ปฌ๋ ์ ์ ์๋ ๋ชจ๋ ๊ฐ์ ๋ค์ ์ ์ฅํ๋ ๋นํจ์จ์ ์ธ ๋์(์๋ณ์๊ฐ ์์ผ๋ฏ๋ก..)
๊ฐ ํ์ ์ปฌ๋ ์ ์ ๋งคํํ๋ ํ ์ด๋ธ์ ๋ชจ๋ ์ปฌ๋ผ์ ๋ฌถ์ด์ ๊ธฐ๋ณธ ํค๋ก ๊ตฌ์ฑํด์ผ ํจ
๊ฒฐ๋ก ์ ์ผ๋ก, ์ ๋ ํธ ๋ฐ์ค์ ๊ฐ์ด ๋ณ๊ฒฝ์ด ํ์ ์๋ ๋จ์ํ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ๋ฉด, ๊ฐ ํ์ ์ปฌ๋ ์ ๋์ ์ผ๋๋ค ๋จ๋ฐฉํฅ ๊ด๊ณ๋ฅผ ์ถ์ฒ (์๋ณ์, ์ง์์ ์ธ ๊ฐ ์ถ์ , ๋ณ๊ฒฝ์ด ํ์ํ ๊ฒฝ์ฐ)
@Embeddable public class Address { private String city; private String street; private String zipcode; //... } @Entity @Table(name = "ADDRESS") public class AddressEntity { @Id @GeneratedValue private Long id; private Address address; public AddressEntity(String city, String street, String zipcode) { this.address = new Address(city, street, zipcode); } //.. } @Entity public class Member extends BaseEntity{ //... @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "member_id") private List<AddressEntity> addressHistory = new ArrayList<>(); }
๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ ์ธ์ด
JPQL (Java Persistence Query Language)
SQL์ ์ถ์ํํ ๊ฐ์ฒด ์งํฅ ์ฟผ๋ฆฌ ์ธ์ด(ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์กด X)
ํ ์ด๋ธ์ด ์๋ ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ์ฟผ๋ฆฌ
๋ฌธ์๋ก JPQL์ด ์์ฑ๋๋ค๋ณด๋ ๋์ ์ฟผ๋ฆฌ ์์ฑ์ด ์ด๋ ค์ด ๋จ์
List<Member> result = em.createQuery( "select m From Member m where m.name like โ%park%'", Member.class ).getResultList();
QueryDSL
๋ฌธ์๊ฐ ์๋ ์๋ฐ์ฝ๋๋ก JPQL ์์ฑ
์ปดํ์ผ ์์ ์ ๋ฌธ๋ฒ ์ค๋ฅ ์ฒดํฌs
ํธ๋ฆฌํ ๋์ ์ฟผ๋ฆฌ ์์ฑ
JPQL ๋น๋ ์ญํ
JPAFactoryQuery query = new JPAQueryFactory(em); QMember m = QMember.member; List<Member> list = query.selectFrom(m) .where(m.age.gt(18)) .orderBy(m.name.desc()) .fetch();
๋ค์ดํฐ๋ธ SQL
JPQL๋ก ํด๊ฒฐํ ์ ์๋ ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์กด์ ์ธ ๊ธฐ๋ฅ ์ฌ์ฉ ์ SQL์ ์ง์ ์์ฑ
String sql = "SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = โkimโ"; List<Member> resultList = em.createNativeQuery(sql, Member.class).getResultList();
๊ธฐํ
JPA๋ฅผ ์ฌ์ฉํ๋ฉด์ JDBC API, SpringJdbcTemplate, MyBatis ๋ฑ์ ํจ๊ป ์ฌ์ฉ ๊ฐ๋ฅ
๋จ, ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ ์ ํ ์์ (SQL์ ์คํํ๊ธฐ ์ง์ )์ ๊ฐ์ ํ๋ฌ์ ํ์ (em.flush())
๊ธฐ๋ณธ ๋ฌธ๋ฒ
๋ฐํ ํ์
TypeQuery
: ๋ฐํ ํ์ ์ด ๋ช ํํ ๋ ์ฌ์ฉQuery
: ๋ฐํ ํ์ ์ด ๋ช ํํ์ง ์์ ๋ ์ฌ์ฉ
์กฐํ
query.getResultList()
: ๊ฒฐ๊ณผ๊ฐ ํ๋ ์ด์์ผ ๊ฒฝ์ฐ (๋ฆฌ์คํธ ๋ฐํ)๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด ๋น ๋ฆฌ์คํธ ๋ฐํ
query.getSingleResult()
: ๊ฒฐ๊ณผ๊ฐ ์ ํํ ํ๋์ผ ๊ฒฝ์ฐ (๋จ์ผ ๊ฐ์ฒด ๋ฐํ)๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด: javax.persistence.NoResultException
๋ ์ด์์ด๋ฉด: javax.persistence.NonUniqueResultException
ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ
Member result = em.createQuery("select m from Member m where m.username = :username", Member.class)
.setParameter("username", "member1")
.getSingleResult();
ํ๋ก์ ์
SELECT ์ ์ ์กฐํํ ๋์์ ์ง์ ํ๋ ๋ฐฉ์
์ํฐํฐ ํ๋ก์ ์ , ์๋ฒ ๋๋ ํ์ ํ๋ก์ ์ , ์ค์นผ๋ผ ํ์ ํ๋ก์ ์
์ค์นผ๋ผ ํ์ ํ๋ก์ ์ ์ ๊ฒฝ์ฐ ์ฌ๋ฌ ๊ฐ ์กฐํ ์ DTO ์กฐํ ์ถ์ฒ
List<MemberDto> result = em.createQuery("select new jpql.MemberDto(m.username, m.age) from Member m", MemberDto.class) .getResultList();
ํ์ด์ง
setFirstResult(int startPosition) : ์กฐํ ์์ ์์น
setMaxResults(int maxResult) : ์กฐํํ ๋ฐ์ดํฐ ์
String jpql = "select m from Member m order by m.name desc";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setFirstResult(0)
.setMaxResults(10)
.getResultList();
์กฐ์ธ
๋ด๋ถ ์กฐ์ธ:
SELECT m FROM Member m
[INNER] JOIN
m.team t
์ธ๋ถ ์กฐ์ธ
SELECT m FROM Member m
LEFT [OUTER] JOIN
m.team t
์ธํ ์กฐ์ธ
select count(m)
from Member m, Team t
where m.username = t.name
.
Join On (JPA 2.1, Hibernate 5.1 ์ด์)
์กฐ์ธ ๋์ ํํฐ๋ง
SELECT m, t FROM Member m LEFT JOIN m.team t
on t.name = 'A'
์ฐ๊ด๊ด๊ณ๊ฐ ์๋ ์ํฐํฐ ์ธ๋ถ ์กฐ์ธ
SELECT m, t FROM Member m LEFT JOIN Team t
on m.username = t.name
์๋ธ ์ฟผ๋ฆฌ
select m from Member m
where m.age > (select avg(m2.age) from Member m2)
์ง์ ํจ์
[NOT] EXISTS (Subquery): ์๋ธ์ฟผ๋ฆฌ์ ๊ฒฐ๊ณผ๊ฐ ์กด์ฌํ๋ฉด ์ฐธ
{ALL | ANY | SOME} (Subquery)
ALL: ๋ชจ๋ ๋ง์กฑํ๋ฉด ์ฐธ
ANY, SOME: ์กฐ๊ฑด์ ํ๋๋ผ๋ ๋ง์กฑํ๋ฉด ์ฐธ
[NOT] IN (Subquery): ์๋ธ์ฟผ๋ฆฌ์ ๊ฒฐ๊ณผ ์ค ํ๋๋ผ๋ ๊ฐ์ ๊ฒ์ด ์์ผ๋ฉด ์ฐธ
.
ํ๊ณ
JPA๋ WHERE, HAVING ์ ์์๋ง ์๋ธ ์ฟผ๋ฆฌ ์ฌ์ฉ ๊ฐ๋ฅ
Hibernate๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ SELECT ์ ๋ ๊ฐ๋ฅ
FROM ์ ์ ์๋ธ ์ฟผ๋ฆฌ๋ ํ์ฌ JPQL์์ ๋ถ๊ฐ๋ฅ (์กฐ์ธ์ผ๋ก ํ์ด๋ณด๊ธฐ)
ํ์ ํํ
๋ฌธ์
: 'She''s'์ซ์
: 10L(Long), 10D(Double), 10F(Float)Boolean
: true, falseENUM
: jpql.MemberType.Admin (ํจํค์ง๋ช ํฌํจ) or query.setParameter()Member result = em.createQuery("select m from Member m where m.type = :userType", Member.class) .setParameter("userType", MemberType.ADMIN) .getResultList();
์ํฐํฐ ํ์
: ์์ ๊ด๊ณ์์ ์ฌ์ฉ์กฐํ ๋์์ ํน์ ์์์ผ๋ก ํ์
--JPQL select i from Item i where type(i) IN (Book, Movie) --SQL select i from i where i.DTYPE in ('Book', 'Movie')
ํ์ ์บ์คํ
--JPQL select i from Item i where treat(i as Book).auther = 'kim' --SQL select i.* from Item i where i.DTYPE = 'B' and i.auther = 'kim'
COALESCE
: ํน์ ์ปฌ๋ผ์ด Null์ผ ๊ฒฝ์ฐ ๋์ฒด ๊ฐ ๋ฐํselect coalesce(m.username,'์ด๋ฆ ์๋ ํ์') from Member m
NULLIF
: ์ง์ ๋ ๋ ์์ด ๊ฐ์ผ๋ฉด Null ๋ฐํselect NULLIF(m.username, '๊ด๋ฆฌ์') from Member m
์ค๊ธ ๋ฌธ๋ฒ
๊ฒฝ๋ก ํํ์
์ํ ํ๋
(m.username): ๊ฒฝ๋ก ํ์์ ์ข ์ (ํ์ ๋ถ๊ฐ)๋จ์ผ ๊ฐ ์ฐ๊ด ๊ฒฝ๋ก
(m.team): ๋ฌต์์ ๋ด๋ถ ์กฐ์ธ(inner join) ๋ฐ์ (ํ์ ๊ฐ๋ฅ)์ปฌ๋ ์ ๊ฐ ์ฐ๊ด ๊ฒฝ๋ก
(m.orders): ๋ฌต์์ ๋ด๋ถ ์กฐ์ธ ๋ฐ์ (ํ์ ๋ถ๊ฐ)๋ช ์์ ์กฐ์ธ์ ํตํด ๋ณ์นญ์ผ๋ก ํ์ ๊ฐ๋ฅ
.
๋ช ์์ ์กฐ์ธ
: JOIN ํค์๋๋ฅผ ์ง์ ์ฌ์ฉ์กฐ์ธ์ด ๋ฐ์ํ๋ ์ํฉ์ ํ ๋์ ํ์ ํ ์ ์์ด์ ์ฟผ๋ฆฌ ํ๋์ด ํธ๋ฆฌ
์กฐ์ธ์ SQL ํ๋์ ์ค์ํ ํฌ์ธํธ์ด๋ฏ๋ก, ๊ฐ๊ธ์ ๋ช ์์ ์กฐ์ธ์ ์ฌ์ฉํ์ !!
select m, t from Member m join m.team t
๋ฌต์์ ์กฐ์ธ
: ๊ฒฝ๋ก ํํ์์ ์ํด ๋ฌต์์ ์ผ๋ก ์กฐ์ธ ๋ฐ์๋ด๋ถ ์กฐ์ธ๋ง ๊ฐ๋ฅ
์กฐ์ธ ์ฟผ๋ฆฌ๋ฅผ ํ์ ํ๊ธฐ ์ด๋ ค์
select m.team from Member m
์ํฐํฐ ์ง์ ์ฌ์ฉ
JPQL์์ ์ํฐํฐ๋ฅผ ์ง์ ์ฌ์ฉํ๋ฉด SQL์์ ํด๋น ์ํฐํฐ์ ๊ธฐ๋ณธ ํค ๊ฐ์ ์ฌ์ฉ
Named Query
๋ฏธ๋ฆฌ ์ ์ํด๋๊ณ ์ฌ์ฉํ๋ JPQL ์ ์ ์ฟผ๋ฆฌ
์ ํ๋ฆฌ์ผ์ด์ ๋ก๋ฉ ์์ ์ ์ฟผ๋ฆฌ ๊ฒ์ฆ ๋ฐ ์บ์ฑ ํ ์ฌ์ฌ์ฉ
๋ฒํฌ ์ฐ์ฐ
ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ์ฌ๋ฌ ์ํฐํฐ ๋ณ๊ฒฝ (UPDATE, DELETE)
executeUpdate()๋ก ์ํฅ์ ๋ฐ์ ์ํฐํฐ ์ ํ์ธ ๊ฐ๋ฅ
๋ฒํฌ ์ฐ์ฐ์
์์์ฑ ์ปจํ ์คํธ๋ฅผ ๋ฌด์ํ๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ์ฟผ๋ฆฌ๋ฅผ ์ ๋ฌ
ํ๋ฏ๋ก๋ฒํฌ ์ฐ์ฐ์ ๋จผ์ ์คํํ๊ฑฐ๋
๋ฒํฌ ์ฐ์ฐ ์ํ ํ ์์์ฑ ์ปจํ ์คํธ ์ด๊ธฐํ (em.clear())
@Entity
@NamedQuery(
name = "Member.findByUsername",
query="select m from Member m where m.username = :username")
public class Member {
...
}
//
List<Member> resultList =
em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "ํ์1")
.getResultList();
ํ์น ์กฐ์ธ
Fetch Join
JPQL
์ฑ๋ฅ ์ต์ ํ
๋ฅผ ์ํด ์ ๊ณต์ฟผ๋ฆฌ ํ ๋ฒ์
์ฐ๊ด๋ ์ํฐํฐ๋ ์ปฌ๋ ์ ์ ํจ๊ป ์กฐํ
(์ฆ์ ๋ก๋ฉ ์ฐ์ ์ ์ฉ)์ผ๋ฐ ์กฐ์ธ์์๋ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ํจ๊ป ์กฐํํ์ง ์์
N + 1 ์ด์์ ํด๊ฒฐ ๋ฐฉ๋ฒ
[ LEFT [OUTER] / INNER ] JOIN FETCH
SELECT m FROM Member m JOIN FETCH m.team;
Collection Fetch Join
์ผ๋๋ค ๊ด๊ณ์์์ ํ์น ์กฐ์ธ
-- JPQL SELECT t FROM Team t JOIN FETCH t.members WHERE t.name = 'ํA' -- SQL SELECT T.*, M.* FROM TEAM T INNER JOIN MEMBER M ON T.ID=M.TEAM_ID WHERE T.NAME = 'ํA
์ผ๋๋ค ๊ด๊ณ์์์ N+1 ๋ฌธ์ ๋
batchSize ์ค์ ์ผ๋ก ํด๊ฒฐ ๊ฐ๋ฅ
LAZY ๋์ ์, IN ์ฟผ๋ฆฌ๋ก size ๋งํผ ํ ๋ฒ์ ์กฐํ
๊ฐ๋ณ ์ฑ์
@BatchSize(size = 100) @OneToMany(mappedBy = "team") List<Member> members = new ArrayList<Member>();
Global ์ค์
hibernate.default_batch_fetch_size: 100
SQL
select * from member where member.team_id in (?, ?, ?..)
DISTINCT
JPQL์์ DISTINCT๊ฐ ์ ๊ณตํ๋ ๊ธฐ๋ฅ
์ฟผ๋ฆฌ์ DISTINCT ์ถ๊ฐ
์ ํ๋ฆฌ์ผ์ด์ ์์ ์ค๋ณต ์ํฐํฐ ์ ๊ฑฐ
ํ๊ณ
ํ์น ์กฐ์ธ ๋์์๋ ๋ณ์นญ ๋ถ๊ฐ
๊ฐ์ฒด ๊ทธ๋ํ ์ฌ์(N์ ํด๋นํ๋ ๋ชจ๋ ๋ฐ์ดํฐ ์กฐํ๋ฅผ ๊ธฐ๋)๊ณผ ๋ง์ง ์์
t.members์์ ์กฐ๊ฑด์ ๊ฑธ๊ณ ์ถ๋ค๋ฉด, member๋ฅผ select์ ์์ ์ฌ์ฉํ์.
์ปฌ๋ ์ ์ ํ ๊ฐ๋ง ํ์น ์กฐ์ธ ๊ฐ๋ฅ
์ปฌ๋ ์ ํ์น ์กฐ์ธ์ ํ๋ฉด ํ์ด์ง API ์ฌ์ฉ ๋ถ๊ฐ
๋จ์ผ ๊ฐ ์ฐ๊ด ํ๋(1:1/N:1)๋ ํ์น ์กฐ์ธ์ ํด๋ ํ์ด์ง ๊ฐ๋ฅ
์ปฌ๋ ์ ํ์น ์กฐ์ธ์ผ๋ก ์ปฌ๋ ์ ๋ฐ์ดํฐ๊ฐ ์๋ฆฌ๋ ํ์ ๋ฐ์
Last updated