๐ ์๋ฐ ORM ํ์ค JPA ํ๋ก๊ทธ๋๋ฐ
์๋ฐ ORM ํ์ค JPA ํ๋ก๊ทธ๋๋ฐ ์ฑ ์ ์ฝ๊ณ ๋ด์ฉ์ ์์ฃผ ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํ ๊ธ์ ๋๋ค. ์ฑ ์๋ ์์ธํ ์ค๋ช ๊ณผ ์์ ๊ฐ ๋ง์ผ๋ ๊ผญ ๊ตฌ์ ํด์ ์ฝ๋๊ฒ์ ์ถ์ฒํฉ๋๋ค~๐
1. JPA ์๊ฐ
1.1 SQL์ ์ง์ ๋ค๋ฃฐ ๋ ๋ฐ์ํ๋ ๋ฌธ์ ์
- ์๋ฐ๋ก ๊ฐ๋ฐํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ถ๋ถ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ฐ์ดํฐ ์ ์ฅ์๋ก ์ฌ์ฉํ๋ค.
- JDBC API๋ฅผ ์ฌ์ฉํด SQL์ ์ ๋ฌํ๋ค.
1.1.1 ๋ฐ๋ณต, ๋ฐ๋ณต ๊ทธ๋ฆฌ๊ณ ๋ฐ๋ณต
๊ฐ์ฒด๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ CURD ํ๋ ค๋ฉด ๋๋ฌด๋ง์ SQL๊ณผ JDBC API๋ฅผ ์ฝ๋๋ก ์์ฑํด์ผ ํ๋ค. ํ ์ด๋ธ์ด ์ฌ๋ฌ๊ฐ๋ผ๋ฉด ๋ฌด์ํ ๋ง์ SQL์ ์์ฑํด์ผ ํ๋ค.
1.1.2 SQL์ ์์กด์ ์ธ ๊ฐ๋ฐ
์ ํ๋ฆฌ์ผ์ด์ ์์ SQL์ ์ง์ ๋ค๋ฃฐ ๋ ๋ฐ์ํ๋ ๋ฌธ์ ์
- ์ง์ ํ ์๋ฏธ์ ๊ณ์ธต ๋ถํ ์ด ์ด๋ ต๋ค.
- ์ํฐํฐ๋ฅผ ์ ๋ขฐํ ์ ์๋ค.
- SQL์ ์์กด์ ์ธ ๊ฐ๋ฐ์ ํผํ๊ธฐ ์ด๋ ต๋ค.
1.1.3 JPA์ ๋ฌธ์ ํด๊ฒฐ
JPA๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ์ฒด๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๊ณ ๊ด๋ฆฌํ ๋, ๊ฐ๋ฐ์๊ฐ ์ง์ SQL์ ์์ฑํ๋ ๊ฒ์ด ์๋๋ผ JPA๊ฐ ์ ๊ณตํ๋ API๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
// ์ ์ฅ
jpa.persist(member);
// ์กฐํ
String memberId = "helloId"
Member member = jpa.find(Member.class, memberId);
// ์์
Member member = jpa.find(Member.class, memberId);
member.setName("์ด๋ฆ๋ณ๊ฒฝ"); // jpa๋ update ๋ฉ์๋๊ฐ ๋ฐ๋ก ์๋ค.
// ์ฐ๊ด๋ ๊ฐ์ฒด ์กฐํ
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
1.2 ํจ๋ฌ๋ค์ ๋ถ์ผ์น
- ๊ฐ์ฒด์ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์งํฅํ๋ ๋ชฉ์ ์ด ์๋ก ๋ค๋ฅด๋ค.
- ๊ธฐ๋ฅ๊ณผ ํํ ๋ฐฉ๋ฒ๋ ๋ค๋ฅด๋ค.
- ๊ฐ์ฒด ๊ตฌ์กฐ๋ฅผ ํ ์ด๋ธ ๊ตฌ์กฐ์ ์ ์ฅํ๋ ๋ฐ๋ ํ๊ณ๊ฐ ์๋ค.
1.2.1 ์์
๊ฐ์ฒด๋ ์์์ด๋ผ๋ ๊ธฐ๋ฅ์ ๊ฐ์ง๊ณ ์์ง๋ง ํ ์ด๋ธ์ ์์์ด๋ผ๋ ๊ธฐ๋ฅ์ด ์๋ค.(์ผ๋ถ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์์ ๊ธฐ๋ฅ์ ์ง์ํ์ง๋ง ๊ฐ์ฒด์ ์์๊ณผ๋ ์ฝ๊ฐ ๋ค๋ฅด๋ค.)
๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ธ๋ง์์๋ ์ํผํ์ ์๋ธํ์ ๊ด๊ณ๋ฅผ ์ฌ์ฉํ๋ฉด ์ ์ฌํ๊ฒ ๋ง๋ค ์ ์๋ค.
์์ ๊ตฌ์กฐ๋ฅผ JDBC API๋ฅผ ์ฌ์ฉํด์ ๊ตฌํํ๋ ค๋ฉด ์์ฑํด์ผ ํ ์ฝ๋๋์ด ๋ง๋ง์น ์๋ค.
- INSERT INTO ITEMโฆ
- INSERT INTO ALBUMโฆ
JPA์ ์์
JPA๋ ์์๊ณผ ๊ด๋ จ๋ ํจ๋ฌ๋ค์ ๋ถ์ผ์น ๋ฌธ์ ๋ฅผ ๊ฐ๋ฐ์ ๋์ ํด๊ฒฐํด ์ค๋ค.
// ์ ์ฅ
jpa.persist(album);
// JPA์์ ์คํ ํด์ฃผ๋ SQL
INSERT INTO ITEM...
INSERT INTO ALBUM...
1.2.2 ์ฐ๊ด๊ด๊ณ
- ๊ฐ์ฒด๋ ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํด ๋ค๋ฅธ ๊ฐ์ฒด์ ์ฐ๊ด๊ด๊ณ๋ฅผ ๊ฐ์ง๋ค. ์ฐธ์กฐ์ ์ ๊ทผํด์ ์ฐ๊ด๋ ๊ฐ์ฒด๋ฅผ ์กฐํํ๋ค.
- ํ ์ด๋ธ์ ์ธ๋ ํค๋ฅผ ์ฌ์ฉํด ๋ค๋ฅธ ํ ์ด๋ธ๊ณผ ์ฐ๊ด๊ด๊ณ๋ฅผ ๊ฐ์ง๋ค. ์กฐ์ธ์ ์ฌ์ฉํด์ ์ฐ๊ด๋ ํ ์ด๋ธ์ ์กฐํํ๋ค.
- ๊ฐ์ฒด๋ ์ฐธ์กฐ๊ฐ ์๋ ๋ฐฉํฅ์ผ๋ก๋ง ์กฐํํ ์ ์๋ค.
๊ฐ์ฒด๋ฅผ ํ ์ด๋ธ์ ๋ง์ถ์ด ๋ชจ๋ธ๋ง
// ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ฌ์ฉํ๋ ๋ฐฉ์์ ๋ง์ถ๋ฉด ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ํตํด ์กฐํํ ์ ์๋ค.
class Member {
String id; // MEMBER_ID ์ปฌ๋ผ ์ฌ์ฉ
Long teamId; // TEAM_ID FK ์ปฌ๋ผ ์ฌ์ฉ
}
๊ฐ์ฒด์งํฅ ๋ชจ๋ธ๋ง
// ํจ๋ฌ๋ค์ ๋ถ์ผ์น๋ฅผ ํด๊ฒฐํ๋ ค๊ณ ์๋ชจํ๋ ์ฝ๋๊ฐ ๋ง๋ค.
class Member {
String id; // MEMBER_ID ์ปฌ๋ผ ์ฌ์ฉ
Team team; // ์ฐธ์กฐ๋ก ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ๋๋ค.
}
// ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ
member.getId();
member.getTeam.getId(); // TEAM_ID FK๋ฅผ ์ง์ ๊ตฌํด์ผ ํจ
...์๋ต...
// ์กฐํ
SQL ์คํ
Member member = new Member ();
Team team = new Team();
...์๋ต...
member.setTeam(team); // ์ง์ ํ์๊ณผ ํ ๊ด๊ณ ์ค์
JPA์ ์ฐ๊ด๊ด๊ณ
// JPA๋ ์ฐ๊ด๊ด๊ณ์ ๊ด๋ จ๋ ํจ๋ฌ๋ค์ ๋ถ์ผ์น ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด์ค๋ค.
// ์ ์ฅ
member.setTeam(team);
jpa.persist(member); // TEAM_ID FK๋ฅผ ์ง์ ์ง์ ํ ํ์ ์์
// ์กฐํ
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam(); // ๋ฐ๋ก ์ฌ์ฉ ๊ฐ๋ฅ
1.2.3 ๊ฐ์ฒด ๊ทธ๋ํ ํ์
๊ฐ์ฒด์์ ํ์์ ์์๋ ํ์ ์กฐํํ ๋๋ ๋ค์์ฒ๋ผ ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํด์ ์ฐ๊ด๋ ํ์ ์ฐพ์ผ๋ฉด ๋๋๋ฐ, ์ด๊ฒ์ ๊ฐ์ฒด ๊ทธ๋ํ ํ์์ด๋ผ ํ๋ค.
// ๊ฐ์ฒด ๊ทธ๋ํ ํ์
member.getOrder().getOrderItem()...
// ๋ฌธ์ ๋ฐ์
member.getOrder(); // ๋ง์ฝ null์ด๋ผ๋ฉด?
SQL์ ์ง์ ๋ค๋ฃจ๋ฉด ์ฒ์ ์คํํ๋ SQL์ ๋ฐ๋ผ ๊ฐ์ฒด ๊ทธ๋ํ๋ฅผ ์ด๋๊น์ง ํ์ํ ์ ์๋์ง ์ ํด์ง๋ค. ์ด๋ ๊ฐ์ฒด ๊ทธ๋ํ๊ฐ ์ธ์ ๋์ด์ง์ง ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ํจ๋ถ๋ก ํ์ํ ์ ์๋ค.
JPA์ ๊ฐ์ฒด ๊ทธ๋ํ ํ์
JPA๋ ์ฐ๊ด๋ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ์์ ์ ์ ์ ํ SELECT SQL์ ์คํํ๋ค(์ง์ฐ๋ก๋ฉ). ๋ฐ๋ผ์ JPA๋ฅผ ์ฌ์ฉํ๋ฉด ์ฐ๊ด๋ ๊ฐ์ฒด๋ฅผ ์ ๋ขฐํ๊ณ ์กฐํํ ์ ์๋ค.
1.2.4 ๋น๊ต
- ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ๊ธฐ๋ณธ ํค์ ๊ฐ์ผ๋ก ๊ฐ row๋ฅผ ๊ตฌ๋ถ
- ๊ฐ์ฒด๋ ๋์ผ์ฑ, ๋๋ฑ์ฑ ๋น๊ต.
- ๋์ผ์ฑ์ == ๋น๊ต. ๊ฐ์ฒด ์ธ์คํด์ค์ ์ฃผ์ ๊ฐ์ ๋น๊ตํ๋ค.
- ๋๋ฑ์ฑ์ equals() ๋น๊ต. ๊ฐ์ฒด ๋ด๋ถ์ ๊ฐ์ ๋น๊ตํ๋ค.
JDBC API ๋น๊ต
String memberId = "100";
Member member1 = memberDAO.getMember(memberID);
Member member2 = memberDAO.getMember(memberID);
// false
member1 == member2;
JPA ๋น๊ต
String memberId = "100";
Member member1 = jpa.find(memberID);
Member member2 = jpa.find(memberID);
// true
member1 == member2;
1.2.5 ์ ๋ฆฌ
- ๊ฐ์ฒด ๋ชจ๋ธ๊ณผ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ธ์ ์งํฅํ๋ ํจ๋ฌ๋ค์์ด ์๋ก ๋ค๋ฅด๋ค.
- ์ด ํจ๋ฌ๋ค์ ์ฐจ์ด๋ฅผ ๊ทน๋ณตํ๋ ค๊ณ ๊ฐ๋ฐ์๊ฐ ๋๋ฌด ๋ง์ ์๊ฐ๊ณผ ์ฝ๋๋ฅผ ์๋นํ๋ค.
- ๊ฒฐ๊ตญ, ๊ฐ์ฒด ๋ชจ๋ธ๋ง์ ํ์ ์๊ณ ์ ์ ๋ฐ์ดํฐ ์ค์ฌ์ ๋ชจ๋ธ๋ก ๋ณํด๊ฐ๋ค.
- JPA๋ ํจ๋ฌ๋ค์ ๋ถ์ผ์น ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ , ์ ๊ตํ ๊ฐ์ฒด ๋ชจ๋ธ๋ง์ ์ ์งํ๊ฒ ๋์์ค๋ค.
1.3 JPA๋ ๋ฌด์์ธ๊ฐ?
JPA๋?
- JPA(java persistence API)๋ ์๋ฐ์ง์ ORM ๊ธฐ์ ํ์ค์ด๋ค. ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ JDBC ์ฌ์ด์์ ๋์ํ๋ค.
ORM์ด๋?
- ORM(Object-Relational Mapping)์ ๊ฐ์ฒด์ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋งคํํ๋ค๋ ๋ป์ด๋ค.
- ๋ค์ํ ORM ํ๋ ์์ํฌ๋ค์ด ์๋๋ฐ ์๋ฐ ์ง์์์๋ ํ์ด๋ฒ๋ค์ดํธ ํ๋ ์์ํฌ๊ฐ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ค.
1.3.1 JPA ์๊ฐ
- JPA๋ ์๋ฐ ORM ๊ธฐ์ ์ ๋ํ API ํ์ค ๋ช ์ธ๋ค.
- ์ฝ๊ฒ ์ด์ผ๊ธฐํด์ ์ธํฐํ์ด์ค๋ฅผ ๋ชจ์๋ ๊ฒ์ด๋ค.
- ๋ฐ๋ผ์ JPA๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด JPA๋ฅผ ๊ตฌํํ ORM ํ๋ ์์ํฌ๋ฅผ ์ ํํด์ผ ํ๋ค.
- ORM ํ๋ ์์ํฌ ์ค ํ์ด๋ฒ๋ค์ดํธ๊ฐ ๊ฐ์ฅ ๋์ค์ ์ด๋ค.
1.3.2 ์ JPA๋ฅผ ์ฌ์ฉํด์ผ ํ๋๊ฐ?
- ์์ฐ์ฑ
- ์ ์ง๋ณด์
- ํจ๋ฌ๋ค์ ๋ถ์ผ์น ํด๊ฒฐ
- ์ฑ๋ฅ (์บ์)
- ๋ฐ์ดํฐ ์ ๊ทผ ์ถ์ํ์ ๋ฒค๋ ๋
๋ฆฝ์ฑ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ง๋ค ์ฌ์ฉ๋ฒ์ด ๋ค๋ฅด๋ค (์: ํ์ด์ง ์ฟผ๋ฆฌ)
- JPA์๊ฒ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ง ์๋ ค์ฃผ๋ฉด ์๋์ผ๋ก ํด๊ฒฐ๋๋ค
- ํ์ค
- ์๋ฐ ์ง์ ORM ๊ธฐ์ ํ์ค
- ํ์ค์ ์ฌ์ฉํ๋ฉด ๋ค๋ฅธ ๊ตฌํ ๊ธฐ์ ๋ก ์์ฝ๊ฒ ๋ณ๊ฒฝ ๊ฐ๋ฅ
1.4 ์ ๋ฆฌ
- SQL์ ์ง์ ๋ค๋ฃฐ ๋ ๋ฐ์ํ๋ ๋ค์ํ ๋ฌธ์
- ๊ฐ์ฒด์งํฅ ์ธ์ด์ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ด์ ํจ๋ฌ๋ค์ ๋ถ์ผ์น ๋ฌธ์
- JPA๊ฐ ๊ฐ ๋ฌธ์ ๋ฅผ ์ด๋ป๊ฒ ํด๊ฒฐํ๋์ง
- JPA๊ฐ ๋ฌด์์ธ์ง
- JPA ์ฅ์
2. JPA ์์
2.1 ์ดํด๋ฆฝ์ค ์ค์น์ ํ๋ก์ ํธ ๋ถ๋ฌ์ค๊ธฐ
2.2 H2 ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์น
2.3 ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํ๋ก์ ํธ ๊ตฌ์กฐ
2.4 ๊ฐ์ฒด ๋งคํ ์์
// ๊ฐ์ฒด ๋งคํ ์
@Entity
@Table(name="MEMBER")
public class Member {
@Id
@Column(name = "ID")
private String id;
@Column(name = "NAME")
private String username;
private Integer age;
...
}
์ด๋ ธํ ์ด์ | ์ค๋ช |
---|---|
@Entity | ์ด ํด๋์ค๋ฅผ ํ ์ด๋ธ๊ณผ ๋งคํํ๋ค๊ณ JPA์๊ฒ ์๋ ค์ค๋ค. @Entity๊ฐ ์ฌ์ฉ๋ ํด๋์ค๋ฅผ ์ํฐํฐ ํด๋์ค๋ผ ํ๋ค. |
@Table | ์ํฐํฐ ํด๋์ค์ ๋งคํํ ํ ์ด๋ธ ์ ๋ณด๋ฅผ ์๋ ค์ค๋ค. ์ด ์ด๋ ธํ ์ด์ ์ ์๋ตํ๋ฉด ํด๋์ค ์ด๋ฆ์ ํ ์ด๋ธ ์ด๋ฆ์ผ๋ก ๋งคํํ๋ค. (์ ํํ๋ ์ํฐํฐ ์ด๋ฆ์ ์ฌ์ฉํจ) |
@Id | ์ํฐํฐ ํด๋์ค์ ํ๋๋ฅผ ํ ์ด๋ธ ๊ธฐ๋ณธํค(primary key)์ ๋งคํํ๋ค. @Id๊ฐ ์ฌ์ฉ๋ ํ๋๋ฅผ ์๋ณ์ ํ๋๋ผ ํ๋ค. |
@Column | ํ๋๋ฅผ ์ปฌ๋ผ์ ๋งคํํ๋ค. |
๋งคํ ์ ๋ณด๊ฐ ์๋ ํ๋ | ํ๋๋ช ์ ์ฌ์ฉํด์ ์ปฌ๋ผ๋ช ์ผ๋ก ๋งคํํ๋ค. ๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋ฉด @Column(name=โAGEโ)์ฒ๋ผ ๋ช ์์ ์ผ๋ก ๋งคํํด์ผ ํ๋ค. |
2.5 ๊ฐ์ฒด ๋งคํ ์์
2.5.1 ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐฉ์ธ
๋ฐ์ดํฐ๋ฒ ์ด์ค๋ง๋ค SQL ๋ฌธ๋ฒ๊ณผ ํจ์๊ฐ ์กฐ๊ธ์ฉ ๋ค๋ฅด๋ค๋ ๋ฌธ์ ์ ์ด ์๋ค.
- ๋ฐ์ดํฐ ํ์ : ๊ฐ๋ณ ๋ฌธ์ ํ์ ์ผ๋ก MySQL์ VARCHAR, ์ค๋ผํด์ VARCHAR2 ์ฌ์ฉ
- ๋ค๋ฅธ ํจ์๋ช : ๋ฌธ์์ด์ ์๋ฅด๋ ํจ์๋ก SQL ํ์ค์ SUBSTRING(), ์ค๋ผํด์ SUBSTR()
- ํ์ด์ง ์ฒ๋ฆฌ: MySQL์ LIMIT, ์ค๋ผํด์ ROWNUM
๋ฐ์ดํฐ๋ฒ ์ด์ค๋ง์ ๊ณ ์ ํ ๊ธฐ๋ฅ์ JPA์์๋ ๋ฐฉ์ธ(Dialect)์ด๋ผ ํ๋ค. ํ์ด๋ฒ๋ค์ดํธ๋ฅผ ํฌํจํ ๋๋ถ๋ถ์ JPA ๊ตฌํ์ฒด๋ค์ ์์ ๊ฐ์ ๋ฌธ์ ์ ์ ํด๊ฒฐํ๋ ค๊ณ ๋ค์ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐฉ์ธ ํด๋์ค๋ฅผ ์ ๊ณตํ๋ค. ๋ฐฉ์ธ๋ง ๊ต์ฒดํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ง๋ SQL์ด ์์์ ์คํ๋๋ค.
<!-- ํ์ ์์ฑ -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" /> <!-- ๋ฐฉ์ธ ์ค์ -->
<!-- ์ต์
-->
<property name="hibernate.show_sql" value="true" /> <!-- ํ์ด๋ฒ๋ค์ดํธ๊ฐ ์คํํ SQL์ ์ถ๋ ฅํ๋ค -->
<property name="hibernate.format_sql" value="true" /> <!-- ํ์ด๋ฒ๋ค์ดํธ๊ฐ ์คํํ SQL์ ๋ณด๊ธฐ ์ฝ๊ฒ ์ ๋ ฌํ๋ค -->
<property name="hibernate.use_sql_comments" value="true" /> <!-- ์ฟผ๋ฆฌ๋ฅผ ์ถ๋ ฅํ ๋ ์ฃผ์๋ ํจ๊ป ์ถ๋ ฅํ๋ค -->
<property name="hibernate.id.new_generator_mappings" value="true" /> <!-- JPA ํ์ค์ ๋ง์ถ ์๋ก์ด ํค ์์ฑ ์ ๋ต์ ์ฌ์ฉํ๋ค. -->
2.6 ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ
์์์ฝ๋๋ฅผ ์ดํด๋ณด์
public class JpaMain {
public static void main(String[] args) {
//์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ ์์ฑ
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
EntityManager em = emf.createEntityManager(); //์ํฐํฐ ๋งค๋์ ์์ฑ
EntityTransaction tx = em.getTransaction(); //ํธ๋์ญ์
๊ธฐ๋ฅ ํ๋
try {
tx.begin(); //ํธ๋์ญ์
์์
logic(em); //๋น์ฆ๋์ค ๋ก์ง
tx.commit();//ํธ๋์ญ์
์ปค๋ฐ
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); //ํธ๋์ญ์
๋กค๋ฐฑ
} finally {
em.close(); //์ํฐํฐ ๋งค๋์ ์ข
๋ฃ
}
emf.close(); //์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ ์ข
๋ฃ
}
// ๋น์ฆ๋์ค ๋ก์ง
public static void logic(EntityManager em) {...}
}
์ฝ๋๋ ํฌ๊ฒ 3๋ถ๋ถ์ผ๋ก ๋๋์ด ์๋ค
- ์ํฐํฐ ๋งค๋์ ์์ฑ
- ํธ๋์ญ์ ๊ด๋ฆฌ
- ๋น์ฆ๋์ค ๋ก์ง
2.6.1 ์ํฐํฐ ๋งค๋์ ์ค์
์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ ์์ฑ
JPA๋ฅผ ์์ํ๋ ค๋ฉด ์ฐ์ ์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ๋ฅผ ์์ฑํด์ผ ํ๋ค.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ๋ JPA๋ฅผ ๋์์ํค๊ธฐ ์ํ ๊ธฐ๋ฐ ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์
ํ๋ ์์ฑํ๋ฏ๋ก ์์ฑ ๋น์ฉ์ ์์ฃผ ํฌ๋ค. ๋ฐ๋ผ์ ์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์ฒด์ ๋ฑ ํ ๋ฒ๋ง ์์ฑํ๊ณ ๊ณต์ ํด์ ์ฌ์ฉํด์ผ ํ๋ค.
์ํฐํฐ ๋งค๋์ ์์ฑ
EntityManager em = emf.createEntityManager();
JPA์ ๊ธฐ๋ฅ ๋๋ถ๋ถ์ ์ํฐํฐ ๋งค๋์ ๊ฐ ์ ๊ณตํ๋ค. ๋ํ์ ์ผ๋ก ์ํฐํฐ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฑ๋ก,์์ ,์ญ์ ,์กฐํํ ์ ์๋ค. ์ํฐํฐ ๋งค๋์ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์
๊ณผ ๋ฐ์ ํ ๊ด๊ณ๊ฐ ์์ผ๋ฏ๋ก ์ค๋ ๋๊ฐ์ ๊ณต์ ํ๊ฑฐ๋ ์ฌ์ฌ์ฉํ๋ฉด ์๋๋ค.
์ข
๋ฃ
์ฌ์ฉ์ด ๋๋ ์ํฐํฐ ๋งค๋์ , ์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ๋ ๋ฐ๋์ ์ข
๋ฃํด์ผํ๋ค.
em.close(); // ์ํฐํฐ ๋งค๋์ ์ข
๋ฃ
emf.close() // ์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ ์ข
๋ฃ
2.6.2 ํธ๋์ญ์ ๊ด๋ฆฌ
JPA๋ฅผ ์ฌ์ฉํ๋ฉด ํญ์ ํธ๋์ญ์ ์์์ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํด์ผ ํ๋ค. ํธ๋์ญ์ ์์ด ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์์ธ๊ฐ ๋ฐ์ํ๋ค.
EntityTransaction tx = em.getTransaction(); //ํธ๋์ญ์
๊ธฐ๋ฅ ํ๋
try {
tx.begin(); //ํธ๋์ญ์
์์
logic(em); //๋น์ฆ๋์ค ๋ก์ง
tx.commit();//ํธ๋์ญ์
์ปค๋ฐ
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); //ํธ๋์ญ์
๋กค๋ฐฑ
} finally {
em.close(); //์ํฐํฐ ๋งค๋์ ์ข
๋ฃ
}
emf.close(); //์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ ์ข
๋ฃ
2.6.3 ๋น์ฆ๋์ค ๋ก์ง
JPA๋ฅผ ์ด์ฉํ ๋ฑ๋ก, ์์ , ์กฐํ, ์ญ์ ์๋ ์๋์ ๊ฐ๋ค.
public static void logic(EntityManager em) {
String id = "id1";
Member member = new Member();
member.setId(id);
member.setUsername("์งํ");
member.setAge(2);
//๋ฑ๋ก
// INSERT INTO MEMBER ...
em.persist(member);
//์์
// JPA๋ ์ํฐํฐ ๋ณ๊ฒฝ ์ถ์ ๊ธฐ๋ฅ์ด ์์ด ์ํฐํฐ์ ๊ฐ๋ง ๋ณ๊ฒฝํ๋ฉด ์์ ๋จ
// UPDATE MEMBER SET AGE=20, NAME='์งํ' WHERE ID = 'id1';
member.setAge(20);
//ํ ๊ฑด ์กฐํ
// SELECT * FROM MEMBER WHERE ID = 'id1';
Member findMember = em.find(Member.class, id);
System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());
//๋ชฉ๋ก ์กฐํ
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
System.out.println("members.size=" + members.size());
//์ญ์
// DELETE FROM MEMBER WHERE ID = 'id1';
em.remove(member);
}
2.6.4 JPQL
JPA๋ SQL์ ์ถ์ํํ JPQL์ด๋ผ๋ ๊ฐ์ฒด ์งํฅ ์ฟผ๋ฆฌ ์ธ์ด๋ฅผ ์ ๊ณตํ๋ค.
//๋ชฉ๋ก ์กฐํ
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
System.out.println("members.size=" + members.size());
JPQL๊ณผ SQL์ ๊ฐ์ฅ ํฐ ์ฐจ์ด์
- JPQL์ ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ์ฟผ๋ฆฌํ๋ค.
- SQL์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ์ ๋์์ผ๋ก ์ฟผ๋ฆฌํ๋ค.
JPQL์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ์ ์ ํ ์์ง ๋ชปํ๋ค.
2.7 ์ ๋ฆฌ
- JPA๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ ๊ฐ๋ฐ ํ๊ฒฝ ์ค์
- JPA๋ฅผ ์ฌ์ฉ
- JPQL ์ค๋ช
3. ์์์ฑ ๊ด๋ฆฌ
3.1 ์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ์ ์ํฐํฐ ๋งค๋์
- ์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ๋ฅผ ๋ง๋๋ ๋น์ฉ์ ์๋นํ ํฌ๋ค.
- ๋ฐ๋ผ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด์์ ๊ณต์ ํ๋๋ก ์ค๊ณ๋์ด ์๋ค. (๊ทธ๋์ ์ผ๋ฐ์ ์ผ๋ก ํ๋๋ง ์์ฑ ํ๋ค)
- ์ํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ๋ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์ ๊ทผํด๋ ์์ ํ๋ค.
- ์ํฐํฐ ๋งค๋์ ๋ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์ ๊ทผํ๋ฉด ๋์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
3.2 ์์์ฑ ์ปจํ ์คํธ๋?
์์์ฑ ์ปจํ ์คํธ(persistence context)๋ ํด์ํ์๋ฉด ์ํฐํฐ๋ฅผ ์๊ตฌ ์ ์ฅํ๋ ํ๊ฒฝ์ด๋ ๋ป์ด๋ค.
// ์ด ๋ฉ์๋๋ ์ ํํ ํํํ์๋ฉด '์ํฐํฐ ๋งค๋์ ๋ฅผ ์ฌ์ฉํด์ ํ์ ์ํฐํฐ๋ฅผ ์์์ฑ ์ปจํ
์คํธ์ ์ ์ฅํ๋ค' ์ด๋ค.
em.persist(memeber);
์์์ฑ ์ปจํ ์คํธ๋
- ์ํฐํฐ ๋งค๋์ ๋ฅผ ์์ฑํ ๋ ํ๋ ๋ง๋ค์ด์ง๋ค.
- ์ํฐํฐ ๋งค๋์ ๋ฅผ ํตํด์ ์ ๊ทผํ ์ ์๋ค.
- ์ํฐํฐ ๋งค๋์ ๋ฅผ ํตํด ๊ด๋ฆฌ๋ฐ๋๋ค.
- ์ฌ๋ฌ ์ํฐํฐ ๋งค๋์ ๊ฐ ์ ๊ทผํ ์๋ ์๋ค.
3.3 ์ํฐํฐ์ ์๋ช ์ฃผ๊ธฐ
์ํฐํฐ์๋ 4๊ฐ์ง ์ํ๊ฐ ์กด์ฌํ๋ค.
- ๋น์์(new / transient): ์์์ฑ ์ปจํ ์คํธ์ ์ ํ ๊ด๊ณ๊ฐ ์๋ ์ํ
- ์์(managed): ์์์ฑ ์ปจํ ์คํธ์ ์ ์ฅ๋ ์ํ
- ์ค์์(detached): ์์์ฑ ์ปจํ ์คํธ์ ์ ์ฅ๋์๋ค๊ฐ ๋ถ๋ฆฌ๋ ์ํ
- ์ญ์ (removed): ์ญ์ ๋ ์ํ
3.4 ์์์ฑ ์ปจํ ์คํธ์ ํน์ง
์์์ฑ ์ปจํ ์คํธ์ ํน์ง์ ๋ค์๊ณผ ๊ฐ๋ค.
- ์์์ฑ ์ปจํ ์คํธ๋ ์ํฐํฐ๋ฅผ ์๋ณ์๊ฐ(@Id)์ผ๋ก ๊ตฌ๋ถํ๋ค. ์์ ์ํ๋ ์๋ณ์ ๊ฐ์ด ๋ฐ๋์ ์์ด์ผ ํ๋ค.
- JPA๋ ๋ณดํต ํธ๋์ญ์ ์ ์ปค๋ฐํ๋ ์๊ฐ ์์์ฑ ์ปจํ ์คํธ์ ์๋ก ์ ์ฅ๋ ์ํฐํฐ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ํ๋๋ฐ ์ด๊ฒ์ ํ๋ฌ์(flush)๋ผ ํ๋ค.
์์์ฑ ์ปจํ ์คํธ๊ฐ ์ํฐํฐ๋ฅผ ๊ด๋ฆฌํ ๋ ์ฅ์
- 1์ฐจ ์บ์
- ๋์ผ์ฑ ๋ณด์ฅ
- ํธ๋์ญ์ ์ ์ง์ํ๋ ์ฐ๊ธฐ ์ง์ฐ
- ๋ณ๊ฒฝ ๊ฐ์ง
- ์ง์ฐ๋ก๋ฉ
3.4.1 ์ํฐํฐ ์กฐํ
์์์ฑ ์ปจํ ์คํธ๋ ๋ด๋ถ์ ์บ์๋ฅผ ๊ฐ์ง๊ณ ์๋๋ฐ ์ด๊ฒ์ 1์ฐจ ์บ์๋ผ ํ๋ค. ์์ ์ํ์ ์ํฐํฐ๋ ๋ชจ๋ ์ด๊ณณ์ ์ ์ฅ๋๋ค.
1์ฐจ ์บ์์์ ์กฐํ
- 1์ฐจ ์บ์์ ํค๋ ์๋ณ์(@Id) ๊ฐ.
- ์๋ณ์ ๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ธฐ๋ณธ ํค์ ๋งคํ.
- 1์ฐจ ์บ์์์ ์ํฐํฐ๋ฅผ ์ฐพ๊ณ ์ํฐํฐ๊ฐ ์์ผ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํํ๋ค.
// ๋น์์ ์ํ
Member member = new Member();
member.setId("member1");
member.setUsername("ํ์1");
// ์์ ์ํ (1์ฐจ ์บ์์ ์ ์ฅ๋จ)
em.persist(member);
// 1์ฐจ ์บ์์์ ์กฐํ
Member findMember = em.find(Member.class, "member1");
๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํ
- ์กฐํ์ ์ํฐํฐ๊ฐ 1์ฐจ ์บ์์ ์์ผ๋ฉด ์ํฐํฐ ๋งค๋์ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์กฐํํด์ ์ํฐํฐ๋ฅผ ์์ฑ.
- 1์ฐจ ์บ์์ ์ ์ฅํ ํ์ ์์ ์ํ์ ์ํฐํฐ๋ฅผ ๋ฐํํ๋ค.
์์ ์ํฐํฐ์ ๋์ผ์ฑ ๋ณด์ฅ
์์์ฑ ์ปจํ
์คํธ๋ ์ฑ๋ฅ์ ์ด์ ๊ณผ ์ํฐํฐ์ ๋์ผ์ฑ์ ๋ณด์ฅํ๋ค.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b);
๋์ผ์ฑ๊ณผ ๋๋ฑ์ฑ
- ๋์ผ์ฑ(identity): ์ค์ ์ธ์คํด์ค๊ฐ ๊ฐ๋ค. ๋ฐ๋ผ์ ์ฐธ์กฐ ๊ฐ์ ๋น๊ตํ๋ == ๋น๊ต์ ๊ฐ์ด ๊ฐ๋ค.
- ๋๋ฑ์ฑ(equality): ์ค์ ์ธ์คํด์ค๋ ๋ค๋ฅผ ์ ์์ง๋ง ์ธ์คํด์ค๊ฐ ๊ฐ์ง๊ณ ์๋ ๊ฐ์ด ๊ฐ๋ค.
3.4.2 ์ํฐํฐ ๋ฑ๋ก
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// ์ํฐํฐ ๋งค๋์ ๋ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ ํธ๋์ญ์
์ ์์ํด์ผ ํ๋ค.
tx.begin(); //ํธ๋์ญ์
์์
em.persist(memberA);
em.persist(memberB);
// ์ฌ๊ธฐ๊น์ง INSERT SQL์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ณด๋ด์ง ์๋๋ค.
// ์ปค๋ฐํ๋ ์๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ INSERT SQL์ ๋ณด๋ธ๋ค.
tx.commit();
- ์ํฐํฐ ๋งค๋์ ๋ ํธ๋์ญ์ ์ ์ปค๋ฐํ๊ธฐ ์ง์ ๊น์ง ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ์ง ์๊ณ ๋ด๋ถ ์ฟผ๋ฆฌ ์ ์ฅ์์ INSERT SQL์ ๋ชจ์๋๋ค.
- ํธ๋์ญ์ ์ ์ปค๋ฐํ ๋ ๋ชจ์๋ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ณด๋ธ๋ค. ์ด๋ฅผ ํธ๋์ญ์ ์ ์ง์ํ๋ ์ฐ๊ธฐ ์ง์ฐ์ด๋ผ ํ๋ค.
ํ๋ฌ์
- ํ๋ฌ์๋ ์์์ฑ ์ปจํ ์คํธ์ ๋ณ๊ฒฝ ๋ด์ฉ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋๊ธฐํ ํ๋ ์์
- ๊ตฌ์ฒด์ ์ผ๋ก ์ด์ผ๊ธฐํ๋ฉด ์ฐ๊ธฐ ์ง์ฐ SQL์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ณด๋ด๋ ์์ ์ด๋ค.
ํธ๋์ญ์ ์ ์ง์ํ๋ ์ฐ๊ธฐ ์ง์ฐ์ด ๊ฐ๋ฅํ ์ด์
- ๋ฑ๋ก ์ฟผ๋ฆฌ๋ฅผ ๊ทธ๋ ๊ทธ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๋ฌํด๋ ํธ๋์ญ์ ์ ์ปค๋ฐํ์ง ์์ผ๋ฉด ์๋ฌด ์์ฉ ์๋ค.
- ๊ฒฐ๊ตญ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ SQL์ ๊ทธ๋ ๊ทธ๋ ์ ๋ฌํ๋, ์ปค๋ฐ ์ง์ ์๋ง ์ ๋ฌํ๋ ๊ฒฐ๊ณผ๋ ๊ฐ๋ค.
3.4.3 ์ํฐํฐ ์์
SQL ์์ ์ฟผ๋ฆฌ์ ๋ฌธ์ ์
- ์์ ์ฟผ๋ฆฌ๊ฐ ๋ง์์ง๋ค.
- ๋น์ฆ๋์ค ๋ก์ง์ ๋ถ์ํ๊ธฐ ์ํด SQL์ ๊ณ์ ํ์ธํด์ผํ๋ค.
- ๊ฒฐ๊ตญ ์ง๊ฐ์ ์ ์ผ๋ก ๋น์ฆ๋์ค ๋ก์ง์ด SQL์ ์์กดํ๋ค.
JPA์ ์์ : ๋ณ๊ฒฝ๊ฐ์ง
- JPA์์ ์ํฐํฐ ์์ ์ ๋จ์ํ ์ํฐํฐ๋ฅผ ์กฐํํด์ ๋ฐ์ดํฐ๋ง ๋ณ๊ฒฝํ๋ฉด ๋๋ค.
- ์ด๋ ๊ฒ ์ํฐํฐ์ ๋ณ๊ฒฝ์ฌํญ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์๋์ผ๋ก ๋ฐ์ํ๋ ๊ธฐ๋ฅ์ ๋ณ๊ฒฝ ๊ฐ์ง๋ผ ํ๋ค.
- ๋ณ๊ฒฝ ๊ฐ์ง๋ ์์์ฑ ์ปจํ ์คํธ๊ฐ ๊ด๋ฆฌํ๋ ์์ ์ํ์ ์ํฐํฐ์๋ง ์ ์ฉ๋๋ค.
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// ํธ๋์ญ์
์์
tx.begin();
// ์์ ์ํฐํฐ ์กฐํ
Member memberA = em.find(Member.class, "memberA");
// ์์ ์ํฐํฐ ๋ฐ์ดํฐ ์์
memberA.setUserName("hi");
memberA.setAge(10);
// ํธ๋์ญ์
์ปค๋ฐ
tx.commit();
- JPA์ ์์ ๊ธฐ๋ณธ์ ๋ต์ ์ํฐํฐ์ ๋ชจ๋ ํ๋๋ฅผ ์ ๋ฐ์ดํธ ํ๋ค.
- ํ๋๊ฐ ๋๋ฌด ๋ง๊ฑฐ๋ ์ ์ฅ๋๋ ๋ด์ฉ์ด ๋๋ฌด ํฌ๋ฉด ์์ ๋ ๋ฐ์ดํฐ๋ง ์ฌ์ฉํด์ ๋์ ์ผ๋ก UPDATE SQL์ ์์ฑํ๋ ์ ๋ต์ ์ ํํ ์ ์๋ค.
@Entity
@org.hibernate.annotations.DynamicUpdate
public class Member{...}
์ฐธ๊ณ
์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ง๋ง ์ปฌ๋ผ์ด ๋๋ต 30๊ฐ ์ ๋๊ฐ ์๋๋ผ๋ฉด ๊ธฐ๋ณธ ์ ๋ต์ ์์ ์ฟผ๋ฆฌ๊ฐ ๋น ๋ฅด๋ค.
์ถ๊ฐ๋ก INSERT SQL์ ๋์ ์ผ๋ก ์์ฑํ๋ @DynamicInsert
๋ ์๋ค.
3.4.4 ์ํฐํฐ ์ญ์
// ์์ ์ํฐํฐ ์กฐํ
Member memberA = em.find(Member.class, "memberA");
em.remove(memberA);
em.remove();
ํธ์ถ- ํธ์ถ ์๊ฐ ์์์ฑ ์ปจํ ์คํธ์์ ์ํฐํฐ ์ ๊ฑฐ
- ์ฐ๊ธฐ ์ง์ฐ SQL ์ ์ฅ์์ ์ญ์ ์ฟผ๋ฆฌ ๋ฑ๋ก
- ํธ๋์ญ์ ์ปค๋ฐ์ ํ๋ฌ์ (์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ญ์ ์ฟผ๋ฆฌ ์ ๋ฌ)
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ๋์ญ์
์ปค๋ฐ
3.5 ํ๋ฌ์
ํ๋ฌ์๋(flush()
)๋ ์์์ฑ ์ปจํ
์คํธ์ ๋ณ๊ฒฝ ๋ด์ฉ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ํ๋ค. ๋์ ์์๋ ๋ค์๊ณผ ๊ฐ๋ค.
- ๋ณ๊ฒฝ ๊ฐ์ง๊ฐ ์์์ฑ ์ปจํ ์คํธ์ ์๋ ๋ชจ๋ ์ํฐํฐ๋ฅผ ์ค๋ ์ท๊ณผ ๋น๊ตํด์ ์์ ๋ ์ํฐํฐ๋ฅผ ์ฐพ๋๋ค.
- ์์ ๋ ์ํฐํฐ๋ ์์ ์ฟผ๋ฆฌ๋ฅผ ๋ง๋ค์ด ์ฐ๊ธฐ ์ง์ฐ SQL ์ ์ฅ์์ ๋ฑ๋กํ๋ค.
- ์ฐ๊ธฐ ์ง์ฐ SQL ์ ์ฅ์์ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์กํ๋ค.(๋ฑ๋ก,์์ ,์ญ์ ์ฟผ๋ฆฌ)
์์์ฑ ์ปจํ ์คํธ๋ฅผ ํ๋ฌ์ํ๋ ๋ฐฉ๋ฒ 3๊ฐ์ง
- ์ง์ ํธ์ถ (
em.flush()
) - ํธ๋์ญ์ ์ปค๋ฐ
- JPQL ์ฟผ๋ฆฌ ์คํ
- JPQL์ SQL๋ก ๋ณํ๋์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ํฐํฐ ์กฐํ
- ๋ง์ฝ ์ํฐํฐ๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์ผ๋ฉด ์กฐํ ๋ถ๊ฐ
- ๋ฐ๋ผ์ ํ๋ฌ์๋ฅผ ํตํด ๋ณ๊ฒฝ ๋ด์ฉ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ํด์ผ ํจ
์ฐธ๊ณ
์๋ณ์๋ฅผ ๊ธฐ์ค์ผ๋ก ์กฐํํ๋ find()
๋ฉ์๋๋ฅผ ํธ์ถํ ๋๋ ํ๋ฌ์๊ฐ ์คํ๋์ง ์๋๋ค.
3.5.1 ํ๋ฌ์ ๋ชจ๋ ์ต์
FlushModeType.AUTO
: ์ปค๋ฐ์ด๋ ์ฟผ๋ฆฌ๋ฅผ ์คํํ ๋ ํ๋ฌ์(๊ธฐ๋ณธ๊ฐ)FlushModeType.COMMIT
: ์ปค๋ฐํ ๋๋ง ํ๋ฌ์
em.setFlushMode(FlushModeType.COMMIT); // ํ๋ฌ์ ๋ชจ๋ ์ง์ ์ค์
3.6 ์ค์์
- ์์์ฑ ์ปจํ ์คํธ๊ฐ ๊ด๋ฆฌํ๋ ์์ ์ํ์ ์ํฐํฐ๊ฐ ์์์ฑ ์ปจํ ์คํธ์์ ๋ถ๋ฆฌ๋ ๊ฒ์ ์ค์์ ์ํ๋ผ ํ๋ค.
- ์ค์์ ์ํ์ ์ํฐํฐ๋ ์์์ฑ ์ปจํ
์คํธ๊ฐ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๋ค.
์์ ์ํ์ ์ํฐํฐ๋ฅผ ์ค์์ ์ํ๋ก ๋ง๋๋ ๋ฐฉ๋ฒ
em.detach(entity)
: ํน์ ์ํฐํฐ๋ง ์ค์์ ์ํ๋ก ์ ํem.clear()
: ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์์ ํ ์ด๊ธฐํem.close()
: ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ข ๋ฃ
3.6.1 ์ํฐํฐ๋ฅผ ์ค์์ ์ํ๋ก ์ ํ: detach()
ํน์ ์ํฐํฐ๋ง ์ค์์ ์ํ๋ก ์ ํ
3.6.2 ์์์ฑ ์ปจํ ์คํธ ์ด๊ธฐํ: clear()
์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ด๊ธฐํํด์ ํด๋น ์์์ฑ ์ปจํ ์คํธ์ ๋ชจ๋ ์ํฐํฐ๋ฅผ ์ค์์ ์ํ๋ก ๋ง๋ฌ
3.6.3 ์์์ฑ ์ปจํ ์คํธ ์ข ๋ฃ: close()
์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ข ๋ฃํ๋ฉด ํด๋น ์์์ฑ ์ปจํ ์คํธ๊ฐ ๊ด๋ฆฌํ๋ ์์ ์ํ์ ์ํฐํฐ๊ฐ ๋ชจ๋ ์ค์์ ์ํ๊ฐ ๋๋ค.
3.6.4 ์ค์์ ์ํ์ ํน์ง
- ๊ฑฐ์ ๋น์์ ์ํ์ ๊ฐ๊น๋ค.
- ์๋ณ์ ๊ฐ์ ๊ฐ์ง๊ณ ์๋ค. (์ค์์ ์ํ๋ ์ด๋ฏธ ํ ๋ฒ ์์ ์ํ์์ผ๋ฏ๋ก ์๋ณ์ ๊ฐ์ ๊ฐ์ง๊ณ ์๋ค)
- ์ง์ฐ ๋ก๋ฉ์ ํ ์ ์๋ค.
3.6.5 ๋ณํฉ: merge()
์ค์์ ์ํ์ ์ํฐํฐ๋ฅผ ๋ค์ ์์ ์ํ๋ก ๋ณ๊ฒฝํ๋ ค๋ฉด ๋ณํฉ์ ์ฌ์ฉํ๋ค. merge()
๋ฉ์๋๋ ์ค์์ ์ํ์ ์ํฐํฐ๋ฅผ ๋ฐ์์ ๊ทธ ์ ๋ณด๋ก ์๋ก์ด ์์ ์ํ์ ์ํฐํฐ๋ฅผ ๋ฐํํ๋ค.
Member mergeMember = em.merge(member);
์ค์์ ๋ณํฉ
์ค์์ ์ํ์ธ member
์ํฐํฐ์ ์์ ์ํ์ธ mergeMember
์ํฐํฐ๋ ์๋ก ๋ค๋ฅธ ์ธ์คํด์ค๋ค. ๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ด ์ฐธ์กฐํ๋๋ก ์ฌ์ฉํ๋ ๊ฒ์ด ์์ ํ๋ค.
// Member mergeMember = em.merge(member); // ์ด ์ฝ๋๋ ์ํ
member = em2.merge(member); // ์ด๋ ๊ฒ ์ฌ์ฉ
๋น์์ ๋ณํฉ
๋ณํฉ์ ๋น์์ ์ํฐํฐ๋ ์์ ์ํ๋ก ๋ง๋ค ์ ์๋ค.
Member member = new Member();
Mmeber newMember = em.merge(member); // ๋น์์ ๋ณํฉ
tx.commit();
๋ณํฉ ๋์ ์์
- ํ๋ผ๋ฏธํฐ๋ก ๋์ด์จ ์ํฐํฐ์ ์๋ณ์ ๊ฐ์ผ๋ก ์์์ฑ ์ปจํ ์คํธ ์กฐํ
- ์ฐพ๋ ์ํฐํฐ๊ฐ ์์ผ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์์๋ ์์ผ๋ฉด ์ํฐํฐ ์์ฑํด์ ๋ณํฉ
3.7 ์ ๋ฆฌ
- ์ํฐํฐ ๋งค๋์ ๋ ์คํฐํฐ ๋งค๋์ ํฉํ ๋ฆฌ์์ ์์ฑํ๋ค.
- ์์์ฑ ์ปจํ ์คํธ๋ ์ํฐํฐ ๋งค๋์ ๋ฅผ ํตํด์ ์ ๊ทผํ ์ ์๋ค.
- ์์์ฑ ์ปจํ ์คํธ๋ ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ด์์ ๊ฐ์ฒด๋ฅผ ๋ณด๊ดํ๋ ๊ฐ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ ์ญํ ์ ํ๋ค.
- ์์์ฑ ์ปจํ ์คํธ ๋๋ถ์ 1์ฐจ ์บ์, ๋์ผ์ฑ ๋ณด์ฅ, ํธ๋์ญ์ ์ ์ง์ํ๋ ์ฐ๊ธฐ์ง์ฐ, ๋ณ๊ฒฝ ๊ฐ์ง, ์ง์ฐ ๋ก๋ฉ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๋ค.
- ์์์ฑ ์ปจํ ์คํธ์ ์ ์ฅํ ์ํฐํฐ๋ ํ๋ฌ์ ์์ ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์๋๋ค.
- ํ๋ฌ์๋ ์ผ๋ฐ์ ์ผ๋ก ํธ๋์ญ์ ์ ์ปค๋ฐํ ๋ ๋์ํ๋ค.
- ์์ ์ํ: ์์์ฑ ์ปจํ ์คํธ๊ฐ ๊ด๋ฆฌํ๋ ์ํฐํฐ
- ์ค์์ ์ํ: ์์ ์ํ์ ์ํฐํฐ๊ฐ ์์์ฑ ์ปจํ ์คํธ์์ ๋ถ๋ฆฌ๋ ๊ฒ์ ์ค์์ ์ํ
4. ์ํฐํฐ ๋งคํ
- ๊ฐ์ฒด์ ํ
์ด๋ธ ๋งคํ:
@Entity
,@Table
- ๊ธฐ๋ณธ ํค ๋งคํ:
@Id
- ํ๋์ ์ปฌ๋ผ ๋งคํ:
@Column
- ์ฐ๊ด๊ด๊ณ ๋งคํ:
@ManyToOne
,@JoinColumn
4.1 @Entity
JPA๋ฅผ ์ฌ์ฉํด์ ํ ์ด๋ธ๊ณผ ๋งคํํ ํด๋์ค
์์ฑ | ๊ธฐ๋ฅ | ๊ธฐ๋ณธ๊ฐ |
---|---|---|
name | JPA์์ ์ฌ์ฉํ ์ํฐํฐ ์ด๋ฆ์ ์ง์ ํ๋ค. ๊ธฐ๋ณธ๊ฐ์ ํด๋์ค ์ด๋ฆ์ด๋ค. ๋ง์ฝ ๋ค๋ฅธ ํจํค์ง์ ์ด๋ฆ์ด ๊ฐ์ ์ํฐํฐ ํด๋์ค๊ฐ ์๋ค๋ฉด ์ถ๋ํ๋ค. | ํด๋์ค์ด๋ฆ (์: Member) |
์ฃผ์์ฌํญ
- ๊ธฐ๋ณธ ์์ฑ์๋ ํ์ (
public
๋๋protected
) final, enum, interface, inner
ํด๋์ค์๋ ์ฌ์ฉํ ์ ์๋ค.- ์ ์ฅํ ํ๋์
final
์ ์ฌ์ฉํ๋ฉด ์๋๋ค.
4.2 @Table
์ํฐํฐ์ ๋งคํํ ํ ์ด๋ธ์ ์ง์ . ์๋ตํ๋ฉด ๋งคํํ ์ํฐํฐ ์ด๋ฆ์ ํ ์ด๋ธ ์ด๋ฆ์ผ๋ก ์ฌ์ฉํ๋ค.
์์ฑ | ๊ธฐ๋ฅ | ๊ธฐ๋ณธ๊ฐ |
---|---|---|
name | ๋งคํํ ํ ์ด๋ธ ์ด๋ฆ | ์ํฐํฐ ์ด๋ฆ์ ์ฌ์ฉํ๋ค. |
catalog | catalog ๊ธฐ๋ฅ์ด ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ catalog๋ฅผ ๋งคํํ๋ค. | ย |
schema | schema ๊ธฐ๋ฅ์ด ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ schema๋ฅผ ๋งคํํ๋ค. | ย |
uniqueConstraints | DDL ์์ฑ ์ ์ ๋ํฌ ์ ์ฝ์กฐ๊ฑด์ ๋ง๋ ๋ค. 2๊ฐ ์ด์์ ๋ณตํฉ ์ ๋ํฌ ์ ์ฝ์กฐ๊ฑด๋ ๋ง๋ค ์ ์๋ค. ์ด ๊ธฐ๋ฅ์ ์คํค๋ง ์๋ ์์ฑ ๊ธฐ๋ฅ์ ์ฌ์ฉํด์ DDL์ ๋ง๋ค ๋๋ง ์ฌ์ฉ๋๋ค. | ย |
4.3 ๋ค์ํ ๋งคํ ์ฌ์ฉ
@Entity
@Table(name="MEMBER",
uniqueConstraints = {@UniqueConstraint(
name = "NAME_AGE_UNIQUE",
columnNames = {"NAME", "AGE"}
)}
)
public class Member {
...์๋ต...
// enum์ ์ฌ์ฉํ๋ ค๋ฉด @Enumerated ์ด๋
ธํ
์ด์
์ผ๋ก ๋งคํ
@Enumerated(EnumType.STRING)
private RoleType roleType;
// ์๋ฐ์ ๋ ์ง ํ์
์ @Temporal ์ด๋
ธํ
์ด์
์ผ๋ก ๋งคํ
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
// CLOB, BLOBํ์
์ @Lob ์ด๋
ธํ
์ด์
์ผ๋ก ๋งคํ
@Lob
private String description;
...์๋ต...
}
4.4 ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง ์๋ ์์ฑ
JPA๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๋ฅผ ์๋์ผ๋ก ์์ฑํ๋ ๊ธฐ๋ฅ์ ์ง์ํ๋ค.
<!-- ์คํค๋ง ์๋ ์์ฑ -->
<property name="hibernate.hbm2ddl.auto" value="create" />
<!-- ์ฝ์์ ํ
์ด๋ธ ์์ฑ DDL ์ถ๋ ฅ -->
<property name="hibernate.show_sql" value="true" />
hibernate.hbm2ddl.auto ์์ฑ
์ต์ | ์ค๋ช |
---|---|
create | ๊ธฐ์กด ํ ์ด๋ธ์ ์ญ์ ํ๊ณ ์๋ก ์์ฑํ๋ค. DROP + CREATE |
create-drop | create ์์ฑ์ ์ถ๊ฐ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ์ข ๋ฃํ ๋ ์์ฑํ DDL์ ์ ๊ฑฐํ๋ค. DROP + CREATE + DROP |
update | ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ๊ณผ ์ํฐํฐ ๋งคํ์ ๋ณด๋ฅผ ๋น๊ตํด์ ๋ณ๊ฒฝ ์ฌํญ๋ง ์์ ํ๋ค. |
validate | ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ๊ณผ ์ํฐํฐ ๋งคํ์ ๋ณด๋ฅผ ๋น๊ตํด์ ์ฐจ์ด๊ฐ ์์ผ๋ฉด ๊ฒฝ๊ณ ๋ฅผ ๋จ๊ธฐ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ์ง ์๋๋ค. |
none | ์๋ ์์ฑ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ง ์์ผ๋ ค๋ฉด hibernate.hbm2ddl.auto ์์ฑ ์์ฒด๋ฅผ ์ญ์ ํ๊ฑฐ๋ ์ ํจํ์ง ์์ ์ต์ ๊ฐ์ ์ฃผ๋ฉด ๋๋ค. |
hibernate.hbm2ddl.auto ์ฃผ์์ฌํญ
์ด์ ์๋ฒ์์๋ DDL์ ์์ ํ๋ ์ต์
์ ์ ๋ ์ฌ์ฉํ๋ฉด ์๋๋ค.
- ๊ฐ๋ฐ ์ด๊ธฐ ๋จ๊ณ:
create
,update
- ์ด๊ธฐํ ์ํ๋ก ํ
์คํธ, CI์๋ฒ:
create
,create-drop
- ํ
์คํธ ์๋ฒ:
update
,validate
- ์คํ
์ด์ง, ์ด์ ์๋ฒ:
validate
,none
๊ธฐ๋ณธ ์ด๋ฆ ๋งคํ ์ ๋ต ๋ณ๊ฒฝํ๊ธฐ
ํ
์ด๋ธ ๋ช
์ด๋ ์ปฌ๋ผ ๋ช
์ด ์๋ต๋๋ฉด ์๋ฐ์ ์นด๋ฉ ํ๊ธฐ๋ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ
์ด๋ธ์ ์ธ๋์ค์ฝ์ด ํ๊ธฐ๋ฒ์ผ๋ก ๋งคํํ๋ค.
<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" />
4.5 DDL ์์ฑ ๊ธฐ๋ฅ
์ ์ฝ ์กฐ๊ฑด ์ถ๊ฐ
์คํค๋ง ์๋ ์์ฑํ๊ธฐ๋ฅผ ํตํด DDL์ ์ ์ฝ์กฐ๊ฑด์ ์ถ๊ฐํ ์ ์๋ค. ๋ค๋ง DDL์ ์๋ ์์ฑํ ๋๋ง ์ฌ์ฉ๋๊ณ JPA์ ์คํ ๋ก์ง์๋ ์ํฅ์ ์ฃผ์ง ์๋๋ค.
@Entity
@Table(name="MEMBER",
/**
* ์ ๋ํฌ ์ ์ฝ์กฐ๊ฑด ์ถ๊ฐ
* ALET TABLE MEMBER
* ADD CONSTRAINT NAME_AGE_UNIQUE UNIQUE (NAME, AGE)
*/
uniqueConstraints = {@UniqueConstraint(
name = "NAME_AGE_UNIQUE",
columnNames = {"NAME", "AGE"}
)}
)
public class Member {
...์๋ต...
// ์ปฌ๋ผ๋ช
NAME, not null, ๋ฌธ์ํฌ๊ธฐ 10
@Column(name = "NAME", nullable = false, length = 10)
private String username;
...์๋ต...
}
4.6 DDL ์์ฑ ๊ธฐ๋ฅ
JPA๊ฐ ์ ๊ณตํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ธฐ๋ณธ ํค ์์ฑ ์ ๋ต์ ๋ค์๊ณผ ๊ฐ๋ค.
- ์ง์ ํ ๋น: ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ง์ ํ ๋น
- ์๋ ์์ฑ: ๋๋ฆฌ ํค ์ฌ์ฉ ๋ฐฉ์
- IDENTITY: ๊ธฐ๋ณธ ํค ์์ฑ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์
- SEQUENCE: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ์ค๋ฅผ ์ฌ์ฉ
- TABLE: ํค ์์ฑ ํ ์ด๋ธ์ ์ฌ์ฉ
์๋ ์์ฑ ์ ๋ต์ด ๋ค์ํ ์ด์ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฒค๋๋ง๋ค ์ง์ํ๋ ๋ฐฉ์์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ด๋ค. ํค ์์ฑ ์ ๋ต์ ์ฌ์ฉํ๋ ค๋ฉด ๋ค์ ์์ฑ์ ์ถ๊ฐํ๋ค.
<property name="hibernate.id.new_generator_mappings" value="true" />
4.6.1 ๊ธฐ๋ณธ ํค ์ง์ ํ ๋น ์ ๋ต
@Id
@Column(name = "ID")
private String id;
Board board = new Board();
board.setId("id"); // ๊ธฐ๋ณธ ํค ์ง์ ํ ๋น
em.persist(board);
4.6.2 IDENTITY ์ ๋ต
IDENTITY
๋ ๊ธฐ๋ณธ ํค ์์ฑ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์ํ๋ ์ ๋ต์ด๋ค.IDENTITY
์๋ณ์ ์์ฑ ์ ๋ต์ ์ํฐํฐ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํด์ผ ์๋ณ์๋ฅผ ๊ตฌํ ์ ์๋ค.em.persist()
๋ฅผ ํธ์ถํ๋ ์ฆ์INSERT SQL
์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๋ฌ๋๋ค.- ๋ฐ๋ผ์ ํธ๋์ญ์ ์ ์ง์ํ๋ ์ฐ๊ธฐ ์ง์ฐ์ด ๋์ํ์ง ์๋๋ค.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Board board = new Board();
em.persist(board);
board.getId(); // 1
์ฐธ๊ณ
IDENTITY ์ ๋ต์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ INSERTํ ํ์ ๊ธฐ๋ณธ ํค๊ฐ์ ์กฐํํ ์์๋ค. ๋ฐ๋ผ์ ์ํฐํฐ์ ์๋ณ์ ๊ฐ์ ํ ๋นํ๋ ค๋ฉด JPA๋ ์ถ๊ฐ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์กฐํํด์ผ ํ๋ค. ํ์ง๋ง ํ์ด๋ฒ๋ค์ดํธ๋ JDBC3์ ์ถ๊ฐ๋ Statement.getGeneratedKeys()
๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ๋ฒ๋ง ํต์ ํ๋ค.
4.6.3 SEQUENCE ์ ๋ต
SEQUENCE ์ ๋ต์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ์ค๋ฅผ ์ด์ฉํด ๊ธฐ๋ณธ ํค๋ฅผ ์์ฑํ๋ค.
-- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ์ค ์์ฑ
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;
@Entity
@SequenceGenerator(
name = "BOARD_SEQ_GENERATOR",
sequenceName = "BOARD_SEQ", // ๋งคํํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ์ค ์ด๋ฆ
initialValue = 1,
allocationsSize = 1
)
public class BOARD {
@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "BOARD_SEQ_GENERATOR"
)
private Long id;
}
@SequenceGenerator
์์ฑ | ๊ธฐ๋ฅ | ๊ธฐ๋ณธ๊ฐ |
---|---|---|
name | ์๋ณ์ ์์ฑ๊ธฐ ์ด๋ฆ | ํ์ |
sequenceName | ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฑ๋ก๋์ด ์๋ ์ํ์ค ์ด๋ฆ | hibernate_sequence |
initialValue | DDL ์์ฑ ์์๋ง ์ฌ์ฉ๋จ. ์ํ์ค DDL์ ์์ฑํ ๋ ์ฒ์ ์์ํ๋ ์๋ฅผ ์ง์ ํ๋ค. | 1 |
allocationsSize | ์ํ์ค ํ ๋ฒ ํธ์ถ์ ์ฆ๊ฐํ๋ ์ (์ฑ๋ฅ ์ต์ ํ์ ์ฌ์ฉ๋จ) | 50 |
catalog, schema | ๋ฐ์ดํฐ๋ฒ ์ด์ค catalog, schema ์ด๋ฆ | ย |
SEQUENCE ์ ๋ต๊ณผ ์ต์ ํ
SEQUENCE ์ ๋ต์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ 2๋ฒ ํต์ ํ๋ค.
- ์๋ณ์ ๊ตฌํ๊ธฐ (SELECT BOARD_SEQ.NEXTVAL FROM DUAL)
- ์กฐํํ ์ํ์ค๋ฅผ ๊ธฐ๋ณธ ํค ๊ฐ์ผ๋ก ์ฌ์ฉํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ (INSERT INTO BOARD โฆ)
JPA๋ ์ํ์ค ์ ๊ทผํ๋ ํ์๋ฅผ ์ค์ด๊ธฐ ์ํด @SequenceGenerator.allocationsSize
๋ฅผ ์ฌ์ฉํ๋ค. ์ฌ๊ธฐ์ ์ค์ ํ ๊ฐ๋งํผ ํ ๋ฒ์ ์ํ์ค ๊ฐ์ ์ฆ๊ฐ์ํค๊ณ , ๊ทธ๋งํผ ๋ฉ๋ชจ๋ฆฌ์ ์ํ์ค ๊ฐ์ ํ ๋นํ๋ค. ์ด ๋ฐฉ๋ฒ์ ์ํ์ค ๊ฐ์ ์ ์ ํ๋ฏ๋ก ์ฌ๋ฌ JVM์ด ๋์์ ๋์ํด๋ ๊ธฐ๋ณธ ํค ๊ฐ์ด ์ถฉ๋ํ์ง ์๋๋ค. ๋ฐ๋ฉด์ ์ํ์ค ๊ฐ์ ํ ๋ฒ์ ๋ง์ด ์ฆ๊ฐ์ํจ๋ค. ์ด๋ฐ ์ํฉ์ด ๋ถ๋ด์ค๋ฝ๊ณ INSERT
์ฑ๋ฅ์ด ์ค์ํ์ง ์์ผ๋ฉด allocationsSize
๊ฐ์ 1๋ก ์ค์ ํ๋ค.
4.6.4 TABLE ์ ๋ต
TABLE ์ ๋ต์ ํค ์์ฑ ์ ์ฉ ํ ์ด๋ธ์ ํ๋ ๋ง๋ค๊ณ ์ฌ๊ธฐ์ ์ด๋ฆ๊ณผ ๊ฐ์ผ๋ก ์ฌ์ฉํ ์ปฌ๋ผ์ ๋ง๋ค์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ์ค๋ฅผ ํ๋ด๋ด๋ ์ ๋ต์ด๋ค.
-- ๋ฐ์ดํฐ๋ฒ ์ด์ค ํค ์์ฑ ์ฉ๋ ํ
์ด๋ธ ์์ฑ
create table MY_SEQUENCES (
sequence_name varchar(255) not null,
next_val bigint,
primary key (sequence_name)
)
@Entity
@TableGenerator(
name = "BOARD_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "BOARD_SEQ",
allocationsSize = 1
)
public class BOARD {
@Id
@GeneratedValue(
strategy = GenerationType.TABLE,
generator = "BOARD_SEQ_GENERATOR"
)
private Long id;
}
@TableGenerator
์์ฑ | ๊ธฐ๋ฅ | ๊ธฐ๋ณธ๊ฐ |
---|---|---|
name | ์๋ณ์ ์์ฑ๊ธฐ ์ด๋ฆ | ํ์ |
table | ํค์์ฑ ํ ์ด๋ธ๋ช | hibernate_sequences |
pkColumnName | ์ํ์ค ์ปฌ๋ผ๋ช | sequence_name |
valueColumnName | ์ํ์ค ๊ฐ ์ปฌ๋ผ๋ช | next_val |
pkColumnValue | ํค๋ก ์ฌ์ฉํ ๊ฐ ์ด๋ฆ | ์ํฐํฐ ์ด๋ฆ |
initialValue | ์ด๊ธฐ ๊ฐ, ๋ง์ง๋ง์ผ๋ก ์์ฑ๋ ๊ฐ์ด ๊ธฐ์ค์ด๋ค. | 0 |
allocationsSize | ์ํ์ค ํ ๋ฒ ํธ์ถ์ ์ฆ๊ฐํ๋ ์ (์ฑ๋ฅ ์ต์ ํ์ ์ฌ์ฉ๋จ) | 50 |
catalog, schema | ๋ฐ์ดํฐ๋ฒ ์ด์ค catalog, schema ์ด๋ฆ | ย |
uniqueConstraints(DDL) | ์ ๋ํฌ ์ ์ฝ ์กฐ๊ฑด์ ์ง์ ํ ์ ์๋ค. | ย |
TABLE ์ ๋ต๊ณผ ์ต์ ํ
TABLE ์ ๋ต์ ๊ฐ์ ์กฐํํ๋ฉด์ SELECT ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ณ , ๋ค์ ๊ฐ์ผ๋ก ์ฆ๊ฐ์ํค๊ธฐ ์ํด UPDATE ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค. SEQUENCE ์ ๋ต๊ณผ ๋น๊ตํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ ๋ฒ ๋ ํต์ ํ๋ ๋จ์ ์ด ์๋ค.
4.6.5 AUTO ์ ๋ต
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐฉ์ธ์ ๋ฐ๋ผ IDENTITY, SEQUENCE, TABLE ์ ๋ต ์ค ํ๋๋ฅผ ์๋์ผ๋ก ์ ํํ๋ค. @GeneratedValue.strategy
์ ๊ธฐ๋ณธ๊ฐ์ AUTO
๋ค.
@Id @GeneratedValue
private Long id;
4.6.6 ๊ธฐ๋ณธ ํค ๋งคํ ์ ๋ฆฌ
- ์ง์ ํ ๋น: ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ง์ ์๋ณ์ ๊ฐ์ ํ ๋น
- SEQUENCE: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ์ค์์ ์๋ณ์ ๊ฐ์ ํ๋ ํ ์์์ฑ ์ปจํ ์คํธ์ ์ ์ฅ
- TABLE: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ์ค ์์ฑ์ฉ ํ ์ด๋ธ์์ ์๋ณ์ ๊ฐ์ ํ๋ฑ ํ ์์์ฑ ์ปจํ ์คํธ์ ์ ์ฅ
- IDENTITY: ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ํฐํฐ๋ฅผ ์ ์ฅํด์ ์๋ณ์ ๊ฐ์ ํ๋ํ ํ ์์์ฑ ์ปจํ
์คํธ์ ์ ์ฅ
์๋ณ์ ์ ํ ์ ๋ต
ํ
์ด๋ธ์ ๊ธฐ๋ณธ ํค๋ฅผ ์ ํํ๋ ์ ๋ต์ ํฌ๊ฒ 2๊ฐ์ง๊ฐ ์๋ค.
- ์์ฐ ํค(natural key)
- ๋น์ฆ๋์ค์ ์๋ฏธ๊ฐ ์๋ ํค
- ์) ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ, ์ด๋ฉ์ผ, ์ ํ๋ฒํธ
- ๋๋ฆฌ ํค(surrogate key)
- ๋น์ฆ๋์ค์ ๊ด๋ จ์ด ์๋ ์์๋ก ๋ง๋ค์ด์ง ํค, ๋์ฒด ํค๋ก๋ ๋ถ๋ฆฐ๋ค
- ์) ์ค๋ผํด ์ํ์ค, auto_increment, ํค ์์ฑ ํ ์ด๋ธ ์ฌ์ฉ
๊ถ์ฅํ๋ ์๋ณ์ ์ ํ ์ ๋ต
์์ฐ ํค๋ณด๋ค๋ ๋๋ฆฌ ํค๋ฅผ ๊ถ์ฅํ๋ค.
- ์์ฐ ํค๋ ๋ณ๊ฒฝ๋ ์ ์๋ค.
- ๋น์ฆ๋์ค ํ๊ฒฝ์ ์ธ์ ๊ฐ ๋ณํ๋ค.
- ํ ์ด๋ธ์ ํ ๋ฒ ์ ์ํ๋ฉด ๋ณ๊ฒฝํ๊ธฐ ์ด๋ ต๋ค.
4.7 ํ๋์ ์ปฌ๋ผ ๋งคํ: ๋ ํผ๋ฐ์ค
๋ถ๋ฅ | ๋งคํ ์ด๋ ธํ ์ด์ | ์ค๋ช |
---|---|---|
ํ๋์ ์ปฌ๋ผ ๋งคํ | @Column | ์ปฌ๋ผ์ ๋งคํํ๋ค. |
@Enumerated | ์๋ฐ์ enum ํ์ ์ ๋งคํํ๋ค. | |
@Temporal | ๋ ์ง ํ์ ์ ๋งคํํ๋ค. | |
@Lob | BLOB, CLOB ํ์ ์ ๋งคํํ๋ค. | |
@Transient | ํน์ ํ๋๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋งคํํ์ง ์๋๋ค. | |
๊ธฐํ | @Access | JPA๊ฐ ์ํฐํฐ์ ์ ๊ทผํ๋ ๋ฐฉ์์ ์ง์ ํ๋ค. |
4.7.1 @Column
@Column์ ๊ฐ์ฒด ํ๋๋ฅผ ํ ์ด๋ธ ์ปฌ๋ผ์ ๋งคํํ๋ค.
์์ฑ | ๊ธฐ๋ฅ | ๊ธฐ๋ณธ๊ฐ |
---|---|---|
name | ํ๋์ ๋งคํํ ํ ์ด๋ธ์ ์ปฌ๋ผ ์ด๋ฆ | ๊ฐ์ฒด์ ํ๋ ์ด๋ฆ |
insertable (๊ฑฐ์ ์ฌ์ฉํ์ง ์์) | ์ํฐํฐ ์ ์ฅ ์ ์ด ํ๋๋ ๊ฐ์ด ์ ์ฅํ๋ค. false๋ก ์ค์ ํ๋ฉด ์ด ํ๋๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ์ง ์๋๋ค. | true |
updatable (๊ฑฐ์ ์ฌ์ฉํ์ง ์์) | ์ํฐํฐ ์์ ์ ์ด ํ๋๋ ๊ฐ์ด ์์ ํ๋ค. false๋ก ์ค์ ํ๋ฉด ์ด ํ๋๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์ ํ์ง ์๋๋ค. | true |
table (๊ฑฐ์ ์ฌ์ฉํ์ง ์์) | ํ๋์ ์ํฐํฐ๋ฅผ ๋ ๊ฐ ์ด์์ ํ ์ด๋ธ์ ๋งคํํ ๋ ์ฌ์ฉํ๋ค. ์ง์ ํ ํ๋๋ฅผ ๋ค๋ฅธ ํ ์ด๋ธ์ ๋งคํํ ์ ์๋ค. | ํ์ฌ ํด๋์ค๊ฐ ๋งคํ๋ ํ ์ด๋ธ |
nullable (DDL) | null ๊ฐ์ ํ์ฉ ์ฌ๋ถ ์ค์ . | true |
unique (DDL) | @Table์ uniqueConstraints์ ๊ฐ์ง๋ง ํ ์ปฌ๋ผ์ ๊ฐ๋จํ ์ ๋ํฌ ์ ์ฝ์กฐ๊ฑด์ ๊ฑธ ๋ ์ฌ์ฉํ๋ค. ๋ง์ฝ ๋ ์ปฌ๋ผ ์ด์์ ์ฌ์ฉํด์ ์ ๋ํฌ ์ ์ฝ์กฐ๊ฑด์ ์ฌ์ฉํ๋ ค๋ฉด @Table.uniqueConstraints๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. | ย |
length (DDL) | ๋ฌธ์ ๊ธธ์ด ์ ์ฝ์กฐ๊ฑด, String ํ์ ์๋ง ์ฌ์ฉํ๋ค. | 255 |
precision, scale (DDL) | BigDecimal ํ์ ์์ ์ฌ์ฉํ๋ค. precision์ ์์์ ์ ํฌํจํ ์ ์ฒด ์๋ฆฟ์, scale์ ์์์ ์๋ฆฟ์๋ค. double, floatํ์ ์๋ ์ ์ฉ๋์ง ์๋๋ค. | precision = 19, scale=2 |
columnDefinition | ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปฌ๋ผ ์ ๋ณด๋ฅผ ์ง์ ์ค ์ ์๋ค. | ํ๋์ ์๋ฐ ํ์ ๊ณผ ๋ฐฉ์ธ ์ ๋ณด๋ฅผ ์ฌ์ฉํด์ ์ ์ ํ ์ปฌ๋ผ ํ์ ์ ์์ฑํ๋ค. |
4.7.2 @Enumerated
์๋ฐ์ enum ํ์ ์ ๋งคํํ ๋ ์ฌ์ฉํ๋ค.
์์ฑ | ๊ธฐ๋ฅ | ๊ธฐ๋ณธ๊ฐ |
---|---|---|
value | EnumType.ORDINAL: enum ์์๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ EnumType.STRING: enum ์ด๋ฆ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ |
EnumType.ORDINAL |
4.7.3 @Temporal
๋ ์ง ํ์
(java.util.Date
, java.util.Calendar
)์ ๋งคํํ ๋ ์ฌ์ฉํ๋ค.
์์ฑ | ๊ธฐ๋ฅ | ๊ธฐ๋ณธ๊ฐ |
---|---|---|
value |
- TemporalType.DATE: ๋ ์ง, ๋ฐ์ดํฐ๋ฒ ์ด์ค date ํ์
๊ณผ ๋งคํ (์: 2013-10-11)
- TemporalType.TIME: ์๊ฐ, ๋ฐ์ดํฐ๋ฒ ์ด์ค time ํ์ ๊ณผ ๋งคํ (์: 11:11:11) - TemporalType.TIMESTAMP: ๋ ์ง์ ์๊ฐ, ๋ฐ์ดํฐ๋ฒ ์ด์ค timestamp ํ์ ๊ณผ ๋งคํ (์: 2013-10-11 11:11:11) |
TemporalType์ ํ์ ์ง์ |
4.7.4 @Lob
๋ฐ์ดํฐ๋ฒ ์ด์ค BLOB, CLOB ํ์ ๊ณผ ๋งคํํ๋ค. ๋งคํํ๋ ํ๋ ํ์ ์ ๋ฐ๋ผ ๋งคํ์ ๋ฌ๋ฆฌํ๋ค.
- CLOB: String, char[], java.sql.CLOB
- BLOB: byte[], java.sql.BLOB
4.7.5 @Transient
์ด ํ๋๋ ๋งคํํ์ง ์๋๋ค. ๋ฐ๋ผ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ์ง ์๊ณ ์กฐํํ์ง ์๋๋ค.
4.7.6 @Access
JPA๊ฐ ์ํฐํฐ ๋ฐ์ดํฐ์ ์ ๊ทผํ๋ ๋ฐฉ์์ ์ง์ ํ๋ค.
- AccessType.FIELD: ํ๋ ์ ๊ทผ
- AccessType.PROPERTY: ํ๋กํผํฐ ์ ๊ทผ, ์ ๊ทผ์(Getter) ์ฌ์ฉ
@Entity
@Access(AccessType.FIELD)
public class Member {...}
@Entity
@Access(AccessType.PROPERTY)
public class Member {...}
/**
* ๋ ์ ๊ทผ ๋ฐฉ์์ ํจ๊ป ์ฌ์ฉํ ์๋ ์๋ค.
*
* @Id๋ ํ๋ ์ ๊ทผ ๋ฐฉ์
* getFullName()๋ ํ๋กํผํฐ ์ ๊ทผ ๋ฐฉ์
* Member ์ํฐํฐ๋ฅผ ์ ์ฅํ๋ฉด ํ
์ด๋ธ์ FULLNAME ์ปฌ๋ผ์
* firstName + lastName์ ๊ฒฐ๊ณผ๊ฐ ์ ์ฅ๋๋ค.
*/
@Entity
public class Member {
@Id
private String id;
@Transient
private String firstName;
@Transient
private String lastName;
@Access(AccessType.PROPERTY)
public String getFullName() {
return firstName + lastName;
}
...
}
5. ์ฐ๊ด๊ด๊ณ ๋งคํ ๊ธฐ์ด
์ฐ๊ด๊ด๊ณ ๋งคํ ํค์๋ ์ ๋ฆฌ
- ๋ฐฉํฅ(Direction): [๋จ๋ฐฉํฅ, ์๋ฐฉํฅ]์ด ์๋ค. ๋ฐฉํฅ์ ๊ฐ์ฒด๊ด๊ณ์์๋ง ์กด์ฌํ๊ณ ํ ์ด๋ธ ๊ด๊ณ๋ ํญ์ ์๋ฐฉํฅ์ด๋ค.
- ๋ค์ค์ฑ(Multiplcity): [๋ค๋์ผ(N:1), ์ผ๋๋ค(1:N), ์ผ๋์ผ(1:1), ๋ค๋๋ค(N:M)] ๋ค์ค์ฑ์ด ์๋ค.
- ์ฐ๊ด๊ด๊ณ ์ฃผ์ธ(Owner): ๊ฐ์ฒด๋ฅผ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ก ๋ง๋ค๋ฉด ์ฐ๊ด๊ด๊ณ ์ฃผ์ธ์ ์ ํด์ผ ํ๋ค.
5.1 ๋จ๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ
๊ฐ์ฒด ์ฐ๊ด๊ด๊ณ์ ํ ์ด๋ธ ์ฐ๊ด๊ด๊ณ์ ์ฐจ์ด
- ๊ฐ์ฒด
- ์ฐธ์กฐ(์ฃผ์)๋ก ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ๋๋ค.
- ์ธ์ ๋ ๋จ๋ฐฉํฅ
- ์๋ฐฉํฅ์ ํ๋ ค๋ฉด ์๋ก๋ค๋ฅธ ๋จ๋ฐฉํฅ ๊ด๊ณ 2๊ฐ๋ก ๊ตฌํ
- ํ
์ด๋ธ
- ์ธ๋ ํค๋ก ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ๋๋ค.
- ์ธ๋ ํค ํ๋๋ก ์๋ฐฉํฅ ์กฐ์ธ ๊ฐ๋ฅ
5.1.1 ์์ํ ๊ฐ์ฒด ์ฐ๊ด๊ด๊ณ
๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํด์ ์ฐ๊ด๊ด๊ณ๋ฅผ ํ์ํ ์ ์๋๋ฐ ์ด๊ฒ์ ๊ฐ์ฒด ๊ทธ๋ํ ํ์์ด๋ผ ํ๋ค.
public static void main(String[] args) {
Member member1 = new Memeber("member1", "ํ์1");
Member member2 = new Memeber("member2", "ํ์1");
Team team = new Team("team", "ํ");
member1.setTeam(team);
member2.setTeam(team);
// ๊ฐ์ฒด ๊ทธ๋ํ ํ์
Team findTeam = member1.getTeam();
}
5.1.2 ํ ์ด๋ธ ์ฐ๊ด๊ด๊ณ
์ธ๋ ํค๋ฅผ ์ฌ์ฉํด์ ์ฐ๊ด๊ด๊ณ๋ฅผ ํ์ํ ์ ์๋๋ฐ ์ด๊ฒ์ ์กฐ์ธ์ด๋ผ ํ๋ค.
SELECT T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.ID
WHERE M.MEMBER_ID = 'member1'
5.1.3 ๊ฐ์ฒด ๊ด๊ณ ๋งคํ
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private Long id;
private String username;
//์ฐ๊ด ๊ด๊ณ ๋งคํ
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
//์ฐ๊ด๊ด๊ณ ์ค์
public void setTeam(Team team) {
this.team = team;
}
...์๋ต...
}
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
...์๋ต...
}
@ManyToOne
- ๋ค๋์ผ(N:1) ๊ด๊ณ๋ผ๋ ๋งคํ ์ ๋ณด
- ์ฐ๊ด๊ด๊ณ ๋งคํํ ๋ ๋ค์ค์ฑ์ ๋ํ๋ด๋ ์ด๋
ธํ
์ด์
ํ์๋ก ์ฌ์ฉํด์ผ ํ๋ค.
@JoinColumn(name=โTEAM_IDโ)
- ์กฐ์ธ์ปฌ๋ผ์ ์ธ๋ ํค๋ฅผ ๋งคํํ ๋ ์ฌ์ฉํ๋ค.
- name ์์ฑ์ ๋งคํํ ์ธ๋ ํค ์ด๋ฆ์ ์ง์ ํ๋ค. (์ฌ๊ธฐ์ ํ ํ ์ด๋ธ์ TEAM_ID)
- ์๋ตํ ์ ์๋ค.
5.1.4 @JoinColumn
@JoinColumn์ ์ธ๋ ํค๋ฅผ ๋งคํํ ๋ ์ฌ์ฉํ๋ค.
์์ฑ | ๊ธฐ๋ฅ | ๊ธฐ๋ณธ๊ฐ |
---|---|---|
name | ๋งคํํ ์ธ๋ ํค ์ด๋ฆ | ํ๋๋ช + _ + ์ฐธ์กฐํ๋ ํ ์ด๋ธ์ ์ปฌ๋ฌ๋ช |
referencedColumnName | ์ธ๋ ํค๊ฐ ์ฐธ์กฐํ๋ ๋์ ํ ์ด๋ธ์ ์ปฌ๋ผ๋ช | ์ฐธ์กฐํ๋ ํ ์ด๋ธ์ ๊ธฐ๋ณธ ํค ์ปฌ๋ผ๋ช |
foreignKey (DDL) | ์ธ๋ ํค ์ ์ฝ์กฐ๊ฑด์ ์ง์ ์ง์ ํ ์ ์๋ค. ์ด ์์ฑ์ ํ ์ด๋ธ์ ์์ฑํ ๋๋ง ์ฌ์ฉํ๋ค. | ย |
unique, nullable, insertable, updatable, columnDefinition, table | @Column์ ์์ฑ๊ณผ ๊ฐ๋ค. | ย |
5.1.5 @ManyToOne
@ManyToOne ์ด๋ ธํ ์ด์ ์ ๋ค๋์ผ ๊ด๊ณ์์ ์ฌ์ฉํ๋ค.
์์ฑ | ๊ธฐ๋ฅ | ๊ธฐ๋ณธ๊ฐ |
---|---|---|
optional | false๋ก ์ค์ ํ๋ฉด ์ฐ๊ด๋ ์ํฐํฐ๊ฐ ํญ์ ์์ด์ผ ํ๋ค. | true |
fetch | ๊ธ๋ก๋ฒ ํ์น ์ ๋ต์ ์ค์ ํ๋ค. | @ManyToOne=FetchType.EAGER @OneToMany=FetchType.LAZY |
cascade | ์์์ฑ ์ ์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ค. | ย |
targetEntity (๊ฑฐ์ ์ฌ์ฉํ์ง ์์) | ์ฐ๊ด๋ ์ํฐํฐ์ ํ์ ์ ๋ณด๋ฅผ ์ค์ ํ๋ค. ์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ง ์๊ณ ์ปฌ๋ ์ ์ ์ฌ์ฉํด๋ ์ ๋ค๋ฆญ์ผ๋ก ํ์ ์ ๋ณด๋ฅผ ์ ์ ์๋ค. | ย |
5.2 ์ฐ๊ด๊ด๊ณ ์ฌ์ฉ
์ฐ๊ด๊ด๊ณ๋ฅผ ๋ฑ๋ก, ์์ , ์ญ์ , ์กฐํํ๋ ์์ ๋ฅผ ํตํด ์ฐ๊ด๊ด๊ณ๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ์์๋ณธ๋ค.
5.2.1 ์ ์ฅ
JPA์์ ์ํฐํฐ๋ฅผ ์ ์ฅํ ๋ ์ฐ๊ด๋ ๋ชจ๋ ์ํฐํฐ๋ ์์ ์ํ์ฌ์ผ ํ๋ค.
public void testSave() {
// ํ1 ์ ์ฅ
Team team1 = new Team("team1", "ํ1");
em.persist(team1);
// ํ์1 ์ ์ฅ
Member member1 = new Member("member1", "ํ์1");
member1.setTeam(team1); //์ฐ๊ด๊ด๊ณ ์ค์ member1 -> team1
em.persist(member1);
// ํ์2 ์ ์ฅ
Member member2 = new Member("member2", "ํ์2");
member2.setTeam(team1); //์ฐ๊ด๊ด๊ณ ์ค์ member2 -> team1
em.persist(member2);
}
5.2.2 ์กฐํ
์ฐ๊ด๊ด๊ณ๊ฐ ์๋ ์ํฐํฐ ์กฐํ ๋ฐฉ๋ฒ์ ํฌ๊ฒ 2๊ฐ์ง๋ค.
- ๊ฐ์ฒด ๊ทธ๋ํ ํ์(๊ฐ์ฒด ์ฐ๊ด๊ด๊ณ๋ฅผ ์ฌ์ฉํ ์กฐํ)
- ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ ์ฌ์ฉ(JPQL)
๊ฐ์ฒด ๊ทธ๋ํ ํ์
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); // ๊ฐ์ฒด ๊ทธ๋ํ ํ์
System.out.println("ํ ์ด๋ฆ = " + team.getName());
๊ฐ์ฒด ์งํฅ ์ฟผ๋ฆฌ ์ฌ์ฉ
public static void testJPQL(EntityManager em) {
String jpql1 = "select m from Member m join m.team t where t.name=:teamName";
List<Member> resultList = em.createQuery(jpql1, Member.class)
.setParameter("teamName", "ํ1")
.getResultList();
for (Member member : resultList) {
System.out.println("[query] member.username = " + member.getUsername());
}
}
/*
* ๊ฒฐ๊ณผ
* [query] member.username=ํ์1
* [query] member.username=ํ์2
*/
์คํ๋๋ SQL์ ๋ค์๊ณผ ๊ฐ๋ค.
SELECT M.* FROM MEMBER MEMBER
INNER JOIN
TEAM TEAM ON MEMBER.TEAM_ID = TEAM1_.ID
WHERE
TEAM1_.NAME='ํ1'
์คํ๋ SQL๊ณผ JPQL์ ๋น๊ตํ๋ฉด JPQL์ ๊ฐ์ฒด(์ํฐํฐ)๋ฅผ ๋์์ผ๋ก ํ๊ณ SQL๋ณด๋ค ๊ฐ๊ฒฐํ๋ค.
5.2.3 ์์
private static void updateRelation(EntityManager em) {
// ์๋ก์ด ํ2
Team team2 = new Team("team2", "ํ2");
em.persist(team2);
//ํ์1์ ์๋ก์ด ํ2 ์ค์
Member member = em.find(Member.class, "member1");
member.setTeam(team2);
}
์คํ๋๋ ์์ SQL์ ๋ค์๊ณผ ๊ฐ๋ค.
UPDATE MEMBER
SET
TEAM_ID='team2', ...
WHERE ID = 'member1'
5.2.4 ์ฐ๊ด๊ด๊ณ ์ ๊ฑฐ
private static void deleteRelation(EntityManager em) {
Member member1 = em.find(Member.class, "member1");
member1.setTeam(null); //์ฐ๊ด๊ด๊ณ ์ ๊ฑฐ
}
์คํ๋๋ ์ฐ๊ด๊ด๊ณ ์ ๊ฑฐ SQL์ ๋ค์๊ณผ ๊ฐ๋ค.
UPDATE MEMBER
SET
TEAM_ID=null, ...
WHERE ID = 'member1'
5.2.5 ์ฐ๊ด๊ด๊ณ ์ญ์
์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์ญ์ ํ๋ ค๋ฉด ๊ธฐ์กด์ ์๋ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋จผ์ ์ ๊ฑฐํ๊ณ ์ญ์ ํด์ผ ํ๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ์ธ๋ ํค ์ ์ฝ์กฐ๊ฑด์ผ๋ก ์ธํด, ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
member1.setTeam(null); // ํ์1 ์ฐ๊ด๊ด๊ณ ์ ๊ฑฐ
member2.setTeam(null); // ํ์2 ์ฐ๊ด๊ด๊ณ ์ ๊ฑฐ
em.remove(team); // ํ ์ญ์
5.3 ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ
๊ฐ์ฒด
- ํ์๊ณผ ํ์ ๋ค๋์ผ ๊ด๊ณ
- ํ์์ ํ์์ ๊ด๊ณ๋ ์ผ๋๋ค ๊ด๊ณ
๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ
์ด๋ธ์ ์ธ๋ ํค ํ๋๋ก ์๋ฐฉํฅ ์กฐํ
5.3.1 ์๋ฐฉํญ ์ฐ๊ด๊ด๊ณ ๋งคํ
@Entity
public class Member {
...์๋ต...
// ์ฐ๊ด ๊ด๊ณ ๋งคํ
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
...์๋ต...
}
@Entity
public class Team {
...์๋ต...
// @OneToMany ๋งคํ ์ ๋ณด๋ฅผ ์ฌ์ฉํ๋ค.
// mappedBy ์์ฑ์ ์๋ฐฉํฅ ๋งคํ์ผ ๋ ์ฌ์ฉํ๋๋ฐ ๋ฐ๋์ชฝ ๋งคํ์ ํ๋ ์ด๋ฆ์ ๊ฐ์ผ๋ก ์ฃผ๋ฉด ๋๋ค.
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
...์๋ต...
}
5.4 ์ฐ๊ด๊ด๊ณ ์ฃผ์ธ
- ๊ฐ์ฒด์๋ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ผ๋ ๊ฒ์ด ์๋ค.
- ์๋ก ๋ค๋ฅธ ๋จ๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ 2๊ฐ๋ฅผ ์๋ฐฉํฅ์ธ ๊ฒ์ฒ๋ผ ๋ณด์ด๊ฒ ํ ๋ฟ์ด๋ค.
- ์ํฐํฐ๋ฅผ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ก ์ค์ ํ๋ฉด ๊ฐ์ฒด์ ์ฐธ์กฐ๋ ๋์ธ๋ฐ ์๋ ํค๋ ํ๋๋ค.
- JPA์์๋ ๋ ๊ฐ์ฒด ์ฐ๊ด๊ด๊ณ ์ค ํ๋๋ฅผ ์ ํด์ ํ
์ด๋ธ์ ์ธ๋ ํค๋ฅผ ๊ด๋ฆฌํด์ผ ํ๋๋ฐ ์ด๊ฒ์ ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ด๋ผ ํ๋ค.
5.4.1 ์๋ฐฉํฅ ๋งคํ์ ๊ท์น: ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ
- ๋ ์ฐ๊ด๊ด๊ณ ์ค ํ๋๋ฅผ ์ฐ๊ด๊ด๊ณ ์ฃผ์ธ์ผ๋ก ์ ํด์ผ ํ๋ค.
- ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ๋ง์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ด๊ด๊ณ์ ๋งคํ๋๊ณ ์๋ ํค๋ฅผ ๊ด๋ฆฌ(๋ฑ๋ก,์์ ,์ญ์ )ํ ์ ์๋ค.
- ์ฃผ์ธ์ด ์๋ ์ชฝ์ ์ฝ๊ธฐ๋ง ํ ์ ์๋ค.
mappedBy
- ์ฃผ์ธ์
mappedBy
์์ฑ์ ์ฌ์ฉํ์ง ์๋๋ค. - ์ฃผ์ธ์ด ์๋๋ฉด
mappedBy
์์ฑ์ ์ฌ์ฉํด์ ์์ฑ์ ๊ฐ์ผ๋ก ์ฐ๊ด๊ด๊ณ ์ฃผ์ธ์ ์ง์ ํ๋ค. (โ๋ด๊ฐ ์๋์๊ฒ ๋งคํ๋์๋คโ๋ผ๊ณ ์๊ฐํ๋ฉด ํธํจ)
์ฐ๊ด๊ด๊ณ ์ฃผ์ธ์ ์ ํ๋ค๋ ๊ฒ์ ์ธ๋ ํค ๊ด๋ฆฌ์๋ฅผ ์ ํํ๋ ๊ฒ์ด๋ค.
5.4.2 ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ ์ธ๋ ํค๊ฐ ์๋ ๊ณณ
- ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ ํ ์ด๋ธ์ ์ธ๋ ํค๊ฐ ์๋ ๊ณณ์ผ๋ก ์ ํด์ผ ํ๋ค.
- ๋ค๋์ผ, ์ผ๋๋ค ๊ด๊ณ์์๋ ํญ์ ๋ค ์ชฝ์ด ์ธ๋ ํค๋ฅผ ๊ฐ์ง๋ค.
- ๋ค ์ชฝ์ธ
@ManyToOne
์ ํญ์ ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ด ๋๋ฏ๋กmappedBy
๋ฅผ ์ค์ ํ ์ ์๋ค. - ๋ฐ๋ผ์
@ManyToOne
์๋mappedBy
์์ฑ์ด ์๋ค.
5.5 ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ ์ ์ฅ
public void testSave() {
// ํ1 ์ ์ฅ
Team team1 = new Team("team1", "ํ1");
em.persist(team1);
// ํ์1 ์ ์ฅ
Member member1 = new Member("member1", "ํ์1");
member1.setTeam(team1); //์ฐ๊ด๊ด๊ณ ์ค์ member1 -> team1
em.persist(member1);
// ํ์2 ์ ์ฅ
Member member2 = new Member("member2", "ํ์2");
member2.setTeam(team1); //์ฐ๊ด๊ด๊ณ ์ค์ member2 -> team1
em.persist(member2);
}
// ์ฃผ์ธ์ด ์๋ ๊ณณ์ ์
๋ ฅ๋ ๊ฐ์ ์ธ๋ ํค์ ์ํฅ์ ์ฃผ์ง ์๋๋ค.
team1.getMembers().add(member1); //๋ฌด์(์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ด ์๋)
team1.getMembers().add(member2); //๋ฌด์(์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ด ์๋)
// Member.team์ ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ด๋ค.
// ์ํฐํฐ ๋งค๋์ ๋ ์ด๊ณณ์ ์
๋ ฅ๋ ๊ฐ์ ์ฌ์ฉํด ์ธ๋ ํค ๊ด๋ฆฌํ๋ค.
member1.setTeam(team1); //์ฐ๊ด๊ด๊ณ ์ค์ (์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ)
member2.setTeam(team1); //์ฐ๊ด๊ด๊ณ ์ค์ (์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ)
5.6 ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ์ ์ฃผ์์
๊ฐ์ฅ ํํ ์ค์๋ ์ฐ๊ด๊ด๊ณ ์ฃผ์ธ์๋ ๊ฐ์ ์ ๋ ฅํ์ง ์๊ณ , ์ฃผ์ธ์ด ์๋ ๊ณณ์๋ง ๊ฐ์ ์ ๋ ฅํ๋ ๊ฒ์ด๋ค.
public void testSaveNonOwner() {
// ํ์1 ์ ์ฅ
Member member1 = new Member("member1", "ํ์1");
em.persist(member1);
// ํ์2 ์ ์ฅ
Member member2 = new Member("member2", "ํ์2");
em.persist(member2);
Team team1 = new Team("team1", "ํ1");
// ์ฃผ์ธ์ด ์๋ ๊ณณ์ ์ฐ๊ด๊ด๊ณ ์ค์
team1.getMembers().add(member1);
team2.getMembers().add(member2);
em.persist(team1);
}
์กฐํ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ๋ค.
MEMBER_ID | USERNAME | TEAM_ID |
---|---|---|
member1 | ํ์1 | null |
member2 | ํ์2 | null |
5.6.1 ์์ํ ๊ฐ์ฒด๊น์ง ๊ณ ๋ คํ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ
๊ทธ๋ ๋ค๋ฉด ์ ๋ง ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์๋ง ๊ฐ์ ์ ์ฅํ๊ณ ์ฃผ์ธ์ด ์๋ ๊ณณ์๋ ๊ฐ์ ์ ์ฅํ์ง ์์๋ ๋ ๊น? ๊ฐ์ฒด ๊ด์ ์์๋ ์์ชฝ ๋ฐฉํฅ ๋ชจ๋ ๊ฐ์ ์ ๋ ฅํด์ฃผ๋ ๊ฒ์ด ๊ฐ์ฅ ์์ ํ๋ค.
member1.setTeam(team1); // ํ์ -> ํ
team1.getMembers().add(member1); // ํ -> ํ์
5.6.2 ์ฐ๊ด๊ด๊ณ ํธ์ ๋ฉ์๋
ํ ๋ฒ์ ์๋ฐฉํฅ ๊ด๊ณ๋ฅผ ์ค์ ํ๋ ๋ฉ์๋๋ฅผ ์ฐ๊ด๊ด๊ณ ํธ์ ๋ฉ์๋๋ผ ํ๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ค์๋ ์ค์ด๋ค๊ณ ์ข ๋ ํธํ๊ฒ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ฅผ ์ค์ ํ ์ ์๋ค.
public class Member {
private Team team;
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
5.6.3 ์ฐ๊ด๊ด๊ณ ํธ์ ๋ฉ์๋ ์์ฑ ์ ์ฃผ์์ฌํญ
์ฐ๊ด๊ด๊ณ๋ฅผ ๋ณ๊ฒฝํ ๋๋ ๊ธฐ์กด ํ์ด ์์ผ๋ฉด ๊ธฐ์กด ํ๊ณผ ํ์์ ์ฐ๊ด๊ด๊ณ๋ฅผ ์ญ์ ํ๋ ์ฝ๋๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค.
member1.setTeam(teamA); // 1
member1.setTeam(teamB); // 2
Member findMember = teamA.getMember(); // member1์ด ์ฌ์ ํ ์กฐํ๋๋ค.
public void setTeam(Team team) {
// ๊ธฐ์กด ํ๊ณผ ๊ด๊ณ๋ฅผ ์ ๊ฑฐ
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
5.7 ์ ๋ฆฌ
- ๋จ๋ฐฉํฅ ๋งคํ์ ์ธ์ ๋ ์ฐ๊ด๊ด๊ณ ์ฃผ์ธ์ด๋ค.
- ์๋ฐฉํฅ์ ๋จ๋ฐฉํฅ์ ์ฃผ์ธ์ด ์๋ ์ฐ๊ด๊ด๊ณ๋ฅผ ํ๋ ์ถ๊ฐํ์ ๋ฟ์ด๋ค.
- ์๋ฐฉํฅ์ ์ฅ์ ์ ๋ฐ๋๋ฐฉํฅ์ผ๋ก ๊ฐ์ฒด ๊ทธ๋ํ ํ์ ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋ ๊ฒ ๋ฟ์ด๋ค.
- ์ฃผ์ธ์ ๋ฐ๋ํธ์
mappedBy
๋ก ์ฃผ์ธ์ ์ง์ ํด์ผ ํ๋ค. - ์ฃผ์ธ์ ๋ฐ๋ํธ์ ๋จ์ํ ๋ณด์ฌ์ฃผ๋ ์ผ๋ง ํ ์ ์๋ค.
- ์ฐ๊ด๊ด๊ณ ์ฃผ์ธ์ ์ธ๋ ํค์ ์์น์ ๊ด๋ จํด์ ์ ํด์ผ์ง ๋น์ฆ๋์ค ์ค์๋๋ก ์ ๊ทผํ๋ฉด ์๋๋ค.
6. ๋ค์ํ ์ฐ๊ด๊ด๊ณ ๋งคํ
์ํฐํฐ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งคํํ ๋ ๊ณ ๋ ค์ฌํญ
- ๋ค์ค์ฑ
- ๋ค๋์ผ(@ManyToOne)
- ์ผ๋๋ค(@OneToMany)
- ์ผ๋์ผ(@OneToOne)
- ๋ค๋๋ค(@ManyToMany)
- ๋จ๋ฐฉํฅ, ์๋ฐฉํฅ
- ๋จ๋ฐฉํฅ: ํ์ชฝ๋ง ์ฐธ์กฐํ๋ ๊ฒ
- ์๋ฐฉํฅ: ์์ชฝ์ด ์๋ก ์ฐธ์กฐํ๋ ๊ฒ
- ์ฐ๊ด๊ด๊ณ ์ฃผ์ธ
6.1 ๋ค๋์ผ
๋ฐ์ดํฐ๋ฒ ์ด์ค ํ
์ด๋ธ์ ์ผ๋๋ค ๊ด๊ณ์์ ์ธ๋ ํค๋ ํญ์ ๋ค์ชฝ์ ์๋ค. ๋ฐ๋ผ์ ๊ฐ์ฒด ์๋ฐฉํฅ ๊ด๊ณ์์ ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ ํญ์ ๋ค์ชฝ์ด๋ค.
6.1.1 ๋ค๋์ผ ๋จ๋ฐฉํฅ [N:1]
@ManyToOne
์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํด์ ๋ค๋์ผ ๊ด๊ณ๋ฅผ ๋งคํํ๋ค.@JoinColumn
์ ํ๋๋ฅผ ์ธ๋ ํค์ ๋งคํํ ๋ ์ฌ์ฉํ๋ค.
public class Member {
...์๋ต...
@ManyToOne
@JoinCoulmn(name="TEAM_ID")
private Team team;
...์๋ต...
}
6.1.2 ๋ค๋์ผ ์๋ฐฉํฅ [N:1, 1:N]
public class Member {
...์๋ต...
@ManyToOne
@JoinCoulmn(name="TEAM_ID")
private Team team;
public void setTeam(Team team) {
this.team = team;
// ๋ฌดํ๋ฃจํ์ ๋น ์ง์ง ์๋๋ก ์ฒดํฌ
if(!team.getMembers().contains(this)) {
team.getMembers().add(this);
}
}
...์๋ต...
}
public class Team {
...์๋ต...
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
public void addMember(Member member) {
this.members.add(member);
// ๋ฌดํ๋ฃจํ์ ๋น ์ง์ง ์๋๋ก ์ฒดํฌ
if (member.getTeam() != this) {
member.setTeam(this);
}
}
...์๋ต...
}
- ์๋ฐฉํฅ์ ์ธ๋ ํค๊ฐ ์๋ ์ชฝ์ด ์ฐ๊ด๊ด๊ณ ์ฃผ์ธ์ด๋ค.
- ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ ํญ์ ์๋ก๋ฅผ ์ฐธ์กฐํด์ผ ํ๋ค.
6.2 ์ผ๋๋ค
์ผ๋๋ค ๊ด๊ณ๋ ์ํฐํฐ๋ฅผ ํ๋ ์ด์ ์ฐธ์กฐํ ์ ์์ผ๋ฏ๋ก ์๋ฐ ์ปฌ๋ ์ ์ธ Collection, List, Set, Map ์ค์ ํ๋๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
6.2.1 ์ผ๋๋ค ๋จ๋ฐฉํฅ [1:N]
public class Team {
...์๋ต...
@OneToMany
@JoinColumn(name = "TEAM_ID") // MEMBER ํ
์ด๋ธ์ TEAM_ID (FK)
private List<Member> members = new ArrayList<>();
...์๋ต...
}
- ์ผ๋๋ค ๊ด๊ณ์์๋ ์ผ์ด ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ด๋ค.
- ์ผ ์ชฝ์์ ์ธ๋ํค๋ฅผ ๊ด๋ฆฌํ๊ฒ ๋ค๋ ์๋ฏธ๊ฐ ๋๋ค.
- ์ผ๋ ๋ค ๋จ๋ฐฉํฅ ๊ด๊ณ๋ฅผ ๋งคํํ ๋๋
@JoinColumn
์ ๋ช ์ํด์ผ ํ๋ค. - ๊ทธ๋ ์ง ์์ผ๋ฉด JPA๋ ์ฐ๊ฒฐ ํ
์ด๋ธ์ ์ค๊ฐ์ ๋๊ณ ์ฐ๊ด๊ด๊ณ๋ฅผ ๊ด๋ฆฌํ๋ ์กฐ์ธ ํ
์ด๋ธ ์ ๋ต์ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๋ค.
์ผ๋๋ค ๋ฌธ์ ์
- ์ํฐํฐ๋ฅผ ๋งคํํ ํ ์ด๋ธ์ด ์๋ ๋ค์ ํ ์ด๋ธ์ ์ธ๋ ํค๋ฅผ ๊ด๋ฆฌํด์ผ ํ๋ค.
- ์ฑ๋ฅ ๋ฌธ์
Member member1 = new Memeber("member1");
Member member2 = new Memeber("member2");
Team team1 = new Team("team1");
team1.getMembers().add(member1);
team1.getMembers().add(member2);
em.persist(member1); // INSERT MEMBER1
em.persist(member2); // INSERT MEMBER2
em.persist(team1); // INSERT TEAM, UPDATE MEMBER1, UPDATE MEMBER2
tx.commit();
-- ์
๋ฐ์ดํธ ์ฟผ๋ฆฌ๊ฐ ์ถ๊ฐ๋๋ค.
insert into Member (MEMBER_ID, username) values (null, ?);
insert into Team (TEAM_ID, name) values (null, ?);
update Member set TEAM_ID=? where MEMBER_ID=?
์ผ๋๋ค ๋จ๋ฐฉํฅ ๋งคํ๋ณด๋ค๋ ๋ค๋์ผ ์๋ฐฉํฅ ๋งคํ์ ์ฌ์ฉํ์
6.2.2 ์ผ๋๋ค ์๋ฐฉํฅ [1:N, N:1]
public class Team {
...์๋ต...
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
...์๋ต...
}
public class Member {
...์๋ต...
@ManyToOne
@JoinCoulmn(name = "TEAM_ID",
insertable = false, updatable = false) // ์ฝ๊ธฐ ์ ์ฉ ๋งคํ
private Team team;
...์๋ต...
}
์ผ๋๋ค ๋จ๋ฐฉํฅ ๋งคํ์ด ๊ฐ์ง๋ ๋จ์ ์ ๊ทธ๋๋ก ๊ฐ์ง๋ค.
6.3 ์ผ๋์ผ [1:1]
์ผ๋์ผ ๊ด๊ณ ํน์ง
- ์ผ๋์ผ ๊ด๊ณ๋ ๊ทธ ๋ฐ๋๋ ์ผ๋์ผ ๊ด๊ณ๋ค.
- ํ
์ด๋ธ ๊ด๊ณ์์ ์ผ๋๋ค, ๋ค๋์ผ์ ํญ์ ๋ค(N)์ชฝ์ด ์ธ๋ ํค๋ฅผ ๊ฐ์ง๋ค. ๋ฐ๋ฉด ์ผ๋์ผ ๊ด๊ณ๋ ์ฃผ ํ
์ด๋ธ์ด๋ ๋์ ํ
์ด๋ธ ๋ ์ค ์ด๋ ๊ณณ์ด๋ ์ธ๋ ํค๋ฅผ ๊ฐ์ง ์ ์๋ค.
6.3.1 ์ฃผ ํ ์ด๋ธ์ ์ธ๋ ํค
์ฃผ ํ ์ด๋ธ์ ์ธ๋ ํค๊ฐ ์๋ ๋จ๋ฐฉํฅ ๊ด๊ณ๋ฅผ ๋จผ์ ์ดํด๋ณด๊ณ , ์๋ฐฉํฅ ๊ด๊ณ๋ ์ดํด ๋ณด์.
๋จ๋ฐฉํฅ
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
// @OneToOne์ ์ฌ์ฉํด ์ผ๋์ผ ๋งคํํ๋ค.
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
...์๋ต...
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
...์๋ต...
}
์๋ฐฉํฅ
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
...์๋ต...
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
@OneToOne(mappedBy = "locker") // ์ฐ๊ด๊ด๊ณ ์ฃผ์ธ ํ๋๋ช
์ ์ ์ธ
private Member member;
...์๋ต...
}
6.3.2 ๋์ ํ ์ด๋ธ์ ์ธ๋ ํค
๋์ ํ ์ด๋ธ์ ์ธ๋ ํค๊ฐ ์๋ ์ผ๋์ผ ๊ด๊ณ๋ฅผ ์์๋ณธ๋ค.
๋จ๋ฐฉํฅ
์ผ๋์ผ ๋จ๋ฐฉํฅ์ ์ด๋ฐ ๋งคํ์ ํ์ฉํ์ง ์๋๋ค.
์๋ฐฉํฅ
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@OneToOne(mappedBy = "member")
private Locker locker;
...์๋ต...
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
@OneToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
...์๋ต...
}
์ผ๋์ผ ๋งคํ์์ ๋์ ํ
์ด๋ธ์ ์ธ๋ ํค๋ฅผ ๋๊ณ ์ถ์ผ๋ฉด ์ด๋ ๊ฒ ์๋ฐฉํฅ์ผ๋ก ๋งคํํ๋ค.
6.4 ๋ค๋๋ค [N:N]
ํ
์ด๋ธ 2๊ฐ๋ก๋ ๋ค๋๋ค ๊ด๊ณ๋ฅผ ํํํ ์ ์๋ค.
์ค๊ฐ์ ์ฐ๊ฒฐ ํ
์ด๋ธ์ ์ถ๊ฐํด์ผ ํ๋ค.
6.4.1 ๋ค๋๋ค: ๋จ๋ฐฉํฅ
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@ManyToMany
@JoinTable(
name = "MEMBER_PRODUCT", // ์ฐ๊ฒฐ ํ
์ด๋ธ ์ง์
joinColumn = @JoinColumn(name = "MEMBER_ID"), // ํ์ฌ ์ํฐํฐ์ ๋งคํํ ์ฐ๊ฒฐ ํ
์ด๋ธ์ ์กฐ์ธ ์ปฌ๋ผ
inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID") // ์๋ ์ํฐํฐ์ ๋งคํํ ์ฐ๊ฒฐ ํ
์ด๋ธ์ ์กฐ์ธ ์ปฌ๋ผ
)
private List<Product> products = new ArrayList<Product>();
...์๋ต...
}
@Entity
public class Product {
@Id @Column(name = "PRODUCT_ID")
private Long id;
...์๋ต...
}
6.4.2 ๋ค๋๋ค: ์๋ฐฉํฅ
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@ManyToMany
@JoinTable(
name = "MEMBER_PRODUCT", // ์ฐ๊ฒฐ ํ
์ด๋ธ ์ง์
joinColumn = @JoinColumn(name = "MEMBER_ID"), // ํ์ฌ ์ํฐํฐ์ ๋งคํํ ์ฐ๊ฒฐ ํ
์ด๋ธ์ ์กฐ์ธ ์ปฌ๋ผ
inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID") // ์๋ ์ํฐํฐ์ ๋งคํํ ์ฐ๊ฒฐ ํ
์ด๋ธ์ ์กฐ์ธ ์ปฌ๋ผ
)
private List<Product> products = new ArrayList<Product>();
...์๋ต...
}
@Entity
public class Product {
@Id @Column(name = "PRODUCT_ID")
private Long id;
@ManyToMany(mappedBy = "products") // ์ญ๋ฐฉํฅ ์ถ๊ฐ
private List<Member> members;
...์๋ต...
}
6.4.3 ๋ค๋๋ค: ๋งคํ์ ํ๊ณ์ ๊ทน๋ณต, ์ฐ๊ฒฐ ์ํฐํฐ ์ฌ์ฉ
์ฐ๊ฒฐํ
์ด๋ธ์ ๊ธฐ๋ณธ ํค๋ฅผ @IdClass
์ฌ์ฉํ ๋ณตํฉ ํค ์ฌ์ฉ ์์ ๋ผ์ ๋๊น.
6.4.4 ๋ค๋๋ค: ์๋ก์ด ๊ธฐ๋ณธ ํค ์ฌ์ฉ
@ManyToMany
๋ ํธ๋ฆฌํ์ง๋ง, ์ค๋ฌด์์ ์ฌ์ฉํ๊ธฐ์๋ ํ๊ณ๊ฐ ์๋ค.- ๋ณดํต์ ์ฐ๊ฒฐ ํ ์ด๋ธ์ ์๋ก์ด ์ปฌ๋ผ์ด ์ถ๊ฐ๋๊ธฐ ๋ง๋ จ์ด๋ค.
- ๊ฒฐ๊ตญ ์ฐ๊ฒฐ ํ ์ด๋ธ์ ๋งคํํ๋ ์ฐ๊ฒฐ ์ํฐํฐ๋ฅผ ๋ง๋ค๊ณ ์ผ๋๋ค, ๋ค๋์ผ ๊ด๊ณ๋ก ํ์ด์ผ ํ๋ค.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts;
...์๋ต...
}
@Entity
public class Product {
@Id @Column(name = "PRODUCT_ID")
private Long id;
// ์ํ ์ํฐํฐ์์ ํ์์ํ ์ํฐํฐ๋ก ๊ฐ์ฒด ๊ทธ๋ํ ํ์ ๊ธฐ๋ฅ์ด ํ์ ์๋ค ํ๋จ.
// ์ฐ๊ด๊ด๊ณ๋ฅผ ๋ง๋ค์ง ์์๋ค.
// @OneToMany(mappedBy = "product")
// private List<MemberProduct> memberProducts = new ArrayList<>();
}
@Entity
public class MemberProduct {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
}
6.4.5 ๋ค๋๋ค ์ฐ๊ด๊ด๊ณ ์ ๋ฆฌ
- ๋ค๋๋ค ๊ด๊ณ๋ฅผ ์ผ๋๋ค, ๋ค๋์ผ ๊ด๊ณ๋ก ํ์ด๋ธ๋ค.
- ์ฐ๊ฒฐ ํ
์ด๋ธ์ ๋ง๋ค ๋ ์๋ณ์๋ฅผ ์ด๋ป๊ฒ ๊ตฌ์ฑํ ์ง ์ ํํ๋ค.
- ์๋ณ ๊ด๊ณ: ๋ฐ์์จ ์๋ณ์๋ฅผ ๊ธฐ๋ณธ ํค + ์ธ๋ ํค๋ก ์ฌ์ฉํ๋ค.
- ๋น์๋ณ ๊ด๊ณ: ๋ฐ์์จ ์๋ณ์๋ ์ธ๋ ํค๋ก๋ง ์ฌ์ฉํ๊ณ ์๋ก์ด ์๋ณ์๋ฅผ ์ถ๊ฐํ๋ค. ์ถ์ฒ ๐
7. ๊ณ ๊ธ ๋งคํ
๋ค๋ฃฐ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ๋ค.
- ์์ ๊ด๊ณ ๋งคํ: ๊ฐ์ฒด์ ์์ ๊ด๊ณ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ด๋ป๊ฒ ๋งคํํ๋์ง
- @MappedSuperclass: ๋ฑ๋ก์ผ, ์์ ์ผ ๊ฐ์ด ์ฌ๋ฌ ์ํฐํฐ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๋ ๋งคํ ์ ๋ณด๋ง ์์๋ฐ๊ณ ์ถ์ ๋
- ๋ณตํฉ ํค์ ์๋ณ ๊ด๊ณ ๋งคํ: ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์๋ณ์๊ฐ ํ๋ ์ด์์ผ ๋ ๋งคํํ๋ ๋ฐฉ๋ฒ
- ์กฐ์ธ ํ ์ด๋ธ: ์ฐ๊ฒฐ ํ ์ด๋ธ์ ๋งคํํ๋ ๋ฐฉ๋ฒ
- ์ํฐํฐ ํ๋์ ์ฌ๋ฌ ํ
์ด๋ธ ๋งคํํ๋ ๋ฐฉ๋ฒ
7.1 ์์ ๊ด๊ณ ๋งคํ
์ํผํ์ ์๋ธํ์ ๋ ผ๋ฆฌ ๋ชจ๋ธ์ ํ ์ด๋ธ๋ก ๊ตฌํํ๋ 3๊ฐ์ง ๋ฐฉ๋ฒ
- ๊ฐ๊ฐ์ ํ ์ด๋ธ๋ก ๋ณํ: JPA์์๋ ์กฐ์ธ ์ ๋ต์ด๋ผ ํ๋ค.
- ํตํฉ ํ ์ด๋ธ๋ก ๋ณํ: JPA์์๋ ๋จ์ผ ํ ์ด๋ธ ์ ๋ต์ด๋ผ ํ๋ค.
- ์๋ธํ์
ํ
์ด๋ธ๋ก ๋ณํ: JPA์์๋ ๊ตฌํ ํด๋์ค๋ง๋ค ํ
์ด๋ธ ์ ๋ต์ด๋ผ ํ๋ค.
7.1.1 ์กฐ์ธ ์ ๋ต
์ํฐํฐ ๊ฐ๊ฐ์ ๋ชจ๋ ํ ์ด๋ธ๋ก ๋ง๋ค๊ณ ์์ ํ ์ด๋ธ์ด ๋ถ๋ชจ ํ ์ด๋ธ์ ๊ธฐ๋ณธ ํค๋ฅผ ๋ฐ์์ ๊ธฐ๋ณธ ํค + ์ธ๋ ํค๋ก ์ฌ์ฉํ๋ ์ ๋ต
@Entity
@Inheritance(strategy = InheritanceType.JOIN) // ๋ถ๋ชจ ํด๋์ค์ ์์ ๋งคํ, ์ ๋ต ์ง์
@DiscriminatorColumn(name = "DTYPE") // ๋ถ๋ชจ ํด๋์ค์ ๊ตฌ๋ถ ์ปฌ๋ผ ์ง์
public abstract class Item {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name; //์ด๋ฆ
private int price; //๊ฐ๊ฒฉ
}
@Entity
@DiscriminatorValue("A") // ์ํฐํฐ๋ฅผ ์ ์ฅํ ๋ ๊ตฌ๋ถ ์ปฌ๋ผ์ ์
๋ ฅํ ๊ฐ์ ์ง์
public class Album extends Item {
private String artist;
}
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {
private String director;
private String actor;
}
@Entity
@DiscriminatorValue("B")
@PrimaryKeyJoinColumn(name = "BOOK_ID") // ๊ธฐ๋ณธ ํค ์ปฌ๋ผ๋ช
๋ณ๊ฒฝ, ๊ธฐ๋ณธ๊ฐ์ ๋ถ๋ชจ ํ
์ด๋ธ ID ์ปฌ๋ผ๋ช
public class Book extends Item {
private String author;
private String isbn;
}
์ฅ์
- ํ ์ด๋ธ์ด ์ ๊ทํ๋๋ค.
- ์ธ๋ ํค ์ฐธ์กฐ ๋ฌด๊ฒฐ์ฑ ์ ์ฝ์กฐ๊ฑด์ ํ์ฉํ ์ ์๋ค.
- ์ ์ฅ๊ณต๊ฐ์ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ๋ค.
๋จ์
- ์กฐํํ ๋ ์กฐ์ธ์ด ๋ง์ด ์ฌ์ฉ๋์ด ์ฑ๋ฅ์ด ์ ํ๋ ์ ์๋ค.
- ์กฐํ ์ฟผ๋ฆฌ๊ฐ ๋ณต์กํ๋ค.
- ๋ฐ์ดํฐ๋ฅผ ๋ฑ๋กํ ๋ INSERT SQL์ด ๋ ๋ฒ ์คํ๋๋ค.
ํน์ง
@DiscriminatorColumn
์์ด๋ ๋์ํ ์ ์๋ค.
๊ด๋ จ ์ด๋ ธํ ์ด์
@PrimaryKeyJoinColumn
,@DiscriminatorColumn
,@DiscriminatorValue
7.1.2 ๋จ์ผ ํ ์ด๋ธ ์ ๋ต
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // ๋ถ๋ชจ ํด๋์ค์ ์์ ๋งคํ, ์ ๋ต ์ง์
@DiscriminatorColumn(name = "DTYPE") // ๋ถ๋ชจ ํด๋์ค์ ๊ตฌ๋ถ ์ปฌ๋ผ ์ง์
public abstract class Item {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item {...}
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {...}
@Entity
@DiscriminatorValue("B")
public class Book extends Item {...}
์ฅ์
- ์กฐ์ธ์ด ํ์ ์์ผ๋ฏ๋ก ์กฐํ ์ฑ๋ฅ์ด ๋น ๋ฅด๋ค.
- ์กฐํ ์ฟผ๋ฆฌ๊ฐ ๋จ์ํ๋ค.
๋จ์
- ์์ ์ํฐํฐ๊ฐ ๋งคํํ ์ปฌ๋ผ์ ๋ชจ๋ null์ ํ์ฉํด์ผ ํ๋ค.
- ๋จ์ผ ํ ์ด๋ธ์ ๋ชจ๋ ๊ฒ์ ์ ์ฅํ๋ฏ๋ก ํ ์ด๋ธ์ด ์ปค์ง ์ ์๋ค. ๊ทธ๋ฌ๋ฏ๋ก ์ํฉ์ ๋ฐ๋ผ์๋ ์กฐํ ์ฑ๋ฅ์ด ์คํ๋ ค ๋๋ ค์ง ์ ์๋ค.
ํน์ง
@DiscriminatorColumn
์ ๊ผญ ์ค์ ํด์ผ ํ๋ค.@DiscriminatorValue
๋ฅผ ์ง์ ํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ์ผ๋ก ์ํฐํฐ ์ด๋ฆ์ ์ฌ์ฉํ๋ค.
7.1.3 ๊ตฌํ ํด๋์ค๋ง๋ค ํ ์ด๋ธ ์ ๋ต
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item {...}
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {...}
@Entity
@DiscriminatorValue("B")
public class Book extends Item {...}
์ผ๋ฐ์ ์ผ๋ก ์ถ์ฒํ์ง ์๋ ์ ๋ต์ด๋ค.
์ฅ์
- ์๋ธ ํ์ ์ ๊ตฌ๋ถํด์ ์ฒ๋ฆฌํ ๋ ํจ๊ณผ์ ์ด๋ค.
- not null ์ ์ฝ ์กฐ๊ฑด์ ์ฌ์ฉํ ์ ์๋ค.
๋จ์
- ์ฌ๋ฌ ์์ ํ ์ด๋ธ์ ํจ๊ป ์กฐํํ ๋ ์ฑ๋ฅ์ด ๋๋ฆฌ๋ค. (UNION์ ์ฌ์ฉํด์ผ ํ๋ค)
- ์์ ํ ์ด๋ธ์ ํตํฉํด์ ์ฟผ๋ฆฌํ๊ธฐ ์ด๋ ต๋ค.
ํน์ง
- ๊ตฌ๋ถ ์ปฌ๋ผ์ ์ฌ์ฉํ์ง ์๋๋ค.
7.2 @MappedSuperclass
๋ถ๋ชจ ํด๋์ค๋ ํ
์ด๋ธ๊ณผ ๋งคํํ์ง ์๊ณ ์์ ํด๋์ค์๊ฒ ๋งคํ ์ ๋ณด๋ง ์ ๊ณตํ๊ณ ์ถ์ผ๋ฉด @MappedSuperclass
์ ์ฌ์ฉํ๋ค.
// ๊ณตํต ๋งคํ ์ ๋ณด ์ ์
@MappedSuperclass
public abstract class BaseEntity {
@Id @GeneratedValue
private Long id;
private String name;
}
@Entity
public class Member extends BaseEntity {
private String email;
}
@Entity
public class Seller extends BaseEntity {
private String shopName;
}
- ์ฐ๊ด๊ด๊ณ๋ฅผ ์ฌ์ ์ ํ๋ ค๋ฉด
@AssociationOverrides
,@AssociationOverride
์ฌ์ฉํ๋ค. - ๋ถ๋ชจ๋ก๋ถํฐ ๋ฌผ๋ ค๋ฐ์ ๋งคํ ์ ๋ณด๋ฅผ ์ฌ์ ์ ํ๋ ค๋ฉด
@AttributeOverrides
,@AttributeOverride
์ฌ์ฉํ๋ค.
// ๋งคํ ์ ๋ณด ์ฌ์ ์
// ๋ถ๋ชจ์๊ฒ ์์๋ฐ์ id ์์ฑ์ ์ปฌ๋ผ๋ช
์ MEMBER_ID๋ก ์ฌ์ ์
@Entity
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID"))
public class Member extends BaseEntity {
private String email;
}
// ๋ ์ด์์ ๋งคํ ์ ๋ณด ์ฌ์ ์
@Entity
@AttributeOverrides({
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID")),
@AttributeOverride(name = "name", column = @Column(name = "MEMBER_NAME"))
})
public class Member extends BaseEntity {
private String email;
}
@MappedSuperclass ํน์ง
- ํ ์ด๋ธ๊ณผ ๋งคํ๋์ง ์๋๋ค.
- ์์ ํด๋์ค์๊ฒ ์ํฐํฐ์ ๋งคํ ์ ๋ณด๋ฅผ ์์ํ๊ธฐ ์ํด ์ฌ์ฉํ๋ค.
- ์ํฐํฐ๊ฐ ์๋๋ฏ๋ก
em.find()
,JPQL
์์ ์ฌ์ฉํ ์ ์๋ค. - ์ง์ ์์ฑํด์ ์ฌ์ฉํ ์ผ์ด ๊ฑฐ์ ์์ผ๋ฏ๋ก ์ถ์ ํด๋์ค๋ก ๋ง๋๋ ๊ฒ์ ๊ถ์ฅํ๋ค.
- ๋ฑ๋ก์ผ์, ์์ ์ผ์, ๋ฑ๋ก์, ์์ ์ ๊ฐ์ ๊ณน์ค ์์ฑ์ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋ค.
7.3 ๋ณตํฉ ํค์ ์๋ณ ๊ด๊ณ ๋งคํ
๋ณตํฉ ํค๋ฅผ ๋งคํํ๋ ๋ฐฉ๋ฒ๊ณผ ์๋ณ ๊ด๊ณ, ๋น์๋ณ ๊ด๊ณ๋ฅผ ๋งคํํ๋ ๋ฐฉ๋ฒ์ ์์๋ณธ๋ค.
7.3.1 ์๋ณ ๊ด๊ณ vs ๋น์๋ณ ๊ด๊ณ
์๋ณ ๊ด๊ณ
๋ถ๋ชจ ํ
์ด๋ธ์ ๊ธฐ๋ณธ ํค๋ฅผ ๋ด๋ ค๋ฐ์์ ์์ ํ
์ด๋ธ์ ๊ธฐ๋ณธ ํค + ์ธ๋ ํค๋ก ์ฌ์ฉํ๋ ๊ด๊ณ๋ค.
๋น์๋ณ ๊ด๊ณ
๋ถ๋ชจ ํ
์ด๋ธ์ ๊ธฐ๋ณธ ํค๋ฅผ ๋ฐ์์ ์์ ํ
์ด๋ธ์ ์ธ๋ ํค๋ก๋ง ์ฌ์ฉํ๋ ๊ด๊ณ๋ค.
- ํ์์ ๋น์๋ณ ๊ด๊ณ(Mandatory): ์ธ๋ ํค์ null์ ํ์ฉํ์ง ์๋๋ค. ์ฐ๊ด๊ด๊ณ๋ฅผ ํ์์ ์ผ๋ก ๋งบ์ด์ผ ํ๋ค.
- ์ ํ์ ๋น์๋ณ ๊ด๊ณ(Optional): ์ธ๋ ํค์ null์ ํ์ฉํ๋ค. ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ์์ง ๋ง์ง ์ ํํ ์ ์๋ค.
์ฐธ๊ณ
์ต๊ทผ์๋ ๋น์๋ณ ๊ด๊ณ๋ฅผ ์ฃผ๋ก ์ฌ์ฉํ๊ณ ๊ผญ ํ์ํ ๊ณณ์๋ง ์๋ณ ๊ด๊ณ๋ฅผ ์ฌ์ฉํ๋ ์ถ์ธ
7.3.2 ๋ณตํฉ ํค: ๋น์๋ณ ๊ด๊ณ ๋งคํ
JPA๋ ๋ณตํฉ ํค๋ฅผ ์ง์ํ๊ธฐ ์ํด ๋ค์ 2๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค.
@IdClass
: ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ฐ๊น์ด ๋ฐฉ๋ฒ@EmbeddedId
: ๊ฐ์ฒด์งํฅ์ ๊ฐ๊น์ด ๋ฐฉ๋ฒ
@IdClass
@Entity
@IdClass(ParentId.class)
public class Parent {
@Id
@Column(name = "PARENT_ID1")
private String id1;
@Id
@Column(name = "PARENT_ID2")
private String id1;
}
public class ParentId implements Serializable {
private String id1;
private String id2;
public ParentId() {}
...
@Override
public boolean equals (Object o) {...}
@Override
public int hashCode() {...}
}
@IdClass๋ฅผ ์ฌ์ฉํ ๋ ์๋ณ์ ํด๋์ค๋ ๋ค์ ์กฐ๊ฑด์ ๋ง์กฑํด์ผ ํ๋ค.
- ์๋ณ์ ํด๋์ค์ ์์ฑ๋ช ๊ณผ ์ํฐํฐ์์ ์ฌ์ฉํ๋ ์๋ณ์์ ์์ฑ๋ช ์ด ๊ฐ์์ผ ํ๋ค.
Serializable
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ผ ํ๋ค.equals
,hashCode
๋ฅผ ๊ตฌํํด์ผ ํ๋ค.- ๊ธฐ๋ณธ ์์ฑ์๊ฐ ์์ด์ผ ํ๋ค.
- ์๋ณ์ ํด๋์ค๋
public
์ด์ด์ผ ํ๋ค.
์ฌ์ฉ๋ฐฉ๋ฒ
// ์ ์ฅ
Parent parent = new Parent();
parent.setId1("1");
parent.setId2("2");
em.persist(parent);
// ์กฐํ
ParentId parentId = new ParentId("1","2");
Parent parent = em.find(Parent.class, parentId);
์์ ํด๋์ค
์์ ํ
์ด๋ธ์์ ์ธ๋ ํค ๋งคํ ๋ฐฉ๋ฒ
@Entity
public class Child {
@Id
private String id;
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID1", referencedColumnName = "PARENT_ID1"),
@JoinColumn(name = "PARENT_ID2", referencedColumnName = "PARENT_ID2")
})
private Parent parent;
}
@EmbeddedId
@Entity
public class Parent {
@EmbeddedId
private ParentId id;
}
@Embeddable
public class ParentId implements Serializable {
@Column(name = "PARENT_ID1")
private String id1;
@Column(name = "PARENT_ID2")
private String id2;
public ParentId() {}
...
@Override
public boolean equals (Object o) {...}
@Override
public int hashCode() {...}
}
@EmbeddedId๋ฅผ ์ฌ์ฉํ ๋ ์๋ณ์ ํด๋์ค๋ ๋ค์ ์กฐ๊ฑด์ ๋ง์กฑํด์ผ ํ๋ค.
@Embeddable
์ด๋ ธํ ์ด์ ์ ๋ถ์ฌ์ฃผ์ด์ผ ํ๋ค.Serializable
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ผ ํ๋ค.equals
,hashCode
๋ฅผ ๊ตฌํํด์ผ ํ๋ค.- ๊ธฐ๋ณธ ์์ฑ์๊ฐ ์์ด์ผ ํ๋ค.
- ์๋ณ์ ํด๋์ค๋
public
์ด์ด์ผ ํ๋ค.
์ฌ์ฉ๋ฐฉ๋ฒ
// ์ ์ฅ
Parent parent = new Parent();
ParentId parentId = new ParentId("1","2");
parent.setId(parentId);
em.persist(parent);
// ์กฐํ
ParentId parentId = new ParentId("1","2");
Parent parent = em.find(Parent.class, parentId);
@IdClass vs @EmbeddedId
@IdClass
: ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ฐ๊น์ด ๋ฐฉ๋ฒ@EmbeddedId
: ๊ฐ์ฒด์งํฅ์ ๊ฐ๊น์ด ๋ฐฉ๋ฒ
// JPQL
em.createQuery("select p.id.id1, p.id.id2 from Parent p"); // @EmbeddedId
em.createQuery("select p.id1, p.id2 from Parent p"); // @IdClass
์ฐธ๊ณ
๋ณตํฉ ํค์๋ @GenerateValue
๋ฅผ ์ฌ์ฉํ ์ ์๋ค. ๋ณตํฉ ํค๋ฅผ ๊ตฌ์ฑํ๋ ์ฌ๋ฌ ์ปฌ๋ผ์ค ํ๋์๋ ์ฌ์ฉํ ์ ์๋ค.
7.3.3 ๋ณตํฉ ํค: ์๋ณ ๊ด๊ณ ๋งคํ
// ๋ถ๋ชจ
@Entity
public class Parent {
@Id
@Column(name = "PARENT_ID")
private String id;
}
// ์์
@Entity
@IdClass(ChildId.class)
public class Child {
@Id
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;
@Id @Column(name = "CHILD_ID")
private String childId;
}
// ์์ ID
public class ChildId implements Serializable {
private String parent;
private String childId;
...์๋ต...
}
// ์์
@Entity
@IdClass(GrandChildId.class)
public class GrandChild {
@Id
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID"),
@JoinColumn(name = "CHILD_ID")
})
private Child child;
@Id @Column(name = "GRANDCHILD_ID")
private String id;
}
// ์์ ID
public class GrandChildId implements Serializable {
private ChildId child;
private String id;
...์๋ต...
}
@EmbeddedId์ ์๋ณ ๊ด๊ณ
// ๋ถ๋ชจ
@Entity
public class Parent {
@Id
@Column(name = "PARENT_ID")
private String id;
}
// ์์
@Entity
public class Child {
@EmbeddedId
private ChildId id;
@MapsId("parentId")
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;
}
// ์์ ID
@Embeddable
public class ChildId implements Serializable {
private String parentId; // @MapsId("parentId")๋ก ๋งคํ
@Column(name = "CHILD_ID")
private String id;
...์๋ต...
}
// ์์
@Entity
public class GrandChild {
@EmbeddedId
private GrandChildId id;
@MapsId("childId")
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID"),
@JoinColumn(name = "CHILD_ID")
})
private Child child;
}
// ์์ ID
@Embeddable
public class GrandChildId implements Serializable {
private ChildId childId; // @MapsId("childId")๋ก ๋งคํ
@Column(name = "GRANDCHILD_ID")
private String id;
...์๋ต...
}
7.3.4 ๋น์๋ณ ๊ด๊ณ๋ก ๊ตฌํ
// ๋ถ๋ชจ
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
}
// ์์
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;
}
// ์์
@Entity
public class GrandChild {
@Id @GeneratedValue
@Column(name = "GRANDCHILD_ID")
private Long id;
@ManyToOne
@JoinColumn(name = "CHILD_ID")
private Child child;
}
์๋ณ ๊ด๊ณ์ ๋ณตํฉ ํค๋ฅผ ์ฌ์ฉํ ์ฝ๋์ ๋น๊ตํ๋ฉด ๋งคํ๋ ์ฝ๊ณ ์ฝ๋๋ ๋จ์ํ๋ค.
7.3.5 ์ผ๋์ผ ์๋ณ ๊ด๊ณ
์ผ๋์ผ ์๋ณ ๊ด๊ณ๋ ์์ ํ ์ด๋ธ์ ๊ธฐ๋ณธ ํค ๊ฐ์ผ๋ก ๋ถ๋ชจ ํ ์ด๋ธ์ ๊ธฐ๋ณธ ํค ๊ฐ๋ง ์ฌ์ฉํ๋ค.
// ๋ถ๋ชจ
@Entity
public class Board {
@Id @GeneratedValue
@Column(name = "BOARD_ID")
private Long id;
@OneToOne(mappedBy = "board")
private BoardDetail boardDetail;
}
@Entity
public class BoardDetail {
@Id
private Long boardId;
// ์๋ณ์๊ฐ ๋จ์ํ ์ปฌ๋ผ ํ๋๋ฉด @MapsId๋ฅผ ์ฌ์ฉํ๊ณ ์์ฑ ๊ฐ์ ๋น์๋๋ฉด ๋๋ค.
@MapsId // BoardDetail.boardId ๋งคํ
@OneToOne
@JoinColumn(name = "BOARD_ID")
private Board board;
}
์ฌ์ฉ๋ฐฉ๋ฒ
Board board = new Board();
em.persist(board);
BoardDetail boardDetail = new BoardDetail();
boardDetail.setBoard(board);
em.persist(boardDetail);
7.3.6 ์๋ณ, ๋น์๋ณ ๊ด๊ณ์ ์ฅ๋จ์
์๋ณ ๊ด๊ณ ์ฅ์
- ํน์ ์ํฉ์์ ์กฐ์ธ ์์ด ํ์ ํ ์ด๋ธ๋ง์ผ๋ก ๊ฒ์ํ ์ ์๋ค.
์๋ณ ๊ด๊ณ ๋จ์
- ์๋ณ ๊ด๊ณ๋ ๋ถ๋ชจ ํ
์ด๋ธ์ ๊ธฐ๋ณธ ํค๋ฅผ ์์ ํ
์ด๋ธ๋ก ์ ํํ๋ฉด์ ์์ ํ
์ด๋ธ์ ๊ธฐ๋ณธ ํค ์ปฌ๋ผ์ด ์ ์ ๋์ด๋๋ค.
- SQL๋ณต์ก, ๊ธฐ๋ณธ ํค ์ธ๋ฑ์ค๊ฐ ์ปค์ง
- ์๋ณ ๊ด๊ณ๋ ๋ณตํฉ ๊ธฐ๋ณธ ํค๋ฅผ ๋ง๋ค์ด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
- ์๋ณ ๊ด๊ณ๋ฅผ ์ฌ์ฉํ ๋ ๊ธฐ๋ณธ ํค๋ก ๋น์ฆ๋์ค ์๋ฏธ๊ฐ ์๋ ์์ฐ ํค ์ปฌ๋ผ์ ์กฐํฉ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
- ๋น์ฆ๋์ค ์๊ตฌ์ฌํญ์ ์ธ์ ๋ ๋ณํ ์ ์๋ค.
- ์๋ณ ๊ด๊ณ๋ ํ ์ด๋ธ ๊ตฌ์กฐ๊ฐ ์ ์ฐํ์ง ๋ชปํ๋ค.
- ์๋ณ ๊ด๊ณ๋ ์ผ๋์ผ ๊ด๊ณ๋ฅผ ์ ์ธํ๊ณ ๋ณ๋์ ๋ณตํฉ ํค ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ค.
- ๊ธฐ๋ณธ ํค ๋งคํ์ ๋ง์ ๋
ธ๋ ฅ์ด ํ์ํ๋ค.
- ๊ธฐ๋ณธ ํค ๋งคํ์ ๋ง์ ๋
ธ๋ ฅ์ด ํ์ํ๋ค.
๋น์๋ณ ๊ด๊ณ ์ฅ์
@GenerateValue
์ฒ๋ผ ํธ๋ฆฌํ ๋๋ฆฌ ํค ์์ฑ ๋ฐฉ๋ฒ ์ฌ์ฉ ๊ฐ๋ฅ- ์๋ณ์ ์ปฌ๋ผ์ด ํ๋์ฌ์ ๋งคํ์ด ์ฝ๋ค.
๋น์๋ณ ๊ด๊ณ ๋จ์
- JPQL ์กฐํ์ ๊ธธ์ด์ง ์ ์๋ค.
๊ฒฐ๋ก
๋น์๋ณ ๊ด๊ณ๋ฅผ ์ฌ์ฉํ๊ณ ๊ธฐ๋ณธ ํค๋ Long
ํ์
์ ๋๋ฆฌ ํค๋ฅผ ์ฌ์ฉํ๋ค.
7.4 ์กฐ์ธ ํ ์ด๋ธ
๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ์ ์ฐ๊ด๊ด๊ณ๋ฅผ ์ค๊ณํ๋ ๋ฐฉ๋ฒ์ ํฌ๊ฒ 2๊ฐ์ง๋ค.
์กฐ์ธ ์ปฌ๋ผ
์กฐ์ธ ํ ์ด๋ธ
์ถ์ฒ ๋ฐฉ๋ฒ
๊ธฐ๋ณธ์ ์กฐ์ธ ์ปฌ๋ผ์ ์ฌ์ฉํ๊ณ ํ์ํ๋ค๊ณ ํ๋จ๋๋ฉด ์กฐ์ธ ํ
์ด๋ธ์ ์ฌ์ฉํ์.
7.4.1 ์ผ๋์ผ ์กฐ์ธ ํ ์ด๋ธ
// ๋ถ๋ชจ
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
@OneToOne
@JoinTable(
name = "PARENT_CHILD",
joinColumn = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID")
)
private Child child;
}
// ์์
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
// ์๋ฐฉํฅ์ผ๋ก ๋งคํํ๋ ค๋ฉด ๋ค์ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
// @OneToOne(mappedBy = "child")
// private Parent parent;
}
7.4.2 ์ผ๋๋ค ์กฐ์ธ ํ ์ด๋ธ
// ๋ถ๋ชจ
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
@OneToMany
@JoinTable(
name = "PARENT_CHILD",
joinColumn = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID")
)
private List<Child> child = new ArrayList<Child>();
}
// ์์
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
}
7.4.2 ๋ค๋์ผ ์กฐ์ธ ํ ์ด๋ธ
// ๋ถ๋ชจ
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
@OneToMany(mappedBy = "parent")
private List<Child> child = new ArrayList<Child>();
}
// ์์
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
@ManyToOne
@JoinTable(
name = "PARENT_CHILD",
joinColumn = @JoinColumn(name = "CHILD_ID"),
inverseJoinColumns = @JoinColumn(name = "PARENT_ID")
)
private Parent parent;
}
7.4.4 ๋ค๋๋ค ์กฐ์ธ ํ ์ด๋ธ
// ๋ถ๋ชจ
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
@ManyToMany
@JoinTable(
name = "PARENT_CHILD",
joinColumn = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID")
)
private List<Child> child = new ArrayList<Child>();
}
// ์์
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
}
7.5 ์ํฐํฐ ํ๋์ ์ฌ๋ฌ ํ ์ด๋ธ ๋งคํ
@SecondaryTable
์ ์ฌ์ฉํ๋ฉด ํ ์ํฐํฐ์ ์ฌ๋ฌ ํ
์ด๋ธ์ ๋งคํํ ์ ์๋ค.
@Entity
@Table(name="BOARD")
@SecondaryTable(
name = "BOARD_DETAIL",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "BOARD_DETAIL_ID")
)
public class Board {
@Id @GeneratedValue
@Column(name = "BOARD_ID")
private Long id;
private String title;
@Column(name = "BOARD_DETAIL")
private String content;
}
@SecondaryTable.name
: ๋งคํํ ๋ค๋ฅธ ํ ์ด๋ธ ์ด๋ฆ@SecondaryTable.pkJoinColumns
: ๋งคํํ ๋ค๋ฅธ ํ ์ด๋ธ์ ๊ธฐ๋ณธ ํค ์ปฌ๋ผ ์์ฑ
// ์ฌ๋ฌ๊ฐ ๊ฐ๋ฅ
@SecondaryTables({
@SecondaryTable(name="BOARD_DETAIL"),
@SecondaryTable(name="BOARD_FILE")
})
@SecondaryTable
์ ์ฌ์ฉํ๋ ๊ฒ๋ณด๋จ ํ
์ด๋ธ๋น ์ํฐํฐ๋ฅผ ๊ฐ๊ฐ ๋ง๋ค์ด์ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํ๋ค.
8. ํ๋ก์์ ์ฐ๊ด๊ด๊ณ ๊ด๋ฆฌ
- ํ๋ก์์ ์ฆ์๋ก๋ฉ, ์ง์ฐ๋ก๋ฉ
- ์์์ฑ ์ ์ด์ ๊ณ ์ ๊ฐ์ฒด
8.1 ํ๋ก์
- ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ์ฐ๊ด๋ ์ํฐํฐ๋ค์ด ํญ์ ์ฌ์ฉ๋๋ ๊ฒ์ ์๋๋ค.
- ์ฌ์ฉํ์ง ์๋ ์ํฐํฐ๋ฅผ ํจ๊ป ์กฐํํด ๋๋ ๊ฒ์ ํจ์จ์ ์ด์ง ์๋ค.
- JPA๋ ์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๊ณ ์ง์ฐ ๋ก๋ฉ์ ์ ๊ณตํ๋ค.
- ์ง์ฐ ๋ก๋ฉ์ด๋ ์ค์ ์ฌ์ฉํ๋ ์์ ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์กฐํํ๋ ๊ฒ์ด๋ค.
- ์ง์ฐ ๋ก๋ฉ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ค๋ฉด ์ค์ ์ํฐํฐ ๊ฐ์ฒด ๋์ ์ ๊ฐ์ง ๊ฐ์ฒด๊ฐ ํ์ํ๋ฐ ์ด๊ฒ์ ํ๋ก์ ๊ฐ์ฒด๋ผ ํ๋ค.
8.1.1 ํ๋ก์ ๊ธฐ์ด
ํ๋ก์์ ํน์ง
- ํ๋ก์ ํด๋์ค๋ ์ค์ ํด๋์ค๋ฅผ ์์ ๋ฐ์์ ๋ง๋ค์ด์ง๋ค.
- ๋ฐ๋ผ์ ์ฌ์ฉํ๋ ์ ์ฅ์์๋ ์ด๊ฒ์ด ์ง์ง ๊ฐ์ฒด์ธ์ง ํ๋ก์ ๊ฐ์ฒด์ธ์ง ๊ตฌ๋ถํ์ง ์๊ณ ์ฌ์ฉํ๋ฉด ๋๋ค.
- ํ๋ก์ ๊ฐ์ฒด๋ ์ค์ ๊ฐ์ฒด์ ๋ํ ์ฐธ์กฐ๋ฅผ ๋ณด๊ดํ๋ค.
- ํ๋ก์ ๊ฐ์ฒด์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด, ํ๋ก์ ๊ฐ์ฒด๋ ์ค์ ๊ฐ์ฒด์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ค.
- ํ๋ก์ ๊ฐ์ฒด๋ ์ฒ์ ์ฌ์ฉํ ๋ ํ ๋ฒ๋ง ์ด๊ธฐํ ํ๋ค.
ํ๋ก์ ๊ฐ์ฒด์ ์ด๊ธฐํ
- ํ๋ก์ ๊ฐ์ฒด๋ ์ค์ ์ฌ์ฉ๋ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์กฐํํด์ ์ค์ ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
- ์ด๋ฅผ ํ๋ก์ ๊ฐ์ฒด์ ์ด๊ธฐํ๋ผ ํ๋ค.
์ค์์ ์ํ์ ์ด๊ธฐํ
// em.getReference()๋ฅผ ํธ์ถํ๋ฉด ์ํฐํฐ๋ฅผ ์ค์ ์ฌ์ฉํ๋ ์์ ๊น์ง ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ๋ฅผ ๋ฏธ๋ฃฌ๋ค.
// MemberProxy ๋ฐํ
Member member = em.getReference(Member.class, "id1");
tx.commit();
em.close(); // ์์์ฑ ์ปจํ
์คํธ ์ข
๋ฃ
member.getName(); // ์ค์์ ์ํ์์ ์ด๊ธฐํ ์๋
// org.hibernate.LazyInitializationException ๋ฐ์
8.1.2 ํ๋ก์์ ์๋ณ์
์ํฐํฐ๋ฅผ ํ๋ก์๋ก ์กฐํํ ๋ ์๋ณ์(PK) ๊ฐ์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํ๋๋ฐ ํ๋ก์ ๊ฐ์ฒด๋ ์ด ์๋ณ์ ๊ฐ์ ๋ณด๊ดํ๋ค.
Team team = em.getReference(Team.class, "team1"); // ์๋ณ์ ๋ณด๊ด
team.getId(); // ์ด๊ธฐํ๋์ง ์์
๋จ @Access(AccessType.PROPERTY)
์ผ ๋๋ง ์์ฒ๋ผ ๋์ํ๊ณ , @Access(AccessType.FIELD)
๋ก ์ค์ ํ๋ฉด ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ด๊ธฐํ ํ๋ค.
8.1.3 ํ๋ก์ ํ์ธ
JPA๊ฐ ์ ๊ณตํ๋ PersistenceUnitUtil.isLoaded(Object entity)
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ํ๋ก์ ์ธ์คํด์ค์ ์ด๊ธฐํ ์ฌ๋ถ๋ฅผ ํ์ธํ ์ ์๋ค.
boolean isLoad = em.getEntityManagerFactory()
.getPersistenceUnitUtil().isLoaded(entity);
8.2 ์ฆ์ ๋ก๋ฉ๊ณผ ์ง์ฐ ๋ก๋ฉ
JPA๋ ๊ฐ๋ฐ์๊ฐ ์ฐ๊ด๋ ์ํฐํฐ์ ์กฐํ ์์ ์ ์ ํํ ์ ์๋๋ก ๋ค์ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค.
- ์ฆ์ ๋ก๋ฉ: ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ์ฐ๊ด๋ ์ํฐํฐ๋ ํจ๊ป ์กฐํํ๋ค.
- ์ค์ ๋ฐฉ๋ฒ:
@ManyToOne(fetch = FetchType.EAGER)
- ์ค์ ๋ฐฉ๋ฒ:
- ์ง์ฐ ๋ก๋ฉ: ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์ค์ ์ฌ์ฉํ ๋ ์กฐํํ๋ค.
- ์ค์ ๋ฐฉ๋ฒ:
@ManyToOne(fetch = FetchType.LAZY)
- ์ค์ ๋ฐฉ๋ฒ:
8.2.1 ์ฆ์ ๋ก๋ฉ
์ฆ์ ๋ก๋ฉ์ ์ฌ์ฉํ๋ ค๋ฉด @ManyToOne(fetch = FetchType.EAGER)
๋ก ์ง์ ํ๋ค.
@Entity
public class Member {
...์๋ต...
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
...์๋ต...
}
8.2.2 ์ง์ฐ ๋ก๋ฉ
์ง์ฐ ๋ก๋ฉ์ ์ฌ์ฉํ๋ ค๋ฉด @ManyToOne(fetch = FetchType.LAZY)
๋ก ์ง์ ํ๋ค. ์ง์ฐ ๋ก๋ฉ์ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ด์ฉํ๋ค.
@Entity
public class Member {
...์๋ต...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
...์๋ต...
}
์ฐธ๊ณ
์กฐํ ๋์์ด ์์์ฑ ์ปจํ
์คํธ์ ์ด๋ฏธ ์์ผ๋ฉด ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ ์ด์ ๊ฐ ์๋ค. ๋ฐ๋ผ์ ์ค์ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ค.
8.2.3 ์ฆ์ ๋ก๋ฉ, ์ง์ฐ ๋ก๋ฉ ์ ๋ฆฌ
- ์ง์ฐ ๋ก๋ฉ: ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ํ๋ก์๋ก ์กฐํํ๋ค. ํ๋ก์๋ฅผ ์ค์ ์ฌ์ฉํ ๋ ์ด๊ธฐํํ๋ฉด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์กฐํํ๋ค.
- ์ฆ์ ๋ก๋ฉ: ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์ฆ์ ์กฐํํ๋ค. ํ์ด๋ฒ๋ค์ดํธ๋ ๊ฐ๋ฅํ๋ฉด SQL ์กฐ์ธ์ ์ฌ์ฉํด์ ํ ๋ฒ์ ์กฐํํ๋ค.
8.3 ์ง์ฐ ๋ก๋ฉ ํ์ฉ
์ฐ๊ด๋ ์ํฐํฐ๊ฐ ์์ฃผ ํจ๊ป ์ฌ์ฉ๋๋์ง ์๋์ง์ ๋ฐ๋ผ ์ฆ์ ๋ก๋ฉ, ์ง์ฐ ๋ก๋ฉ์ ๊ฒฐ์ ํ๋ค.
8.3.1 ํ๋ก์์ ์ปฌ๋ ์ ๋ํผ
์ปฌ๋ ์ ์ ์ง์ฐ ๋ก๋ฉ
Member member = em.find(Member.class, "member1");
List<Order> = member.getOrders();
System.out.println(orders.getClass().getName());
// ์ถ๋ ฅ
// org.hibernate.collection.internal.PersistenBag
8.3.2 JPA ๊ธฐ๋ณธ ํ์น ์ ๋ต
@ManyToOne
,@OneToOne
: ์ฆ์ ๋ก๋ฉ(FetchType.EAGER)@OneToMany
,@ManyToMany
: ์ง์ฐ ๋ก๋ฉ(FetchType.LAZY)
์ถ์ฒํ๋ ๋ฐฉ๋ฒ์ ๋ชจ๋ ์ฐ๊ด๊ด๊ณ์ ์ง์ฐ ๋ก๋ฉ์ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค. ๊ทธ๋ฆฌ๊ณ ๊ผญ ํ์ํ ๊ณณ์๋ง ์ฆ์ ๋ก๋ฉ์ ์ฌ์ฉํ๋๋ก ํ๋ค.
8.3.3 ์ปฌ๋ ์ ์ FetchType.EAGER ์ฌ์ฉ ์ ์ฃผ์์
์ปฌ๋ ์
์ FetchType.EAGER
๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ ์ฃผ์ํ ์ ์ ๋ค์๊ณผ ๊ฐ๋ค.
- ์ปฌ๋ ์
์ ์ฆ์ ๋ก๋ฉํ๋ ๊ฒ์ ๊ถ์ฅํ์ง ์๋๋ค.
- ๋๋ฌด ๋ง์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ ์ ์๊ณ , ๊ฒฐ๊ณผ์ ์ผ๋ก ์ฑ๋ฅ์ด ์ ํ๋ ์ ์๋ค.
- ์ปฌ๋ ์
์ฆ์ ๋ก๋ฉ์ ํญ์
OUTER JOIN
์ ์ฌ์ฉํ๋ค. @ManyToOne
,@OneToOne
- (optional = false): ๋ด๋ถ ์กฐ์ธ
- (optional = true): ์ธ๋ถ ์กฐ์ธ
@OneToMany
,@ManyToMany
- (optional = false): ์ธ๋ถ ์กฐ์ธ
- (optional = true): ๋ด๋ถ ์กฐ์ธ
8.4 ์์์ฑ ์ ์ด: CASCADE
ํน์ ์ํฐํฐ๋ฅผ ์ฐ์ ์ํ๋ก ๋ง๋ค ๋ ์ฐ๊ด๋ ์ํฐํฐ๋ ํจ๊ป ์์ ์ํ๋ก ๋ง๋ค๊ณ ์ถ์ผ๋ฉด ์์์ฑ ์ ์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ค. JPA์์ ์ํฐํฐ๋ฅผ ์ ์ฅํ ๋ ์ฐ๊ด๋ ๋ชจ๋ ์ํฐํฐ๋ ์์ ์ํ์ฌ์ผ ํ๋ค.
8.4.1 ์์์ฑ ์ ์ด: ์ ์ฅ
@Entity
public class Parent {
...์๋ต...
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<Child>();
...์๋ต...
}
Parent parent = new Parent();
Child child = new Child();
child.setParent(parent);
parent.getChildren().add(child);
// Parent๋ง ์ ์ฅํ์ง๋ง, ์์์ฑ ์ ์ด์ ์ํด Child๋ ์ ์ฅ๋จ
em.persist(parent);
์์์ฑ ์ ์ด๋ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งคํํ๋ ๊ฒ๊ณผ๋ ์๋ฌด ๊ด๋ จ์ด ์๋ค. ๋จ์ง ์ํฐํฐ๋ฅผ ์์ํํ ๋ ์ฐ๊ด๋ ์ํฐํฐ๋ ๊ฐ์ด ์์ํํ๋ ํธ๋ฆฌํจ์ ์ ๊ณตํ ๋ฟ์ด๋ค.
8.4.2 ์์์ฑ ์ ์ด: ์ญ์
@Entity
public class Parent {
...์๋ต...
@OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
private List<Child> children = new ArrayList<Child>();
...์๋ต...
}
Parent parent = em.find(Parent.class, 1L);
// Parent๋ง ์ญ์ ํ์ง๋ง, ์์์ฑ ์ ์ด์ ์ํด Child๋ ์ญ์ ๋จ
em.remove(parent);
8.4.3 CASCADE์ ์ข ๋ฅ
public enum CascadeType {
ALL, // ๋ชจ๋ ์ ์ฉ
PERSIST, // ์์
MERGE, // ๋ณํฉ
REMOVE, // ์ญ์
REFRESH,
DETACH
}
8.5 ๊ณ ์ ๊ฐ์ฒด
JPA๋ ๋ถ๋ชจ ์ํฐํฐ์ ์ฐ๊ด๊ด๊ณ๊ฐ ๋์ด์ง ์์ ์ํฐํฐ๋ฅผ ์๋์ผ๋ก ์ญ์ ํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋๋ฐ ์ด๊ฒ์ ๊ณ ์ ๊ฐ์ฒด(ORPHAN
) ์ ๊ฑฐ๋ผ ํ๋ค. ๋ถ๋ชจ ์ํฐํฐ์ ์ปฌ๋ ์
์์ ์์ ์ํฐํฐ์ ์ฐธ์กฐ๋ง ์ ๊ฑฐํ๋ฉด ์์ ์ํฐํฐ๊ฐ ์๋์ผ๋ก ์ญ์ ๋๋ค.
@Entity
public class Parent {
...์๋ต...
@OneToMany(mapeedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<Child>();
...์๋ต...
}
Parent parent = em.find(Parent.class, id);
parent.getChildren().remove(0); // ์์ ์ํฐํฐ๋ฅผ ์ปฌ๋ ์
์์ ์ญ์
// SQL
DELETE FROM CHILD WHERE ID=?
๊ณ ์ ๊ฐ์ฒด ์ ๊ฑฐ๋ ์ฐธ์กฐ๊ฐ ์ ๊ฑฐ๋ ์ํฐํฐ๋ ๋ค๋ฅธ ๊ณณ์์ ์ฐธ์กฐํ์ง ์๋ ๊ณ ์ ๊ฐ์ฒด๋ก ๋ณด๊ณ ์ญ์ ํ๋ ๊ธฐ๋ฅ์ด๋ค. ๋, ๋ถ๋ชจ๋ฅผ ์ ๊ฑฐํ๋ฉด ์์๋ ๊ฐ์ด ์ ๊ฑฐ๋๋ค.
8.6 ์์์ฑ ์ ์ด + ๊ณ ์ ๊ฐ์ฒด, ์๋ช ์ฃผ๊ธฐ
CascadeType.All + orphanRemoval = true
๋ฅผ ๋์์ ์ฌ์ฉํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น? ๋ ์ต์
์ ๋ชจ๋ ํ์ฑํ ํ๋ฉด ๋ถ๋ชจ ์ํฐํฐ๋ฅผ ํตํด์ ์์์ ์๋ช
์ฃผ๊ธฐ๋ฅผ ๊ด๋ฆฌ ํ ์ ์๋ค.
- ์์์ ์ ์ฅํ๋ ค๋ฉด ๋ถ๋ชจ์ ๋ฑ๋กํ๋ฉด ๋๋ค (CASCADE)
- ์์์ ์ญ์ ํ๋ ค๋ฉด ๋ถ๋ชจ์์ ์ ๊ฑฐํ๋ฉด ๋๋ค (orphanRemoval)
9. ๊ฐ ํ์
JPA์ ๋ฐ์ดํฐ ํ์
- ์ํฐํฐ ํ์
@Entity
- ๊ฐ ํ์
- ๊ธฐ๋ณธ๊ฐ ํ์
- ์๋ฐ ๊ธฐ๋ณธ ํ์ (์: int, double)
- ๋ํผ ํด๋์ค(์: Integer)
- String
- ์๋ฒ ๋๋ ํ์
- embedded type
- ์ปฌ๋ ์
๊ฐ ํ์
- collection value type
- ๊ธฐ๋ณธ๊ฐ ํ์
9.1 ๊ธฐ๋ณธ๊ฐ ํ์
private String name;
private int age;
...
9.2 ์๋ฒ ๋๋ ํ์ (๋ณตํฉ ๊ฐ ํ์ )
@Entity
public class Member {
@Embedded Address homeAddress;
}
@Embeddable
public class Address {
@Column(name="city")
private String city;
private String street;
private String zipcode;
...
}
@Embeddable
: ๊ฐ ํ์ ์ ์ ์ํ๋ ๊ณณ์ ํ์@Embedded
: ๊ฐ ํ์ ์ ์ฌ์ฉํ๋ ๊ณณ์ ํ์
9.2.1 ์๋ฒ ๋๋ ํ์ ๊ณผ ํ ์ด๋ธ ๋งคํ
UML์์ ์๋ฒ ๋๋ ๊ฐ ํ์
์ ๋จ์ํ๊ฒ ํํํ๋ ๊ฒ์ด ํธ๋ฆฌํ๋ค.
9.2.2 ์๋ฒ ๋๋ ํ์ ๊ณผ ์ฐ๊ด๊ด๊ณ
@Entity
public class Member {
@Embedded Address address;
@Embedded PhoneNumber phoneNumber;
}
@Embeddable
public class Address {
String city;
String street;
String state;
@Embedded Zipcode zipcode;
}
@Embeddable
public class Zipcode {
String zip;
String plusFour;
}
@Embeddable
public class PhoneNumber {
String areaCode;
String localNumber;
@ManyToOne PhoneServiceProvider provider;
}
@Entity
public class PhoneServiceProvider {
@Id String name;
}
9.2.3 @AttributeOverride: ์์ฑ ์ฌ์ ์
์๋ฒ ๋๋ ํ์
์ ์ ์ํ ๋งคํ์ ๋ณด๋ฅผ ์ฌ์ ์ ํ๋ ค๋ฉด ์ํฐํฐ์ @AttributeOverride
๋ฅผ ์ฌ์ฉํ๋ค.
@Entity
public class Member {
@Embedded
private Address address;
@Embedded
@AttributeOverrides({
@AttributeOverride(ame="city", column=@Column(name="COMPANY_CITY")),
@AttributeOverride(name="street", column=@Column(name="COMPANY_STREET")),
@AttributeOverride(name="zipcode", column=@Column(name="COMPANY_ZIPCODE"))
})
private Address companyAddress;
}
9.2.4 ์๋ฒ ๋๋ ํ์ ๊ณผ null
์๋ฒ ๋๋ ํ์ ์ด null์ด๋ฉด ๋งคํํ ์ปฌ๋ผ ๊ฐ์ ๋ชจ๋ null์ด ๋๋ค.
member.setAddress(null);
em.persist(member);
// ํ์ ํ
์ด๋ธ์ ์ฃผ์์ ๊ด๋ จ๋ CITY,STREET,ZIPCODE ์ปฌ๋ผ ๊ฐ์ ๋ชจ๋ null์ด ๋๋ค.
9.3 ๊ฐ ํ์ ๊ณผ ๋ถ๋ณ ๊ฐ์ฒด
๊ฐ ํ์ ์ ๋ณต์กํ ๊ฐ์ฒด๋ฅผ ์กฐ๊ธ์ด๋ผ๋ ๋จ์ํํ๋ ค๊ณ ๋ง๋ ๊ฐ๋ ์ด๋ค. ๋ฐ๋ผ์ ๊ฐ ํ์ ์ ๋จ์ํ๊ณ ์์ ํ๊ฒ ๋ค๋ฃฐ ์ ์์ด์ผ ํ๋ค.
9.3.1 ๊ฐ ํ์ ๊ณต์ ์ฐธ์กฐ
์๋ฒ ๋๋ ํ์ ๊ฐ์ ๊ฐ ํ์ ์ ์ฌ๋ฌ ์ํฐํฐ์์ ๊ณต์ ํ๋ฉด ์ํจํ๋ค.
member1.setAddress(new Address("OldCity"));
Address address = member1.getAddress();
address.setCity("NewCity");
member2.setAddress(address);
// ํ์2์ ์ฃผ์๋ง NewCity๋ก ๋ณ๊ฒฝ๋๊ธฐ๋ฅผ ๊ธฐ๋ํ์ง๋ง ํ์1์ ์ฃผ์๋ NewCity๋ก ๋ณ๊ฒฝ๋์ด ๋ฒ๋ฆฐ๋ค.
9.3.2 ๊ฐ ํ์ ๋ณต์ฌ
๊ฐ ํ์ ์ ์ค์ ์ธ์คํด์ค์ธ ๊ฐ์ ๊ณต์ ํ๋ ๊ฒ์ ์ํํ๋ค. ๋์ ์ ๊ฐ(์ธ์คํด์ค)์ ๋ณต์ฌํด์ ์ฌ์ฉํด์ผ ํ๋ค.
member1.setAddress(new Address("OldCity"));
Address address = member1.getAddress();
// ๋ณต์ฌ
Address newAddress = address.clone();
newAddress.setCity("NewCity");
member2.setAddress(newAddress);
9.3.3 ๋ถ๋ณ ๊ฐ์ฒด
๊ฐ ํ์ ์ ๋ถ์์ฉ ๊ฑฑ์ ์์ด ์ฌ์ฉํ ์ ์์ด์ผ ํ๋ค. ๊ฐ์ฒด๋ฅผ ๋ถ๋ณํ๊ฒ ๋ง๋ค๋ฉด ๊ฐ์ ์์ ํ ์ ์์ผ๋ฏ๋ก ๋ถ์์ฉ์ ์์ฒ ์ฐจ๋จํ ์ ์๋ค.
// ์ฃผ์ ๋ถ๋ณ ๊ฐ์ฒด
@Embeddable
public class Address {
private String city;
protected Address () {}
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
// Setter๋ ๋ง๋ค์ง ์๋๋ค.
}
9.4 ๊ฐ ํ์ ์ ๋น๊ต
- ๋์ผ์ฑ ๋น๊ต: ์ธ์คํด์ค์ ์ฐธ์กฐ ๊ฐ์ ๋น๊ต,
==
์ฌ์ฉ - ๋๋ฑ์ฑ ๋น๊ต: ์ธ์คํด์ค์ ๊ฐ์ ๋น๊ต,
equals()
์ฌ์ฉ - ๊ฐ ํ์
์ ๋น๊ตํ ๋๋
equals()
๋ฅผ ์ฌ์ฉํด์ ๋๋ฑ์ฑ ๋น๊ต๋ฅผ ํด์ผ ํ๋ค. - ๊ฐ ํ์
์
equals()
,hashCode()
๋ฉ์๋๋ฅผ ์ฌ์ ์ํด์ผ ํ๋ค.
9.5 ๊ฐ ํ์ ์ปฌ๋ ์
๊ฐ ํ์
์ ํ๋ ์ด์ ์ปฌ๋ ์
์ ๋ณด๊ดํ๊ณ @ElementCollection
, @CollectionTable
์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ๋ฉด ๋๋ค.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ElementCollection
@CollectionTable(
name = "FAVORITE_FOODS",
joinColumns = @JoinColumn(name = "MEMBER_ID")
)
@Column(name = "FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<String>();
@ElementCollection
@CollectionTable(
name = "ADDRESS",
joinColumns = @JoinColumn(name = "MEMBER_ID")
)
private List<Address> addressHistory = new ArrayList<Address>();
}
@Embeddable
public class Address {
@Column
private String city;
private String street;
private String zipcode;
}
9.5.1 ๊ฐ ํ์ ์ปฌ๋ ์ ์ฌ์ฉ
๊ฐ ํ์ ์ปฌ๋ ์ ์ ์์์ฑ ์ ์ด(Cascade) + ๊ณ ์ ๊ฐ์ฒด ์ ๊ฑฐ(Orphan remove) ๊ธฐ๋ฅ์ด ํฌํจ๋์ด ์๋ค.
// ๊ฐ ํ์
์ปฌ๋ ์
๋ ์กฐํํ ๋ ํ์น ์ ๋ต์ ์ ํํ ์ ์๋ค.
@ElementCollection(fetch = FetchType.LAZY) // ๊ธฐ๋ณธ๊ฐ LAZY
9.5.2 ๊ฐ ํ์ ์ปฌ๋ ์ ์ ์ ์ฝ์ฌํญ
์ ์ฝ์ฌํญ
- ๊ฐ ํ์ ์ปฌ๋ ์ ์ ๋ณด๊ด๋ ๊ฐ ํ์ ๋ค์ ๋ณ๋์ ํ ์ด๋ธ์ ๋ณด๊ดํ๋ค.
- ๋ฐ๋ผ์ ์ฌ๊ธฐ์ ๋ณด๊ด๋ ๊ฐ ํ์ ์ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ฐ์ดํฐ๋ฅผ ์ฐพ๊ธฐ ์ด๋ ต๋ค.
- ์ด๋ฐ ๋ฌธ์ ๋ก JPA ๊ตฌํ์ฒด๋ค์ ๊ฐ ํ์ ๋ณ๊ฒฝ ์ฌํญ์ด ๋ฐ์ํ๋ฉด ๊ฐ ํ์ ์ปฌ๋ ์ ์ด ๋งคํ๋ ํ ์ด๋ธ์ ์ฐ๊ด๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ญ์ , ํ์ฌ ๊ฐ์ผ๋ก ์ ์ฅํ๋ค.
- ์ฑ๋ฅ ๋ฌธ์ ๋ฐ์
- ๊ฐ ํ์ ์ปฌ๋ ์ ์ ๋งคํํ๋ ํ ์ด๋ธ์ ๋ชจ๋ ์ปฌ๋ผ์ ๋ฌถ์ด์ ๊ธฐ๋ณธ ํค๋ฅผ ๊ตฌ์ฑ ํด์ผํ๋ค.
- ๋ฐ๋ผ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ธฐ๋ณธ ํค ์ ์ฝ ์กฐ๊ฑด์ผ๋ก ์ธํด ์ปฌ๋ผ์ null์ ์ ๋ ฅํ ์ ์๊ณ , ๊ฐ์ ๊ฐ์ ์ค๋ณตํด์ ์ ์ฅํ ์ ์๋ ์ ์ฝ๋ ์๋ค.
ํด๊ฒฐ๋ฐฉ๋ฒ
- ๋ฐ๋ผ์ ์ค๋ฌด์์๋ ๊ฐ ํ์ ์ปฌ๋ ์ ์ด ๋งคํ๋ ํ ์ด๋ธ์ ๋ฐ์ดํฐ๊ฐ ๋ง๋ค๋ฉด ๊ฐ ํ์ ์ปฌ๋ ์ ๋์ ์ ์ผ๋๋ค ๊ด๊ณ๋ฅผ ๊ณ ๋ คํด์ผ ํ๋ค.
- ๊ทธ๋ฆฌ๊ณ ์์์ฑ ์ ์ด + ๊ณ ์ ๊ฐ์ฒด ์ ๊ฑฐ ๊ธฐ๋ฅ์ ์ ์ฉํ๋ฉด ๊ฐ ํ์ ์ปฌ๋ ์ ์ฒ๋ผ ์ฌ์ฉํ ์ ์๋ค.
// ๊ฐ ํ์
์ปฌ๋ ์
๋์ ์ ์ผ๋๋ค ๊ด๊ณ ์ฌ์ฉ
@Entity
public class AddressEntity {
@Id @GeneratedValue
private Long id;
@Embedded
private Address address;
}
@Embeddable
public class Address {
@Column
private String city;
private String street;
private String zipcode;
}
// ์ค์ ์ฝ๋
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<AddressEntity>();
9.6 ์ ๋ฆฌ
์ํฐํฐ ํ์ ์ ํน์ง
- ์๋ณ์(
@Id
)๊ฐ ์๋ค. - ์๋ช
์ฃผ๊ธฐ๊ฐ ์๋ค.
- ์์ฑ, ์์, ์๋ฉธ
- ๊ณต์ ํ ์ ์๋ค.
- ์ฐธ์กฐ ๊ฐ์ ๊ณต์ ํ ์ ์๋ค.
- ์๋ฅผ ๋ค์ด ํ์ ์ํฐํฐ๊ฐ ์๋ค๋ฉด ๋ค๋ฅธ ์ํฐํฐ์์ ํ์ ์ํฐํฐ๋ฅผ ์ฐธ์กฐํ ์ ์๋ค.
๊ฐ ํ์ ์ ํน์ง
- ์๋ณ์๊ฐ ์๋ค
- ์๋ช
์ฃผ๊ธฐ๋ฅผ ์ํฐํฐ์ ์์กดํ๋ค.
- ์์กดํ๋ ์ํฐํฐ๋ฅผ ์ ๊ฑฐํ๋ฉด ๊ฐ์ด ์ ๊ฑฐ๋๋ค.
- ๊ณต์ ํ์ง ์๋ ๊ฒ์ด ์์ ํ๋ค.
- ๋์ ์ ๊ฐ์ ๋ณต์ฌํด์ ์ฌ์ฉํ๋ค.
- ์ค์ง ํ๋์ ์ฃผ์ธ๋ง์ด ๊ด๋ฆฌํด์ผ ํ๋ค.
- ๋ถ๋ณ ๊ฐ์ฒด๋ก ๋ง๋๋ ๊ฒ์ด ์์ ํ๋ค.
10. ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ ์ธ์ด
10.1 ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ ์๊ฐ
JPQL ์ด๋?
- ํ ์ด๋ธ์ด ์๋ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ๊ฒ์ํ๋ ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ๋ค
- SQL์ ์ถ์ํํด์ ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค SQL์ ์์กดํ์ง ์๋๋ค.
- SQL์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ
์ด๋ธ์ ๋์์ผ๋ก ํ๋ ๋ฐ์ดํฐ ์ค์ฌ์ ์ฟผ๋ฆฌ๋ผ๋ฉด JPQL์ ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ํ๋ ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ๋ค.
JPA๋ JPQL๋ฟ๋ง ์๋๋ผ ๋ค์ํ ๊ฒ์ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค.
๊ณต์ ์ง์ ๊ธฐ๋ฅ
- JPQL(Java Persistence Query Language)
- Criteria ์ฟผ๋ฆฌ: JPA์ ํธํ๊ฒ ์์ฑํ๋๋ก ๋์์ฃผ๋ API, ๋น๋ ํด๋์ค ๋ชจ์
- ๋ค์ดํฐ๋ธ SQL: JPA์์ JPQL ๋์ ์ง์ SQL์ ์ฌ์ฉํ ์ ์๋ค.
๋น ๊ณต์ ๊ธฐ๋ฅ
- QueryDSL: Criteria ์ฟผ๋ฆฌ์ฒ๋ผ JPQL์ ํธํ๊ฒ ์์ฑํ๋๋ก ๋์์ฃผ๋ ๋น๋ ํด๋์ค ๋ชจ์, ๋นํ์ค ์คํ์์ค ํ๋ ์์ํฌ๋ค.
- JDBC ์ง์ ์ฌ์ฉ
- MyBatis ๊ฐ์ SQL ๋งคํผ ํ๋ ์์ํฌ ์ฌ์ฉ
10.1.1 JPQL ์๊ฐ
- JPQL์ ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ์กฐํํ๋ ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ๋ค.
- JPQL์ SQL์ ์ถ์ํํด์ ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์กดํ์ง ์๋๋ค.
- ๋ฐฉ์ธ๋ง ๋ณ๊ฒฝํ๋ฉด ๋๋ค.
- JPQL์ SQL๋ณด๋ค ๊ฐ๊ฒฐํ๋ค.
- ์ํฐํฐ ์ง์ ์กฐํ
- ๋ฌต์์ ์กฐ์ธ
- ๋คํ์ฑ ์ง์
@Entity(name="Member")
public class Member {
...์๋ต...
@Column(name = "name")
private String name;
...์๋ต...
}
// JPQL ์ฌ์ฉ
String jpql = "select m from Member as m where m.username = 'kim'";
List<Member> resultList = em.createQuery(jpql, Member.class).getResultList();
-- ์ค์ ์คํ๋ SQL, ๋ณ์นญ์ ๋๋ฌด ๋ณต์กํด ์์ ์์
select
member.id as id,
member.age as age,
member.team_id as team,
member.name as name
from
Member member
where
member.name='kim'
10.1.2 Criteria ์๊ฐ
Criteria๋ JPQL์ ์์ฑํ๋ ๋น๋ ํด๋์ค๋ค. Criteria์ ์ฅ์ ์ ๋ฌธ์๊ฐ ์๋ query.select(m).where(...)
์ฒ๋ผ ํ๋ก๊ทธ๋๋ฐ ์ฝ๋๋ก JPQL์ ์์ฑํ ์ ์๋ค๋ ์ ์ด๋ค.
์ฅ์
- ์ปดํ์ผ ์์ ์ ์ค๋ฅ๋ฅผ ๋ฐ๊ฒฌํ ์ ์๋ค.
- IDE๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๋ ์๋์์ฑ์ ์ง์ํ๋ค.
- ๋์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๊ธฐ ํธํ๋ค.
// Criteria ์ฌ์ฉ ์ค๋น
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
// ๋ฃจํธ ํด๋์ค(์กฐํ๋ฅผ ์์ํ ํด๋์ค)
Root<Member> m = query.from(Member.class);
// ์ฟผ๋ฆฌ ์์ฑ
CriteriaQuery<Member> cq = query.select(m).where(cb.equal(m.get("username"), "kim"));
List<Member> resultList = em.createQuery(cq).getResultList();
Criteria๊ฐ ๊ฐ์ง ์ฅ์ ์ด ๋ง์ง๋ง ๋ชจ๋ ์ฅ์ ์ ์์ํ ์ ๋๋ก ๋ณต์กํ๊ณ ์ฅํฉํ๋ค.
10.1.3 QueryDSL ์๊ฐ
- QueryDSL๋ Criteria์ฒ๋ผ JPQL ๋น๋ ์ญํ ์ ํ๋ค.
- ์ฝ๋ ๊ธฐ๋ฐ์ด๋ค
- ๋จ์ํ๊ณ ์ฌ์ฉํ๊ธฐ ์ฝ๋ค
- ์์ฑํ ์ฝ๋๋ JPQL๊ณผ ๋น์ทํด์ ํ๋์ ๋ค์ด์จ๋ค
// ์ค๋น
JPAQuery query = new JPAQuery(em);
QMember member = QMember.member;
// ์ฟผ๋ฆฌ, ๊ฒฐ๊ณผ์กฐํ
List<Member> members = query.from(member)
.where(member.username.eq("kim"))
.list(member);
10.1.4 ๋ค์ดํฐ๋ธ SQL ์๊ฐ
JPA๋ SQL์ ์ง์ ์ฌ์ฉํ ์ ์๋ ๊ธฐ๋ฅ์ ์ง์ํ๋๋ฐ ์ด๊ฒ์ ๋ค์ดํฐ๋ธ SQL์ด๋ผ ํ๋ค.
String sql = "SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = 'kim'";
List<Member> resultList = em.createNativeQuery(sql, Member.class).getResultList();
๋ค์ดํฐ๋ธ SQL์ ๋จ์ ์ ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์กดํ๋ SQL์ ์์ฑํด์ผ ํ๋ค๋ ๊ฒ์ด๋ค. ๋ฐ๋ผ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ณ๊ฒฝํ๋ฉด ๋ค์ดํฐ๋ธ SQL๋ ์์ ํด์ผ ํ๋ค.
10.1.5 JDBC ์ง์ ์ฌ์ฉ, ๋ง์ด๋ฐํฐ์ค ๊ฐ์ SQL ๋งคํผ ํ๋ ์์ํฌ ์ฌ์ฉ
๋ค์์ ํ์ด๋ฒ๋ค์ดํธ์์ ์ง์ JDBC Connection์ ํ๋ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
Session session = entityManager.unwrap(Session.class);
session.doWork(new Work() {
@Override
public void execute(Connection connection) throw SQLException {...}
});
- JDBC๋ ๋ง์ด๋ฐํฐ์ค๋ฅผ JPA์ ํจ๊ป ์ฌ์ฉํ๋ฉด ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ ์ ํ ์์ ์ ๊ฐ์ ๋ก ํ๋ฌ์ํด์ผ ํ๋ค.
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์์ฑ ์ปจํ
์คํธ๋ฅผ ๋๊ธฐํ ํ๊ธฐ ์ํจ์ด๋ค.
10.2 JPQL
JPQL ํน์ง
- JPQL์ ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ ์ธ์ด๋ค. ๋ฐ๋ผ์ ํ ์ด๋ธ์ด ์๋ ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ์ฟผ๋ฆฌํ๋ค.
- JPQL์ SQL์ ์ถ์ํํด์ ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค SQL์ ์์กดํ์ง ์๋๋ค.
- JPQL์ ๊ฒฐ๊ตญ SQL๋ก ๋ณํ๋๋ค.
10.2.1 ๊ธฐ๋ณธ ๋ฌธ๋ฒ๊ณผ ์ฟผ๋ฆฌ API
JPQL ๋ฌธ๋ฒ
select_๋ฌธ :: =
select_์
from_์
[where_์ ]
[groupby_์ ]
[having_์ ]
[orderby_์ ]
update_๋ฌธ :: update_์ [where_์ ]
delete_๋ฌธ :: delete_์ [where_์ ]
SELECT ๋ฌธ
SELECT m FROM Member AS m where m.username = 'Hello'
- ๋์๋ฌธ์ ๊ตฌ๋ถ
- ์ํฐํฐ์ ์์ฑ์ ๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ๋ค.
- JPQL ํค์๋(SELECT, FROM, AS..)๋ ๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ์ง ์๋๋ค.
- ์ํฐํฐ ์ด๋ฆ
- ํ ์ด๋ธ ๋ช ์ด ์๋๋ผ ์ํฐํฐ ๋ช ์ ์ฌ์ฉํ๋ค.
- @Entity(name=โxxxโ)๋ก ์ง์ ํ ์ ์๋ค.
- ์ํฐํฐ ๋ช ์ ์ง์ ํ์ง ์์ผ๋ฉด ํด๋์ค๋ช ์ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฌ์ฉํ๋ค.
- ๋ณ์นญ์ ํ์
- JPQL์ ๋ณ์นญ์ ํ์๋ก ์ฌ์ฉํด์ผ ํ๋ค. (JPA ๋ช ์ธ๋ก ์น๋ฉด ์ ํํ๋ ์๋ณ ๋ณ์)
- AS๋ ์๋ตํ ์ ์๋ค.
TypeQuery, Query
์์ฑํ JPQL์ ์คํํ๋ ค๋ฉด ์ฟผ๋ฆฌ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ผ ํ๋ค.
- TypeQuery ๊ฐ์ฒด: ๋ฐํ ํ์ ์ ๋ช ํํ๊ฒ ์ง์ ํ ์ ์์ผ๋ฉด ์ฌ์ฉ
- Query ๊ฐ์ฒด: ๋ฐํ ํ์ ์ ๋ช ํํ๊ฒ ์ง์ ํ ์ ์์ผ๋ฉด ์ฌ์
// TypeQuery ์ฌ์ฉ
TypeQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> resultList = query.getResultList();
for (Member member : resultList) {
...
}
// Query ์ฌ์ฉ
Query query = em.createQuery("SELECT m FROM Member m");
List resultList = query.getResultList();
for (Object o : resultList) {
Object[] result = (Object[]) o; // ๊ฒฐ๊ณผ๊ฐ ๋ ์ด์์ด๋ฉด Object[] ๋ฐํ
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
}
๊ฒฐ๊ณผ ์กฐํ
๋ค์ ๋ฉ์๋๋ค์ ํธ์ถํ๋ฉด ์ค์ ์ฟผ๋ฆฌ๋ฅผ ์คํํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์กฐํํ๋ค.
- query.getResultList(): ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ. ๋ง์ฝ ๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด ๋น ์ปฌ๋ ์ ๋ฐํ
- query.getSingleResult(): ๊ฒฐ๊ณผ๊ฐ ์ ํํ ํ๋์ผ ๋ ์ฌ์ฉํ๋ค.
- ๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด javax.persistence.NoResultException ๋ฐ์
- ๊ฒฐ๊ณผ๊ฐ 1๊ฐ๋ณด๋ค ๋ง์ผ๋ฉด javax.persistence.NonUniqueResultException ๋ฐ์
10.2.2 ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ
JDBC๋ ์์น ๊ธฐ์ค ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ๋ง ์ง์ํ์ง๋ง JPQL์ ์ด๋ฆ ๊ธฐ์ค ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ๋ ์ง์ํ๋ค.
์ด๋ฆ ๊ธฐ์ค ํ๋ผ๋ฏธํฐ
String usernameParam = "user1";
// ํ๋ผ๋ฏธํฐ ์์ :์ ๋ถ์ธ๋ค.
TypeQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class);
query.setParameter("username", usernameParam);
List<Member> resultList = query.getResultList();
์์น ๊ธฐ์ค ํ๋ผ๋ฏธํฐ
String usernameParam = "user1";
// ํ๋ผ๋ฏธํฐ๋ ? ๋ค์์ ์์น ๊ฐ์ ์ฃผ๋ฉด ๋๋ค.
List<Member> members = em.createQuery("SELECT m FROM Member m where m.username = ?1", Member.class);
query.setParameter(1, usernameParam);
List<Member> resultList = query.getResultList();
๊ฒฝ๊ณ
ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ ๋ฐฉ์์ ์ฌ์ฉํ์ง ์๊ณ ์ง์ JPQL์ ๋ง๋ค๋ฉด SQL ์ธ์ ์
๊ณต๊ฒฉ์ ๋นํ ์ ์๋ค.
"SELECT m FROM Member m where m.username = '" + usernameParam + "'"
10.2.3 ํ๋ก์ ์
SELECT ์ ์ ์กฐํํ ๋์์ ์ง์ ํ๋ ๊ฒ์ ํ๋ก์ ์ ์ด๋ผ๊ณ ํ๋ค. [SELECT {ํ๋ก์ ์ ๋์} FROM]์ผ๋ก ๋์์ ์ ํํ๋ค. ํ๋ก์ ์ ๋์์ ์ํฐํฐ, ์๋ฒ ๋๋ ํ์ , ์ค์นผ๋ผ ํ์ ์ด ์๋ค. ์ค์นผ๋ผ ํ์ ์ ์ซ์, ๋ฌธ์ ๋ฑ ๊ธฐ๋ณธ ๋ฐ์ดํฐ ํ์ ์ ๋ปํ๋ค.
์ํฐํฐ ํ๋ก์ ์
SELECT m FROM Member m // ํ์
SELECT m.team FROM Member m // ํ
์กฐํํ ์ํฐํฐ๋ ์์์ฑ ์ปจํ
์คํธ์์ ๊ด๋ฆฌํ๋ค.
์๋ฒ ๋๋ ํ์
ํ๋ก์ ์
์๋ฒ ๋๋ ํ์
์ ์กฐํ์ ์์์ ์ด ๋ ์ ์๋ค๋ ์ ์ฝ์ด ์๋ค. ์๋์ Address๋ ์๋ฒ ๋๋ ํ์
์ด๋ค.
// ์๋ชป๋ ์ฟผ๋ฆฌ
String query = "SELECT a From Address a";
// ์ฌ๋ฐ๋ฅธ ์ฟผ๋ฆฌ
String query = "SELECT o.address From Order o";
List<Address> addresses = em.createQuery(query, Address.class).getResultList();
์คํ๋ ์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ๋ค.
select
order.city,
order.street,
order.zipcode
from
Orders order
์๋ฒ ๋๋ ํ์
์ ์ํฐํฐ ํ์
์ด ์๋๋ค. ๋ฐ๋ผ์ ์ด๋ ๊ฒ ์ง์ ์กฐํํ ์๋ฒ ๋๋ ํ์
์ ์์์ฑ ์ปจํ
์คํธ์์ ๊ด๋ฆฌ๋์ง ์๋๋ค.
์ค์นผ๋ผ ํ์
ํ๋ก์ ์
์ซ์, ๋ฌธ์, ๋ ์ง์ ๊ฐ์ ๊ธฐ๋ณธ ๋ฐ์ดํฐ ํ์
๋ค์ ์ค์นผ๋ผ ํ์
์ด๋ผ ํ๋ค.
String query = "SELECT username FROM Member m";
List<String> usernames = em.createQuery(query, String.class).getResultList();
// ์ค๋ณต ๋ฐ์ดํฐ๋ฅผ ์ ๊ฑฐํ๋ ค๋ฉด DISTINCT๋ฅผ ์ฌ์ฉํ๋ค.
SELECT DISTINCT username From Member m
// ํต๊ณ
String query = "SELECT AVG(o.orderAmount) FROM Order o"
Double orderAmountAvg = em.createQuery(query, Double.class).getSingleResult();
์ฌ๋ฌ ๊ฐ ์กฐํ
๊ผญ ํ์ํ ๋ฐ์ดํฐ๋ค๋ง ์ ํํด์ ์กฐํํด์ผ ํ ๋๋ ์๋ค. ์ด ๋๋ TypeQuery๋ฅผ ์ฌ์ฉํ ์ ์๊ณ ๋์ ์ Query๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
Query query = em.createQuery("SELECT m.username, m.age FROM Member m");
List resultList = query.getResultList();
Iterator iterator = resultList.iterator();
while (iterator.hasNext()) {
Object[] row = (Object[]) iterator.next();
String username = (String) row[0];
Integer age = (Integer) row[1];
}
Query query = em.createQuery("SELECT m.username, m.age FROM Member m");
List<Object[]> resultList = query.getResultList();
for (Object[] row : resultList) {
String username = (String) row[0];
Integer age = (Integer) row[1];
}
์ค์นผ๋ผ ํ์ ๋ฟ๋ง ์๋๋ผ ์ํฐํฐ ํ์ ๋ ์ฌ๋ฌ ๊ฐ์ ํจ๊ป ์กฐํํ ์ ์๋ค.
Query query = em.createQuery("SELECT o.member, o.product, o.orderAmount FROM Order o");
List<Object[]> resultList = query.getResultList();
for (Object[] row : resultList) {
Member member = (Member) row[0]; // ์ํฐํฐ
Product product = (Product) row[1]; // ์ํฐํฐ
int orderAmount = (Integer) row[2]; // ์ค์นผ๋ผ
}
๋ฌผ๋ก ์ด๋๋ ์กฐํํ ์ํฐํฐ๋ ์์์ฑ ์ปจํ
์คํธ์์ ๊ด๋ฆฌ๋๋ค.
NEW ๋ช
๋ น์ด
ํ๋ก์ ์
์ UserDTO์ฒ๋ผ ์๋ฏธ ์๋ ๊ฐ์ฒด๋ก ๋ณํํด์ ์ฌ์ฉํ ์ ์๋ค.
public class UserDTO {
private String username;
private int age;
public UserDTO(String username, int age) {
this.username = username;
this.age = age;
}
...
}
TypeQuery<UserDTO> query = em.createQuery("SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m", UserDTO.class);
List<UserDTO> resultList = query.getResultList();
NEW ๋ช ๋ น์ด ์ฌ์ฉ์ ์ฃผ์ ์ฌํญ์ ๋ค์๊ณผ ๊ฐ๋ค.
- ํจํค์ง ๋ช ์ ํฌํจํ ์ ์ฒด ํด๋์ค ๋ช ์ ์ ๋ ฅํด์ผ ํ๋ค.
- ์์์ ํ์
์ด ์ผ์นํ๋ ์์ฑ์๊ฐ ํ์ํ๋ค.
10.2.4 ํ์ด์ง API
JPA ํ์ด์ง
TypeQuery<Member> query = em.createQuery("SELECT m FROM Member m ORDER BY m.username DESC", Member.class);
query.setFirstResult(10); // ์กฐํ ์์ ์์น(0๋ถํฐ ์์)
query.setMaxResult(20); // ์กฐํํ ๋ฐ์ดํฐ ์
query.getResultList();
HSQLDB ํ์ด์ง
SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM
MEMBER M
ORDER BY
M.NAME DESC OFFSET ? LIMIT ?
MySQL ํ์ด์ง
SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM
MEMBER M
ORDER BY
M.NAME DESC LIMIT ?, ?
PostgreSQL ํ์ด์ง
SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM
MEMBER M
ORDER BY
M.NAME DESC LIMIT ? OFFSET ?
์ค๋ผํด ํ์ด์ง
SELECT *
FROM
(
SELECT ROW_.*, ROWNUM ROWNUM_
FROM
(
SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM
MEMBER M
ORDER BY M.NAME
) ROW_
WHERE ROWNUM <= ?
)
WHERE ROWNUM_ > ?
SQLServer ํ์ด์ง
WITH query AS (
SELECT
inner_query.*,
ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as
__hibernate_row_nr__
FROM
(
select
TOP(?) m.id as id,
m.age as age,
m.team_id as team_id,
m.name as name
from Member m
order by m.name DESC
) inner_query
)
SELECT id, age, team_id, name
FROM query
WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?
10.2.5 ์งํฉ๊ณผ ์ ๋ ฌ
์งํฉ์ ์งํฉํจ์์ ํจ๊ป ํต๊ณ ์ ๋ณด๋ฅผ ๊ตฌํ ๋ ์ฌ์ฉํ๋ค.
์งํฉ ํจ์
ํจ์ | ์ค๋ช |
---|---|
COUNT | ๊ฒฐ๊ณผ ์๋ฅผ ๊ตฌํ๋ค. ๋ฐํ ํ์ : Long |
MAX, MIN | ์ต๋, ์ต์ ๊ฐ์ ๊ตฌํ๋ค. ๋ฌธ์, ์ซ์, ๋ ์ง ๋ฑ์ ์ฌ์ฉํ๋ค. |
AVG | ํ๊ท ๊ฐ์ ๊ตฌํ๋ค. ์ซ์ํ์ ๋ง ์ฌ์ฉํ ์ ์๋ค. ๋ฐํ ํ์ : Double |
SUM | ํฉ์ ๊ตฌํ๋ค. ์ซ์ํ์ ๋ง ์ฌ์ฉํ ์ ์๋ค. ๋ฐํ ํ์ : ์ ์ํฉ Long, ์์ํฉ: Double, BigIntegerํฉ: BigInteger, BigDecimalํฉ: BigDecimal |
์งํฉ ํจ์ ์ฌ์ฉ ์ ์ฐธ๊ณ ์ฌํญ
- NULL ๊ฐ์ ๋ฌด์ํ๋ฏ๋ก ํต๊ณ์ ์กํ์ง ์๋๋ค. (DISTINCT๊ฐ ์ ์๋์ด ์์ด๋ ๋ฌด์๋๋ค.)
- ๋ง์ฝ ๊ฐ์ด ์๋๋ฐ SUM, AVG, MAX, MIN ํฉ์๋ฅผ ์ฌ์ฉํ๋ฉด NULL ๊ฐ์ด ๋๋ค. ๋จ COUNT๋ 0์ด ๋๋ค.
- DISTINCT๋ฅผ ์งํฉ ํจ์ ์์ ์ฌ์ฉํด์ ์ค๋ณต๋ ๊ฐ์ ์ ๊ฑฐํ๊ณ ๋์ ์งํฉ์ ๊ตฌํ ์ ์๋ค. ์:
select COUNT( DISTINCT m.age ) from Member m
- DISTINCT๋ฅผ COUNT์์ ์ฌ์ฉํ ๋ ์๋ฒ ๋๋ ํ์ ์ ์ง์ํ์ง ์๋๋ค.
GROUP BY, HAVING
GROUP BY๋ ํต๊ณ ๋ฐ์ดํฐ๋ฅผ ๊ตฌํ ๋ ํน์ ๊ทธ๋ฃน๋ผ๋ฆฌ ๋ฌถ์ด์ค๋ค. HAVING์ GROUP BY์ ํจ๊ป ์ฌ์ฉ๋๋๋ฐ GROUP BY๋ก ๊ทธ๋ฃนํํ ํต๊ณ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ์ค์ผ๋ก ํํฐ๋ง ํ๋ค.
select
t.name,
COUNT(m.age),
SUM(m.age),
AVG(m.age),
MAX(m.age),
MIN(m.age)
from Member m LEFT JOIN m.team t
GROUP BY t.name
HAVING AVG(m.age) >= 10
๋ฌธ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค.
groupby_์ ::= GORUP BY {๋จ์ผ๊ฐ ๊ฒฝ๋ก | ๋ณ์นญ} +
having_์ ::= HAVING ์กฐ๊ฑด์
์ ๋ ฌ(ORDER BY)
๊ฒฐ๊ณผ๋ฅผ ์ ๋ ฌํ ๋ ์ฌ์ฉํ๋ค.
select m from Member m order by m.age DESC, m.username ASC
๋ฌธ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค.
orderby_์ ::= ORDER BY {์ํํ๋ ๊ฒฝ๋ก | ๊ฒฐ๊ณผ ๋ณ์ [ASC | DESC]}+
- ASC: ์ค๋ฆ์ฐจ์(๊ธฐ๋ณธ๊ฐ)
- DESC: ๋ด๋ฆผ์ฐจ์
10.2.6 JPQL ์กฐ์ธ
JPQL๋ ์กฐ์ธ์ ์ง์ํ๋๋ฐ SQL ์กฐ์ธ๊ณผ ๊ธฐ๋ฅ์ ๊ฐ๊ณ ๋ฌธ๋ฒ๋ง ์ฝ๊ฐ ๋ค๋ฅด๋ค.
๋ด๋ถ ์กฐ์ธ
๋ด๋ถ์กฐ์ธ์ INNER JOIN์ ์ฌ์ฉํ๋ค. ์ฐธ๊ณ ๋ก INNER๋ ์๋ตํ ์ ์๋ค.
String teamName = "ํA";
String query = "SELECT m FROM Member m INNER JOIN m.team t WHERE t.name = :teamName";
List<Member> members = em.createQuery(query, Member.class)
.setParameter("teamName", teamName)
.getResultList();
์ฃผ์์ฌํญ
JPQL ์กฐ์ธ์ ์ฐ๊ด ํ๋๋ฅผ ์ฌ์ฉํ๋ค๋ ๊ฒ์ด๋ค. SQL ์กฐ์ธ์ฒ๋ผ ์ฌ์ฉํ๋ฉด ๋ฌธ๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค. ๋ค์์ ์๋ชป๋ ์์ด๋ค.
-- ์๋ชป๋ JPQL ์กฐ์ธ
FROM Member m JOIN Team t
์ธ๋ถ ์กฐ์ธ
JPQL์ ์ธ๋ถ ์กฐ์ธ์ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ๋ค.
SELECT m
FROM Member m LEFT [OUTER] JOIN m.team t
์ปฌ๋ ์
์กฐ์ธ
์ผ๋๋ค ๊ด๊ณ๋ ๋ค๋๋ค ๊ด๊ณ์ฒ๋ผ ์ปฌ๋ ์
์ ์ฌ์ฉํ๋ ๊ณณ์์ ์กฐ์ธํ๋ ๊ฒ์ ์ปฌ๋ ์
์กฐ์ธ์ด๋ผ ํ๋ค.
- [ํ์ -> ํ]์ผ๋ก์ ์กฐ์ธ์ ๋ค๋์ผ ์กฐ์ธ, ๋จ์ผ ๊ฐ ์ฐ๊ดํ๋(m.team) ์ฌ์ฉ
- [ํ -> ํ์]์ผ๋ก์ ์กฐ์ธ์ ์ผ๋๋ค ์กฐ์ธ, ์ปฌ๋ ์ ๊ฐ ์ฐ๊ดํ๋(m.members) ์ฌ์ฉ
-- ํ๊ณผ ํ์๋ชฉ๋ก์ ์ปฌ๋ ์
๊ฐ ์ฐ๊ด ํ๋๋ก ์ธ๋ถ์กฐ์ธ ํ๋ค.
SELECT t, m FROM Team t LEFT JOIN t.members m
์ธํ ์กฐ์ธ
WHERE ์ ์ ์ฌ์ฉํด์ ์ธํ ์กฐ์ธ์ ํ ์ ์๋ค. ์ธํ ์กฐ์ธ์ ๋ด๋ถ ์กฐ์ธ๋ง ์ง์ํ๋ค. ์ธํ ์กฐ์ธ์ ์ฌ์ฉํ๋ฉด ์ ํ ๊ด๊ณ์๋ ์ํฐํฐ๋ ์กฐ์ธํ ์ ์๋ค.
-- JPQL
select count(m) from Member m, Team t
where m.username = t.team
-- sql
select count(m.id)
from
member m cross join team t
where
m.username = t.name
JOIN ON ์ (JPA 2.1)
JPA 2.1๋ถํฐ ์กฐ์ธํ ๋ ON ์ ์ ์ง์ํ๋ค. ์ฐธ๊ณ ๋ก ๋ด๋ถ ์กฐ์ธ์ด ON ์ ์ WHERE ์ ์ ์ฌ์ฉํ ๋์ ๊ฒฐ๊ณผ๊ฐ ๊ฐ์ผ๋ฏ๋ก ๋ณดํต ON ์ ์ ์ธ๋ถ ์กฐ์ธ์์๋ง ์ฌ์ฉํ๋ค.
-- JPQL
select m, t
from Member m
left join m.team t on t.name = 'A'
-- SQL
select m.*, t.*
from Member m
left join Team t on m.team_id = t.id
and t.name = 'A'
10.2.7 ํ์น ์กฐ์ธ
- ํ์น(fetch) ์กฐ์ธ์ JPQL์์ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํด ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ด๋ค.
- ์ฐ๊ด๋ ์ํฐํฐ๋ ์ปฌ๋ ์ ์ ํ ๋ฒ์ ๊ฐ์ด ์กฐํํ๋ ๊ธฐ๋ฅ์ด๋ค.
- join fetch ๋ช ๋ น์ด๋ก ์ฌ์ฉํ ์ ์๋ค.
๋ฌธ๋ฒ
ํ์น ์กฐ์ธ ::= [ LEFT [OUTER] | INNER ] JOIN FETCH ์กฐ์ธ๊ฒฝ๋ก
์ํฐํฐ ํ์น ์กฐ์ธ
๋ค์์ ํ์น ์กฐ์ธ์ ์ฌ์ฉํด์ ํ์ ์ํฐํฐ๋ฅผ ์กฐํํ๋ฉด์ ์ฐ๊ด๋ ํ ์ํฐํฐ๋ ํจ๊ป ์กฐํ ํ๋ JPQL
-- ํ์น ์กฐ์ธ JPQL
select m
from Member m join fetch m.team
-- ์คํ๋ SQL
SELECT
M.*, T.*
FROM MEMBER T
INNER JOIN TEAM T ON M.TEAM_ID = T.ID
JPQL์กฐ์ธ๊ณผ๋ ๋ค๋ฅด๊ฒ m.team ๋ค์์ ๋ณ์นญ์ด ์๋๋ฐ ํ์น ์กฐ์ธ์ ๋ณ์นญ์ ์ฌ์ฉํ ์ ์๋ค. (ํ์ง๋ง ํ์ด๋ฒ๋ค์ดํธ๋ ํ์น ์กฐ์ธ์๋ ๋ณ์นญ์ ํ์ฉํ๋ค.)
์ปฌ๋ ์
ํ์น ์กฐ์ธ
์ผ๋๋ค ๊ด๊ณ์ธ ์ปฌ๋ ์
์ ํ์น ์กฐ์ธ
-- ์ปฌ๋ ์
ํ์น ์กฐ์ธ 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'
๋ฌธ์ ๋ TEAM ํ ์ด๋ธ์์ ํA๋ ํ๋์ง๋ง MEMBERํ ์ด๋ธ๊ณผ ์กฐ์ธํ๋ฉด์ ๊ฒฐ๊ณผ๊ฐ ์ฆ๊ฐํ๋ค. ํ ์ด๋ธ์ ๋ฐ์ดํฐ๊ฐ ํA, ํ์1, ํ์2 ์๋ค๊ณ ํ๋ฉด ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ๋ค.
teamname = ํA, team = Team@0x100
username = ํ์1, member = Member@0x200
username = ํ์2, member = Member@0x300
teamname = ํA, team = Team@0x100
username = ํ์1, member = Member@0x200
username = ํ์2, member = Member@0x300
ํ์น ์กฐ์ธ๊ณผ DISTINCT
์์ ์ปฌ๋ ์
ํ์น ์กฐ์ธ์ ํA๊ฐ ์ค๋ณต์ผ๋ก ์กฐํ๋๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด DISTINCT๋ฅผ ์ฌ์ฉํ๋ค.
select distinct t
from Team t join fetch t.members
where t.name = 'ํA'
์ ์ํฉ์ ๋ฐ์ดํฐ๋ SQL์์ DISTINCT๋ฅผ ํด๋ ํจ๊ณผ๊ฐ ์๋ค.
๋ก์ฐ ๋ฒํธ | ํ | ํ์ |
---|---|---|
1 | ํA | ํ์1 |
2 | ํA | ํ์2 |
ํ์ง๋ง JPQL์ select distinct t์ ์๋ฏธ๋ ์ํฐํฐ์ ์ค๋ณต์ ์ ๊ฑฐํ๋ผ๋ ๊ฒ์ด๋ค. ๋ฐ๋ผ์ ํA๋ ํ๋๋ง ์กฐํ๋๋ค.
teamname = ํA, team = Team@0x100
username = ํ์1, member = Member@0x200
username = ํ์2, member = Member@0x300
ํ์น ์กฐ์ธ๊ณผ ์ผ๋ฐ ์กฐ์ธ์ ์ฐจ์ด
- ์ผ๋ฐ ์กฐ์ธ: JPQL์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ ๋ ์ฐ๊ด๊ด๊ณ๊น์ง ๊ณ ๋ คํ์ง ์๋๋ค. ๋จ์ง SELECT ์ ์ ์ง์ ํ ์ํฐํฐ๋ง ์กฐํํ ๋ฟ์ด๋ค.
- ํ์น ์กฐ์ธ: SELECT ์ ์ ์ง์ ํ ์ํฐํฐ๋ฟ๋ง ์๋๋ผ ์ง์ ํ์ง ์์์ด๋ ์ฐ๊ด๋ ์ํฐํฐ๋ ํจ๊ป ์กฐํํ๋ค.
ํ์น ์กฐ์ธ์ ํน์ง๊ณผ ํ๊ณ
- SQL ํ๋ฒ์ผ๋ก ์ฐ๊ด๋ ์ํฐํฐ๋ค์ ์กฐํํ์ฌ SQL ํธ์ถ ํ์๋ฅผ ์ค์ฌ ์ฑ๋ฅ์ ์ต์ ํํ ์ ์๋ค.
- JPQL์์ ํ์น ์กฐ์ธ์ ์ฌ์ฉํ๋ฉด ๊ธ๋ก๋ฒ ์ ๋ต๋ณด๋ค ์ฐ์ ์ ์ฉ๋๋ค.
- ๊ธ๋ก๋ฒ ์ ๋ต ์: @OneToMany(fetch = FetchType.LAZY)
- ํ์น ์กฐ์ธ์ ํ๋ฉด ์ฟผ๋ฆฌ ์์ ์ ์กฐํํ๋ฏ๋ก ์ค์์ ์ํ์์๋ ๊ฐ์ฒด ๊ทธ๋ํ๋ฅผ ํ์ํ ์ ์๋ค.
- ํ์น ์กฐ์ธ ๋์์๋ ๋ณ์นญ์ ์ค ์ ์๋ค.
- ๋ฐ๋ผ์ SELECT, WHERE, ์๋น ์ฟผ๋ฆฌ์ ํ์น ์กฐ์ธ ๋์์ ์ฌ์ฉํ ์ ์๋ค.
- ํ์ด๋ฒ๋ค์ดํธ์์๋ ํ์น ์กฐ์ธ์ ๋ณ์นญ์ ์ง์ํ๋ค. ํ์ง๋ง ์๋ชป ์ฌ์ฉํ๋ฉด ์ฐ๊ด๋ ๋ฐ์ดํฐ ์๊ฐ ๋ฌ๋ผ์ ธ ๋ฌด๊ฒฐ์ฑ์ด ๊นจ์ง ์ ์๋ค.
- ๋ ์ด์์ ์ปฌ๋ ์ ์ ํ์นํ ์ ์๋ค.
- ์ปฌ๋ ์
์ ํ์น ์กฐ์ธํ๋ฉด ํ์ด์ง API๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
- ์ปฌ๋ ์ (์ผ๋๋ค)์ด ์๋ ๋จ์ผ ๊ฐ ์ฐ๊ด ํ๋(์ผ๋์ผ, ๋ค๋์ผ)์ ํ์น ์กฐ์ธ ๊ฐ๋ฅ
- ํ์ด๋ฒ๋ค์ดํธ๋ ์ปฌ๋ ์ ์ ํ์น ์กฐ์ธํ๊ณ ํ์ด์ง API๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฒฝ๊ณ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ณ ๋ฉ๋ชจ๋ฆฌ์์ ํ์ด์ง ์ฒ๋ฆฌํ๋ค. ํ์ง๋ง ์ฑ๋ฅ ์ด์๊ฐ ๋ฐ์ํ ์ ์๋ค.
์ถ์ฒ์ ๊ธ๋ก๋ฒ ์ ๋ต์ ์ง์ฐ ๋ก๋ฉ์ ์ฌ์ฉํ๊ณ ์ต์ ํ๊ฐ ํ์ํ๋ฉด ๊ทธ ๋ ํ์น ์กฐ์ธ์ ์ ์ฉํ๋ค.
10.2.8 ๊ฒฝ๋ก ํํ์
๊ฒฝ๋ก ํํ์์ด๋ m.username, m.team ์ฒ๋ผ .(์ )์ ์ฐ์ด ๊ฐ์ฒด ๊ทธ๋ํ๋ฅผ ํ์ํ๋ ๊ฒ์ด๋ค.
๊ฒฝ๋ก ํํ์์ ์ฉ์ด ์ ๋ฆฌ
- ์ํ ํ๋: ๋จ์ํ ๊ฐ์ ์ ์ฅํ๊ธฐ ์ํ ํ๋
- ์ฐ๊ด ํ๋: ์ฐ๊ด๊ด๊ณ๋ฅผ ์ํ ํ๋, ์๋ฒ ๋๋ ํ์
ํฌํจ
- ๋จ์ผ ๊ฐ ์ฐ๊ดํ๋: @ManyToOne, @OneToOne, ๋์์ด ์ํฐํฐ
- ์ปฌ๋ ์ ๊ฐ ์ฐ๊ด ํ๋: @OneToMany, @ManyToMany, ๋์์ด ์ปฌ๋ ์
@Entity
public class Member {
...์๋ต...
@Column(name = "name")
private String username; // ์ํ ํ๋
@ManyToOne(...)
private Team team; // ์ฐ๊ด ํ๋ (๋จ์ผ ๊ฐ)
@OneToMany(...)
private List<Order> orders; // ์ฐ๊ด ํ๋ (์ปฌ๋ ์
๊ฐ)
...์๋ต...
}
๊ฒฝ๋ก ํํ์ ํน์ง
JPQL์์ ๊ฒฝ๋ก ํํ์์ ํน์ง
-
์ํ ํ๋ ๊ฒฝ๋ก: ๊ฒฝ๋ก ํ์์ ๋์ด๋ค. ๋๋ ํ์ํ ์ ์๋ค.
-- JPQL select m.username, m.age from Member m -- SQL ๋ณํ select m.name, m.age from Member m
-
๋จ์ผ ๊ฐ ์ฐ๊ด ๊ฒฝ๋ก: ๋ฌต์์ ์ผ๋ก ๋ด๋ถ ์กฐ์ธ์ด ์ผ์ด๋๋ค. ๋จ์ผ ๊ฐ ์ฐ๊ด ๊ฒฝ๋ก๋ ๊ณ์ ํ์ํ ์ ์๋ค.
-- JPQL select o.member from Order o -- ๋ฌต์์ ์กฐ์ธ -- where o.product.name = 'productA' ์ด๋ ๊ฒ ๊ณ์ ํ์ ๊ฐ๋ฅ -- SQL ๋ณํ select m.* from Order o inner join Member m on o.member_id = m.id -- ๋ฌต์์ ์ผ๋ก ๋ด๋ถ ์กฐ์ธ์ด ์ผ์ด๋ฌ๋ค
-
์ปฌ๋ ์ ๊ฐ ์ฐ๊ด ๊ฒฝ๋ก: ๋ฌต์์ ์ผ๋ก ๋ด๋ถ ์กฐ์ธ์ด ์ผ์ด๋๋ค. ๋๋ ํ์ํ ์ ์๋ค. ๋จ FROM ์ ์์ ์กฐ์ธ์ ํตํด ๋ณ์นญ์ ์ป์ผ๋ฉด ๋ณ์นญ์ผ๋ก ํ์ํ ์ ์๋ค.
-- JPQL -- ์ฑ๊ณต select t.members from Team t -- ์คํจ, ์ปฌ๋ ์ ๊ฐ์ ํ์์ด ๋ถ๊ฐ select t.members.username from Team t -- ๋ค์์ฒ๋ผ ๋ณ์นญ์ ์ป์ด์ ๊ฒฝ๋กํ์์ ํด์ผ ํ๋ค. select m.username from Team t join t.members m -- ์ปฌ๋ ์ ์ ํฌ๊ธฐ๋ฅผ ๊ตฌํ ์ ์๋ size๊ธฐ๋ฅ ์ ๊ณต, ์ด๋ฅผ ์ฌ์ฉํ๋ฉด countํจ์๋ฅผ ์ฌ์ฉํ๋ SQL๋ก ๋ณํ๋๋ค. select t.members.size from Team t
๊ฒฝ๋ก ํ์์ ์ฌ์ฉํ ๋ฌต์์ ์กฐ์ธ ์ ์ฃผ์์ฌํญ
- ํญ์ ๋ด๋ถ ์กฐ์ธ์ด๋ค.
- ์ปฌ๋ ์ ์ ๊ฒฝ๋ก ํ์์ ๋์ด๋ค. ์ปฌ๋ ์ ์์ ๊ฒฝ๋ก ํ์์ ํ๋ ค๋ฉด ๋ช ์์ ์ผ๋ก ์กฐ์ธํด์ ๋ณ์นญ์ ์ป์ด์ผ ํ๋ค.
- ๊ฒฝ๋ก ํ์์ ์ฃผ๋ก SELECT, WHERE ์ (๋ค๋ฅธ ๊ณณ์์๋ ์ฌ์ฉ๋จ)์์ ์ฌ์ฉํ์ง๋ง ๋ฌต์์ ์กฐ์ธ์ผ๋ก ์ธํด SQL์ FROM์ ์ ์ํฅ์ ์ค๋ค.
๋จ์ํ๊ณ ์ฑ๋ฅ์ ์ด์๊ฐ ์์ผ๋ฉด ๋ฌธ์ ๊ฐ ์ ๋์ง๋ง, ์ฑ๋ฅ์ด ์ค์ํ๋ฉด ๋ถ์ํ๊ธฐ ์ฝ๋๋ก ๋ฌต์์ ์กฐ์ธ๋ณด๋ค๋ ๋ช ์์ ์กฐ์ธ์ ์ฌ์ฉํ์.
10.2.9 ์๋ธ ์ฟผ๋ฆฌ
JPQL๋ SQL์ฒ๋ผ ์๋ธ ์ฟผ๋ฆฌ๋ฅผ ์ง์ํ๋ค.
- ์ฌ์ฉ๊ฐ๋ฅ: WHERE, HAVING ์
- ์ฌ์ฉ๋ถ๊ฐ: SELECT, FROM ์
- ํ์ด๋ฒ๋ค์ดํธ์ HQL์ SELECT ์ ์ ์๋ธ ์ฟผ๋ฆฌ๋ ํ์ฉํ๋ค. ํ์ง๋ง FROM ์ ์ ์๋ธ ์ฟผ๋ฆฌ๋ ์ง์ํ์ง ์๋๋ค.
- ํ์ด๋ฒ๋ค์ดํธ์ HQL์ SELECT ์ ์ ์๋ธ ์ฟผ๋ฆฌ๋ ํ์ฉํ๋ค. ํ์ง๋ง FROM ์ ์ ์๋ธ ์ฟผ๋ฆฌ๋ ์ง์ํ์ง ์๋๋ค.
์๋ธ์ฟผ๋ฆฌ ์
-- ๋์ด๊ฐ ํ๊ท ๋ณด๋ค ๋ง์ ํ์
select m from Member m
where m.age > (select avg(m2.age) from Member m2)
-- ํ ๊ฑด์ด๋ผ๋ ์ฃผ๋ฌธํ ๊ณ ๊ฐ
select m from Member m
where (select count(o) from Order o where m = o.member) > 0
-- ํ ๊ฑด์ด๋ผ๋ ์ฃผ๋ฌธํ ๊ณ ๊ฐ
-- size ์ด์ฉ, ์์ ๊ฒฐ๊ณผ๊ฐ ๊ฐ๋ค.(์คํ SQL๋ ๊ฐ๋ค)
select m from Member m
where m.orders.size > 0
์๋ธ ์ฟผ๋ฆฌ ํจ์
์๋ธ ์ฟผ๋ฆฌ๋ ๋ค์ ํจ์๋ค๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค.
- EXISTS
- ๋ฌธ๋ฒ: [NOT] EXISTS (subquery)
- ์ค๋ช
: ์๋ธ์ฟผ๋ฆฌ์ ๊ฒฐ๊ณผ๊ฐ ์กด์ฌํ๋ฉด ์ฐธ์ด๋ค.
select m from Member m where exists (select t from m.team t where t.name = 'ํA')
- {ALL | ANY | SOME}
- ๋ฌธ๋ฒ: {ALL | ANY | SOME} (subquery)
- ์ค๋ช
: ๋น๊ต ์ฐ์ฐ์์ ๊ฐ์ด ์ฌ์ฉํ๋ค. {= | > | >= | < | <= | <>}
- ALL: ์กฐ๊ฑด์ ๋ชจ๋ ๋ง์กฑํ๋ฉด ์ฐธ์ด๋ค.
- ANY ํน์ SOME: ๋์ ๊ฐ์ ์๋ฏธ๋ค. ์กฐ๊ฑด์ ํ๋๋ผ๋ ๋ง์กฑํ๋ฉด ์ฐธ์ด๋ค.
select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p) select m from Member m where m.team = ANY (select t from Team t)
- IN
- ๋ฌธ๋ฒ: [NOT] IN (subquery)
- ์ค๋ช
: ์๋ธ์ฟผ๋ฆฌ์ ๊ฒฐ๊ณผ ์ค ํ๋๋ผ๋ ๊ฐ์ ๊ฒ์ด ์์ผ๋ฉด ์ฐธ์ด๋ค. ์ฐธ๊ณ ๋ก IN์ ์๋ธ์ฟผ๋ฆฌ๊ฐ ์๋๊ณณ์์๋ ์ฌ์ฉํ๋ค.
select t from Team t where t IN (select t2 From Team t2 JOIN t2.members m2 where m2.age >= 20)
10.2.10 ์กฐ๊ฑด์
ํ์
ํํ
JPQL์์ ์ฌ์ฉํ๋ ํ์
์ ๋ค์๊ณผ ๊ฐ์ด ํ์ํ๋ค. ๋์๋ฌธ์๋ ๊ตฌ๋ถํ์ง ์๋๋ค.
์ข ๋ฅ | ์ค๋ช | ์์ |
---|---|---|
๋ฌธ์ |
์์ ๋ฐ์ดํ ์ฌ์ด์ ํํ ์์ ๋ฐ์ดํ๋ฅผ ํํํ๊ณ ์ถ์ผ๋ฉด ์์ ๋ฐ์ดํ ์ฐ์ ๋๊ฐ('') ์ฌ์ฉ |
'HELLO' 'She''s' |
์ซ์ |
L(Long ํ์
์ง์ ) D(Doublc ํ์ ์ง์ ) F(Float ํ์ ์ง์ ) |
10L 10D 10F |
๋ ์ง |
DATE {d 'yyyy-mm-dd'} TIME {t 'hh-mm-ss'} DATETIME {ts 'yyyy-mm-dd hh-mm:ss.f'} |
{d '2012-03-24'} {t '10-11-28'} {ts '2012-03-24 10-11-28.123'} m.createDate = {d '2012-03-24'} |
Boolen | TRUE, FALSE | |
Enum | ํจํค์ง๋ช ์ ํฌํจํ ์ ์ฒด ์ด๋ฆ์ ์ฌ์ฉํด์ผ ํ๋ค. | jpabook.MemberType.Admin |
์ํฐํฐ ํ์ | ์ํฐํฐ์ ํ์ ์ ํํํ๋ค. ์ฃผ๋ก ์์๊ณผ ๊ด๋ จํด์ ์ฌ์ฉํ๋ค. | TYPE(m) = Member |
์ฐ์ฐ์ ์ฐ์ ์์
- ๊ฒฝ๋ก ํ์ ์ฐ์ฐ (.)
- ์ํ ์ฐ์ฐ: +, -(๋จํญ ์ฐ์ฐ์), *, /, +, -
- ๋น๊ต์ฐ์ฐ: =, >, >=, <, <=, <>
- ๋ ผ๋ฆฌ ์ฐ์ฐ: NOT, AND, OR
๋ ผ๋ฆฌ ์ฐ์ฐ๊ณผ ๋น๊ต์
- ๋
ผ๋ฆฌ ์ฐ์ฐ
- AND
- OR
- NOT
- ๋น๊ต์
- =, >, >=, <, <=, <>
Between, IN, Like, NULL ๋น๊ต
- Between ์
- ๋ฌธ๋ฒ: x [NOT] BETWEEN A AND B
- ์ค๋ช : x๋ A ~ B ์ฌ์ด์ ๊ฐ์ด๋ฉด ์ฐธ์ด๋ค.(A, B๊ฐ ํฌํจ)
- IN ์
- ๋ฌธ๋ฒ: x [NOT] IN
- ์ค๋ช : X์ ๊ฐ์ ๊ฐ์ด ํ๋๋ผ๋ ์์ผ๋ฉด ์ฐธ์ด๋ค.
- Like ์
- ๋ฌธ๋ฒ: ๋ฌธ์ํํ์ [NOT] LIKE ํจํด๊ฐ [ESCAPE ์ด์ค์ผ์ดํ๋ฌธ์]
- ์ค๋ช
: ๋ฌธ์ํํ์๊ณผ ํจํ
๊ฐ์ ๋น๊ตํ๋ค.
- %(ํผ์ผํธ): ์๋ฌด ๊ฐ๋ค์ด ์ ๋ ฅ๋์ด๋ ๋๋ค. ๊ฐ์ด ์์ด๋ ๋๋ค.
- _(์ธ๋๋ผ์ธ): ํ ๊ธ์๋ ์๋ฌด ๊ฐ์ด ์ ๋ ฅ๋์ด๋ ๋์ง๋ง ๊ฐ์ด ์์ด์ผ ๋๋ค.
-- ํ์A, ํ์1.. where m.username like 'ํ์_' -- ํ์3 where m.username like '__3' -- ํ์% where m.username like 'ํ์\%' ESCAPE '\'
- NULL ๋น๊ต์
- ๋ฌธ๋ฒ: {๋จ์ผ๊ฐ ๊ฒฝ๋ก | ์ ๋ ฅ ํ๋ผ๋ฏธํฐ} IS [NOT] NULL
- ์ค๋ช : NULL์ธ์ง ๋น๊ตํ๋ค. NULL์ = ์ผ๋ก ๋น๊ตํ๋ฉด ์ ๋๊ณ ๊ผญ IS NULL์ ์ฌ์ฉ ํด์ผ ํ๋ค.
where m.username is null where null = null -- ๊ฑฐ์ง where 1 = 1 -- ์ฐธ
์ปฌ๋ ์
์
์ปฌ๋ ์
์๋ง ์ฌ์ฉํ๋ ํน๋ณํ ๊ธฐ๋ฅ์ด๋ค. ์ปฌ๋ ์
์ ์ปฌ๋ ์
์ ์ด์ธ์ ๋ค๋ฅธ ์์ ์ฌ์ฉํ ์ ์๋ค.
- ๋น ์ปฌ๋ ์
๋น๊ต ์
- ๋ฌธ๋ฒ: {์ปฌ๋ ์ ๊ฐ ์ฐ๊ด ๊ฒฝ๋ก} IS [NOT] EMPTY
- ์ค๋ช : ์ปฌ๋ ์ ์ ๊ฐ์ด ๋น์์ผ๋ฉด ์ฐธ
-- JPQL select m from Member m where m.orders is not empty -- SQL ๋ณํ select m.* from Member m where exists ( select o.id from Orders o where m.id = o.member_id )
- ์ปฌ๋ ์
์ ๋ฉค๋ฒ ์
- ๋ฌธ๋ฒ: {์ํฐํฐ๋ ๊ฐ} [NOT] MEMBER [OF] {์ปฌ๋ ์ ๊ฐ ์ฐ๊ด ๊ฒฝ๋ก}
- ์ค๋ช : ์ํฐํฐ๋ง ๊ฐ์ด ์ปฌ๋ ์ ์ ํฌํจ๋์ด ์์ผ๋ฉด ์ฐธ
select t from Team t where :memberParam member of t.members
์ค์นผ๋ผ ์
์ค์นผ๋ผ๋ ์ซ์, ๋ฌธ์, ๋ ์ง, case, ์ํฐํฐ ํ์
๊ฐ์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํ์
๋ค์ ๋งํ๋ค.
- ์ํ ์
- +, -: ๋จํญ ์ฐ์ฐ์ (์์, ์์)
- *, /, +, -: ์ฌ์น ์ฐ์ฐ
- ๋ฌธ์ ํจ์
ํจ์ | ์ค๋ช | ์์ |
---|---|---|
CONCAT(๋ฌธ์1, ๋ฌธ์2, ...) | ๋ฌธ์๋ฅผ ํฉํ๋ค. | CONCAT('A', 'B') = AB |
SUBSTRING(๋ฌธ์, ์์น, [๊ธธ์ด]) | ์์น๋ถํฐ ์์ํด ๊ธธ์ด๋งํผ ๋ฌธ์๋ฅผ ๊ตฌํ๋ค. ๊ธธ์ด ๊ฐ์ด ์์ผ๋ฉด ๋๋จธ์ง ์ ์ฒด ๊ธธ์ด๋ฅผ ๋ปํ๋ค. | SUBSTRING('ABCDEF', 2, 3) = BCD |
TRIM([[LEADING | TRAILING | BOTH] [ํธ๋ฆผ๋ฌธ์] FROM] ๋ฌธ์) |
LEADING: ์ผ์ชฝ๋ง ํธ๋ฆผ TRAILING: ์ค๋ฅธ์ชฝ๋ง ํธ๋ฆผ BOTH: ์์ชฝ ๋ค ํธ๋ฆผ ๊ธฐ๋ณธ๊ฐ์ BOTH ํธ๋ฆผ ๋ฌธ์์ ๊ธฐ๋ณธ๊ฐ์ ๊ณต๋ฐฑ(space)์ด๋ค. |
TRIM('ABC') = 'ABC' |
LOWER(๋ฌธ์) | ์๋ฌธ์๋ก ๋ณ๊ฒฝ | LOWER('ABC') = 'abc' |
UPPER(๋ฌธ์) | ๋๋ฌธ์๋ก ๋ณ๊ฒฝ | UPPER('abc') = 'ABC' |
LENGTH(๋ฌธ์) | ๋ฌธ์ ๊ธธ์ด | LENGTH('ABC') = 3 |
LOCATE(์ฐพ์ ๋ฌธ์, ์๋ณธ ๋ฌธ์, [๊ฒ์ ์์ ์์น]) | ๊ฒ์์์น๋ถํฐ ๋ฌธ์๋ฅผ ๊ฒ์ํ๋ค. 1๋ถํฐ ์์, ๋ชป ์ฐพ์ผ๋ฉด 0 ๋ฐํ | LOCATE('DE', 'ABCDEFG') = 4 |
- ์ํ ํจ์
ํจ์ | ์ค๋ช | ์์ |
---|---|---|
ABS(์ํ์) | ์ ๋๊ฐ์ ๊ตฌํ๋ค | ABS(-10) = 10 |
SQRT(์ํ์) | ์ ๊ณฑ๊ทผ์ ๊ตฌํ๋ค. | SQRT(4) = 2.0 |
MOD(์ํ์, ๋๋ ์) | ๋๋จธ์ง๋ฅผ ๊ตฌํ๋ค. | MOD(4, 3) = 1 |
SIZE(์ปฌ๋ ์ ๊ฐ ์ฐ๊ด ๊ฒฝ๋ก์) | ์ปฌ๋ ์ ์ ํฌ๊ธฐ๋ฅผ ๊ตฌํ๋ค | SIZE(t.members) |
INDEX(๋ณ์นญ) | LIST ํ์ ์ปฌ๋ ์ ์ ์์น๊ฐ์ ๊ตฌํจ, ๋จ ์ปฌ๋ ์ ์ด @OrderColumn์ ์ฌ์ฉํ๋ LIST ํ์ ์ผ ๋๋ง ์ฌ์ฉํ ์ ์๋ค. | t.members m where INDEX(m) > 0 |
- ๋ ์ง ํจ์
๋ ์ง ํจ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ์ฌ ์๊ฐ์ ์กฐํํ๋ค.- CURRENT_DATE: ํ์ฌ ๋ ์ง
- CURRENT_TIME: ํ์ฌ ์๊ฐ
- CURRENT_TIMESTAMP: ํ์ฌ ๋ ์ง ์๊ฐ
select CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP from Team t
select e from Event e where e.endDate < CURRENT_DATE
ํ์ด๋ฒ๋ค์ดํธ๋ ๋ ์ง ํ์ ์์ ๋ , ์, ์ผ, ์๊ฐ, ๋ถ, ์ด ๊ฐ์ ๊ตฌํ๋ ๊ธฐ๋ฅ์ ์ง์ํ๋ค.
-- YEAR, MONTH, DAY, HOUR, MINUTE, SECOND
select year(CURRENT_TIMESTAMP), month(CURRENT_TIMESTAMP) from Member
CASE ์
ํน์ ์กฐ๊ฑด์ ๋ฐ๋ผ ๋ถ๊ธฐํ ๋ CASE ์์ ์ฌ์ฉํ๋ค.
- ๊ธฐ๋ณธ CASE
- ๋ฌธ๋ฒ:
CASE {WHEN <์กฐ๊ฑด์> THEN <์ค์นผ๋ผ์>}+ ELSE <์ค์นผ๋ผ์> END
select case when m.age <= 10 then 'ํ์์๊ธ' when m.age >= 10 then '๊ฒฝ๋ก์๊ธ' else '์ผ๋ฐ์๊ธ' end from Member m
- ๋ฌธ๋ฒ:
- ์ฌํ CASE
- ๋ฌธ๋ฒ:
CASE {WHEN <์ค์นผ๋ผ์1> THEN <์ค์นผ๋ผ์2>}+ ELSE <์ค์นผ๋ผ์> END
select case t.name when 'ํA' then '์ธ์ผํฐ๋ธ110%' when 'ํB' then '์ธ์ผํฐ๋ธ120%' else '์ธ์ผํฐ๋ธ105%' end from Team t
- ๋ฌธ๋ฒ:
- COALESCE
- ๋ฌธ๋ฒ: COALESCE(<์ค์นผ๋ผ์> {, <์ค์นผ๋ผ์>}+)์ค์นผ๋ผ์>์ค์นผ๋ผ์>
- ์ค๋ช
: ์ค์นผ๋ผ์์ ์ฐจ๋ก๋๋ก ์กฐํํด์ null์ด ์๋๋ฉด ๋ฐํํ๋ค.
-- m.username์ด null์ด ์๋๋ฉด '์ด๋ฆ ์๋ ํ์'์ ๋ฐํํ๋ค. select coalesce(m.username, '์ด๋ฆ ์๋ ํ์') from Member m
- NULLIF
- ๋ฌธ๋ฒ: NULLIF(<์ค์นผ๋ผ์>, <์ค์นผ๋ผ์>)์ค์นผ๋ผ์>์ค์นผ๋ผ์>
- ์ค๋ช
: ๋ ๊ฐ์ด ๊ฐ์ผ๋ฉด null์ ๋ฐํํ๊ณ ๋ค๋ฅด๋ฉด ์ฒซ ๋ฒ์งธ ๊ฐ์ ๋ฐํํ๋ค.
-- ๊ด๋ฆฌ์๋ฉด null์ ๋ฐํํ๊ณ ๋๋จธ์ง๋ ๋ณธ์ธ ์ด๋ฆ์ ๋ฐํ select nullif(m.username, '๊ด๋ฆฌ์') from Member m
10.2.11 ๋คํ์ฑ ์ฟผ๋ฆฌ
JPQL๋ก ๋ถ๋ชจ ์ํฐํฐ๋ฅผ ์กฐํํ๋ฉด ๊ทธ ์์ ์ํฐํฐ๋ ํจ๊ป ์กฐํ๋๋ค.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {...}
@Entity
@DiscriminatorValue("B")
public class Book extends Item {
private String author;
...
}
๋ค์์ ์กฐํํ๋ฉด Item์ ์์๋ ํจ๊ป ์กฐํ๋๋ค.
List resultList = em.createQuery("select t from Item i").getResultList();
-- ๋จ์ผ ํ
์ด๋ธ ์ ๋ต(InheritanceType.SINGLE_TABLE) SQL
SELECT * FROM ITEM
-- ์กฐ์ธ ์ ๋ต(InheritanceType.JOINED) SQL
SELECT
i.ITEM_ID, i.DTYPE, i.name, i.price, i.stockQuantity,
b.author, b.isbn,
b.artist, a.ect,
m.actor, m.director
FROM
ITEM i
LEFT OUTER JOIN
BOOK b on i.ITEM_ID = b.ITEM_ID
LEFT OUTER JOIN
ALBUM a on i.ITEM_ID = a.ITEM_ID
LEFT OUTER JOIN
MOVIE M on i.ITEM_ID = m.ITEM_ID
TYPE
TYPE์ ์ํฐํฐ์ ์์ ๊ตฌ์กฐ์์ ์กฐํ ๋์์ ํน์ ์์ ํ์
์ผ๋ก ํ์ ํ ๋ ์ฃผ๋ก ์ฌ์ฉํ๋ค.
-- Item ์ค์ Book, Movie๋ฅผ ์กฐํํ๋ผ.
-- JPQL
select i from Item i
where type(i) IN (Book, Movie)
-- SQL
SELECT i FROM Item i
WHERE i.DTYPE in ('B','M')
TREAT (JPA 2.1)
์๋ฐ์ ํ์
์บ์คํ
๊ณผ ๋น์ทํ๋ค. ์์ ๊ตฌ์กฐ์์ ๋ถ๋ชจ ํ์
์ ํน์ ์์ ํ์
์ผ๋ก ๋ค๋ฃฐ ๋ ์ฌ์ฉํ๋ค.
JPA ํ์ค์ FROM, WHERE ์ ์์ ์ฌ์ฉํ ์ ์์ง๋ง, ํ์ด๋ฒ๋ค์ดํธ๋ SELECT ์ ์์๋ ์ฌ์ฉํ ์ ์๋ค.
-- JPQL
-- treat๋ฅผ ์ฌ์ฉํด์ ๋ถ๋ชจ ํ์
์ธ Item์ ์์ ํ์
์ธ Book์ผ๋ก ๋ค๋ฃฌ๋ค.
-- ๋ฐ๋ผ์ Book์ author ํ๋์ ์ ๊ทผ ํ ์ ์๋ค.
select i from Item i where treat(i as Book).author = 'kim'
10.2.12 ์ฌ์ฉ์ ์ ์ ํจ์ ํธ์ถ (JPA 2.1)
JPA 2.1๋ถํฐ ์ฌ์ฉ์ ์ ์ ํจ์๋ฅผ ์ง์ํ๋ค.
๋ฌธ๋ฒ
function_invocation::= FUNCTION(function_name {, function_arg}*)
์
select function('group_concat', i.name) from Item i
-- ํ์ด๋ฒ๋ค์ดํธ ๊ตฌํ์ฒด๋ ์ถ์ฝ ๊ฐ๋ฅ
select group_concat(i.name) from Item i
ํ์ด๋ฒ๋ค์ดํธ ๊ตฌํ์ฒด๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๋ฐฉ์ธ ํด๋์ค๋ฅผ ์์ํด์ ๊ตฌํํ๊ณ , ์ฌ์ฉํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํจ์๋ฅผ ๋ฏธ๋ฆฌ ๋ฑ๋กํด์ผ ํ๋ค.
// ๋ฐฉ์ธ ํด๋์ค ์์
public class MyH2Dialect extends H2Dialect {
public MyH2Dialect() {
registerFunction(
"group_concat",
new StandardSQLFunction("group_concat", StandardBasicTypes.STRING)
);
}
}
<!-- ์์ํ ๋ฐฉ์ธ ํด๋์ค ๋ฑ๋ก -->
<property name="hibernate.dialect" value="hello.MyH2Dialect">
10.2.13 ๊ธฐํ ์ ๋ฆฌ
- enum์ = ๋น๊ต ์ฐ์ฐ๋ง ์ง์ํ๋ค.
- ์๋ฒ ๋๋ ํ์ ์ ๋น๊ต๋ฅผ ์ง์ํ์ง ์๋๋ค.
EMPTY STRING
- JPA ํ์ค์ โโ์ ๊ธธ์ด 0์ธ Empty String์ผ๋ก ์ ํ์ง๋ง ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ๋ผ โโ๋ฅผ NULL๋ก ์ฌ์ฉํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์์ผ๋ฏ๋ก ํ์ธํ๊ณ ์ฌ์ฉํด์ผ ํ๋ค.
NULL ์ ์
- ์กฐ๊ฑด์ ๋ง์กฑํ๋ ๋ฐ์ดํฐ๊ฐ ํ๋๋ ์์ผ๋ฉด NULL์ด๋ค.
- NULL์ ์ ์ ์๋ ๊ฐ(unknown value)์ด๋ค. NULL๊ณผ์ ๋ชจ๋ ์ํ์ ๊ณ์ฐ ๊ฒฐ๊ณผ๋ NULL์ด ๋๋ค.
- NULL == NULL์ ์ ์ ์๋ ๊ฐ์ด๋ค.
- NULL is NULL์ ์ฐธ์ด๋ค.
JPA ํ์ค ๋ช ์ธ NULL(U), TRUE(T), FALSE(F)์ ๋ ผ๋ฆฌ ๊ณ์ฐ ํ
- AND
AND | T | F | U |
---|---|---|---|
T | T | F | U |
F | F | F | F |
U | U | F | U |
- OR
OR | T | F | U |
---|---|---|---|
T | T | T | T |
F | T | F | U |
U | T | U | U |
- NOT
NOT | ย |
---|---|
T | F |
F | T |
U | U |
10.2.14 ์ํฐํฐ ์ง์ ์ฌ์ฉ
๊ธฐ๋ณธ ํค ๊ฐ
JPQL์์ ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ์ง์ ์ฌ์ฉํ๋ฉด SQL์์๋ ํด๋น ์ํฐํฐ์ ๊ธฐ๋ณธ ํค ๊ฐ์ ์ฌ์ฉํ๋ค.
-- JPQL
-- ์ํฐํฐ์ ์์ด๋๋ฅผ ์ฌ์ฉ
select count(m.id) from Member m
-- ์ํฐํฐ๋ฅผ ์ง์ ์ฌ์ฉ
select count(m) from Member m
// ์ํฐํฐ๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ง์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
String sql = "select m from Member m where m = :member";
List resultList = em.createQuery(sql)
.setParameter("member", member)
.getResultList();
// ์๋ณ์ ๊ฐ์ ์ง์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
String sql = "select m from Member m where m.id = :memberId";
List resultList = em.createQuery(sql)
.setParameter("memberId", 1L)
.getResultList();
์ธ๋ ํค ๊ฐ
์ธ๋ ํค ๊ฐ๋ ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ์ง์ ์ฌ์ฉํ ์ ์๋ค.
// ์ํฐํฐ๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ง์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
String sql = "select m from Member m where m.team = :team";
List resultList = em.createQuery(sql)
.setParameter("team", team)
.getResultList();
// ์๋ณ์ ๊ฐ์ ์ง์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
String sql = "select m from Member m where m.team.id = :teamId";
List resultList = em.createQuery(sql)
.setParameter("teamId", 1L)
.getResultList();
10.2.15 Named ์ฟผ๋ฆฌ: ์ ์ ์ฟผ๋ฆฌ
Named ์ฟผ๋ฆฌ๋ ์ ํ๋ฆฌ์ผ์ด์ ๋ก๋ฉ ์์ ์ JPQL ๋ฌธ๋ฒ์ ์ฒดํฌํ๊ณ ๋ฏธ๋ฆฌ ํ์ฑํด๋๋ค. ๋ฐ๋ผ์ ์๋ฌ ์ ๋ฌด๋ฅผ ๋นจ๋ฆฌ ํ์ธํ ์ ์๊ณ , ์ฌ์ฌ์ฉํ๋ฏ๋ก ์ฑ๋ฅ์ ์ด์ ๋ ์๋ค.
Named ์ฟผ๋ฆฌ๋ฅผ ์ด๋ ธํ ์ด์ ์ ์ ์
// @NamedQuery๋ก Named ์ฟผ๋ฆฌ ์ ์
@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
public class Member {
...
}
// @NamedQueries๋ฅผ ์ฌ์ฉํด ์ฌ๋ฌ๊ฐ์ @NamedQuery ์ง์
@Entity
@NamedQueries(
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"
),
@NamedQuery(
name = "Member.count",
query = "select count(m) from Member m"
)
)
public class Member {
...
}
// ์ฌ์ฉ
List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
.setPrameter("username", "ํ์1")
.getResultList();
Named ์ฟผ๋ฆฌ์ Member.findByUsername ์ฒ๋ผ Member๋ฅผ ๋ถ์ธ ๊ฒ์ ๊ธฐ๋ฅ์ ์ผ๋ก ํน๋ณํ ์๋ฏธ๊ฐ ์๋ ๊ฒ์ด ์๋๋ค. ์ถฉ๋๋ฐฉ์ง, ๊ด๋ฆฌ์ ์ฉ์ดํจ์ ์ํด ๋ถ์ธ ๊ฒ์ด๋ค.
10.3 Criteria
Criteria ์ฟผ๋ฆฌ๋ JPQL์ ์๋ฐ ์ฝ๋๋ก ์์ฑํ๋๋ก ๋์์ฃผ๋ ๋น๋ ํด๋์ค API๋ค. ๋ฌธ์๊ฐ ์๋ ์ฝ๋๋ก JPQL์ ์์ฑํ๋ฏ๋ก ๋ฌธ๋ฒ ์ค๋ฅ๋ฅผ ์ปดํ์ผ ๋จ๊ณ์์ ์ก์ ์ ์๋ค. ๋์ ์ฟผ๋ฆฌ๋ฅผ ์์ ํ๊ฒ ์์ฑํ ์ ์๋ค. ๋ค๋ง ์ฝ๋๊ฐ ๋ณต์กํ๊ณ ์ฅํฉํด์ ์ง๊ด์ ์ผ๋ก ์ดํด๊ฐ ํ๋ค๋ค๋ ๋จ์ ์ด ์๋ค.
๋ด์ฉ ์๋ต ๐
10.4 QueryDSL
QueryDSL์ Criteria ์ฒ๋ผ JPQL ๋น๋ ์ญํ ์ ํ๋๋ฐ ์ฝ๊ณ ๊ฐ๊ฒฐํ๋ฉฐ, ๊ทธ ๋ชจ์๋ ์ฟผ๋ฆฌ์ ๋น์ทํ๊ฒ ๊ฐ๋ฐํ ์ ์๋ค.
QueryDSL์ ์คํ์์ค ํ๋ก์ ํธ์ด๋ค. ์๋ ์ฌ์ดํธ ์ฐธ๊ณ
10.4.1 QueryDSL ์ค์
ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
<!-- QueryDSL JPA ๋ผ์ด๋ธ๋ฌ๋ฆฌ -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
<!-- ์ฟผ๋ฆฌ ํ์
(Q)์ ์์ฑํ ๋ ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<scope>provided</scope>
</dependency>
ํ๊ฒฝ์ค์
QueryDSL์ ์ฌ์ฉํ๋ ค๋ฉด ์ํฐํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฟผ๋ฆฌ ํ์
์ด๋ผ๋ ์ฟผ๋ฆฌ์ฉ ํด๋์ค๋ฅผ ์์ฑํด์ผ ํ๋ค. ๋ค์์ฒ๋ผ ํ๋ฌ๊ทธ์ธ์ ์ถ๊ฐํด์ผ ํ๋ค.
<project>
<build>
<plugins>
...
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
</build>
</project>
์ด์ ์ฝ์์์ mvc compile์ ์
๋ ฅํ๋ฉด outputDirectory์ ์ง์ ํ target/generated-sources ์์น์ QMember.java ์ฒ๋ผ Q๋ก ์์ํ๋ ์ฟผ๋ฆฌ ํ์
๋ค์ด ์์ฑ๋๋ค.
10.4.2 ์์
๊ฐ๋จ ์์
public void queryDSL() {
JPAQuery query = new JPAQuery(entityManager);
QMember qMember = new QMember("m"); // ์์ฑ๋๋ JPQL์ ๋ณ์นญ์ด m
List<Member> members = query.from(qMember)
.where(qMember.name.eq("ํ์1"))
.orderBy(qMember.name.desc)
.list(qMember);
}
๊ธฐ๋ณธ Q ์์ฑ
์ฟผ๋ฆฌ ํ์
(Q)๋ ์ฌ์ฉํ๊ธฐ ํธ๋ฆฌํ๋๋ก ๋ค์๊ณผ ๊ฐ์ด ๊ธฐ๋ณธ ์ธ์คํด์ค๋ฅผ ๋ณด๊ดํ๊ณ ์๋ค.
public class QMember extends EntityPathBase<Member> {
public static final QMember member = new QMember("member1");
...
}
๊ฐ์ ์ํฐํฐ๋ฅผ ์กฐ์ธํ๊ฑฐ๋, ๊ฐ์ ์ํฐํฐ๋ฅผ ์๋ธ์ฟผ๋ฆฌ์ ์ฌ์ฉํ๋ฉด ๊ฐ์ ๋ณ์นญ์ด ์ฌ์ฉ๋๋ฏ๋ก ์ด๋๋ ๋ณ์ง์ ์ง์ ์ง์ ํด์ ์ฌ์ฉํด์ผ ํ๋ค.
QMember qMember = new QMember("m"); // ์ง์ ์ง์
QMember qMember = QMember.member; // ๊ธฐ๋ณธ ์ธ์คํด์ค ์ฌ์ฉ
๋ค์๊ณผ ๊ฐ์ด import static์ ํ์ฉํ๋ฉด ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ์์ฑ ๊ฐ๋ฅํ๋ค.
import static jpabook.jpashop.domain.QMember.member; // ๊ธฐ๋ณธ ์ธ์คํด์ค
public void basic() {
JPAQuery query = new JPAQuery(entityManager);
List<Member> members = query.from(member) // member ๋ฐ๋ก ์ฌ์ฉ
.where(member.name.eq("ํ์1"))
.orderBy(member.name.desc)
.list(member);
}
10.4.3 ๊ฒ์ ์กฐ๊ฑด ์ฟผ๋ฆฌ
// where and
query.from(customer)
.where(customer.firstName.eq("Bob").and(customer.lastName.eq("Wilson")));
// where or
query.from(customer)
.where(customer.firstName.eq("Bob").or(customer.lastName.eq("Wilson")));
// between
// 1800 ~ 2000 ๋
๋
doc.year.between("1800", "2000");
// contains
// sql์ like '%์ํ1%'
item.name.contains("์ํ1");
// startsWith
// sql์ like '์ค%'
person.firstName.startsWith("์ค");
10.4.4 ๊ฒฐ๊ณผ ์กฐํ
์ฟผ๋ฆฌ ์์ฑ์ด ๋๋๊ณ ๊ฒฐ๊ณผ ์กฐํ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์กฐํํ๋ค. ๊ฒฐ๊ณผ ์กฐํ API๋ com.mysema.query.Projectable์ ์ ์๋์ด ์๋ค.
- uniqueResult(): ์กฐํ ๊ฒฐ๊ณผ๊ฐ ํ ๊ฑด์ผ ๋ ์ฌ์ฉ. ์กฐํ ๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด null์ ๋ฐํํ๊ณ ํ๋ ์ด์์ด๋ฉด ์์ธ ๋ฐ์
- singleResult(): uniqueResult()์ ๊ฐ์ง๋ง ๊ฒฐ๊ณผ๊ฐ ํ๋ ์ด์์ด๋ฉด ์ฒ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํ
- list(): ๊ฒฐ๊ณผ๊ฐ ํ๋ ์ด์์ผ ๋ ์ฌ์ฉ. ๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด ๋น ์ปฌ๋ ์ ๋ฐํ
10.4.5 ํ์ด์ง๊ณผ ์ ๋ ฌ
query.from(item)
.where(item.price.gt(20000))
.orderBy(item.price.desc(), item.stockQuantity.asc())
.offset(10).limit(20)
.list(item);
์ค์ ํ์ด์ง ์ฒ๋ฆฌ๋ฅผ ํ๋ ค๋ฉด ๊ฒ์๋ ์ ์ฒด ๋ฐ์ดํฐ ์๋ฅผ ์์์ผ ํ๋ค. ์ด๋๋ list() ๋์ ์ listResults()๋ฅผ ์ฌ์ฉํ๋ค.
SearchResults<Item> result = query.from(item)
.where(item.price.gt(20000))
.orderBy(item.price.desc(), item.stockQuantity.asc())
.offset(10).limit(20)
.listResults(item);
long total = result.getTotal(); // ๊ฒ์๋ ์ ์ฒด ๋ฐ์ดํฐ ์
List<Item> results = result.getResults(); // ์กฐํ๋ ๋ฐ์ดํฐ
10.4.6 ๊ทธ๋ฃน
query.from(item)
.groupBy(item.price)
.having(item.price.gt(1000))
.list(item);
10.4.7 ์กฐ์ธ
์กฐ์ธ์ innerJoin, leftJoin, rightJoin, fullJoin์ ์ฌ์ฉํ ์ ์๋ค. ์ถ๊ฐ๋ก JPQL์ fetch์กฐ์ธ๋ ์ฌ์ฉํ ์ ์๋ค.
์กฐ์ธ์ ๊ธฐ๋ณธ ๋ฌธ๋ฒ์ ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์ ์กฐ์ธ ๋์์ ์ง์ ํ๊ณ , ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์ ๋ณ์นญ์ผ๋ก ์ฌ์ฉํ ์ฟผ๋ฆฌ ํ์ ์ ์ง์ ํ๋ค.
join(์กฐ์ธ๋์, ๋ณ์นญ์ผ๋ก ์ฌ์ฉํ ์ฟผ๋ฆฌ ํ์
)
// ๊ธฐ๋ณธ ์ฌ์ฉ ๋ฐฉ๋ฒ
QCat cat = QCat.cat;
QCat mate = new QCat("mate");
QCate kitten = new QCat("kitten");
query.from(cat)
.innerJoin(cat.mate, mate)
.leftJoin(cat.kittens, kitten)
.list(cat);
// ์กฐ์ธ on ์ฌ์ฉ
query.from(cat)
.leftJoin(cat.kittens, kitten)
.on(kitten.bodyWeight.gt(10.0))
.list(cat);
// ํ์น ์กฐ์ธ ์ฌ์ฉ
query.from(cat)
.innerJoin(cat.mate, mate).fetch()
.leftJoin(cat.kittens, kitten).fetch()
.list(cat);
// ์ธํ ์กฐ์ธ ์ฌ์ฉ
query.from(cat, mate)
.where(cat.mate.eq(mate))
.list(cat);
10.4.8 ์๋ธ ์ฟผ๋ฆฌ
- ์๋ธ ์ฟผ๋ฆฌ๋ฅผ ๋ง๋ค๋ ค๋ฉด com.mysema.query.jpa.JPASubQuery๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
- ์๋ธ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๊ฐ ํ๋๋ฉด unique(), ์ฌ๋ฌ ๊ฑด์ด๋ฉด list()๋ฅผ ์ฌ์ฉํ๋ค.
// ์๋ธ ์ฟผ๋ฆฌ ํ ๊ฑด
QItem item = QItem.item;
QItem itemSub = new QItem("itemSub");
query.from(item)
.where(item.price.eq(
new JPASubQuery().from(itemSub).unique(itemSub.price.max())
))
.list(item);
// ์๋ธ ์ฟผ๋ฆฌ ์ฌ๋ฌ ๊ฑด
QItem item = QItem.item;
QItem itemSub = new QItem("itemSub");
query.from(item)
.where(item.in(
new JPASubQuery().from(itemSub)
.where(item.name.eq(itemSub.name))
.list(itemSub)
))
.list(item);
10.4.9 ํ๋ก์ ์ ๊ณผ ๊ฒฐ๊ณผ ๋ฐํ
select ์ ์ ์กฐํ ๋์์ ์ง์ ํ๋ ๊ฒ์ ํ๋ก์ ์ ์ด๋ผ ํ๋ค.
ํ๋ก์ ์
๋์์ด ํ๋
ํ๋ก์ ์
๋์์ด ํ๋๋ฉด ํด๋น ํ์
์ผ๋ก ๋ฐํํ๋ค.
QItem item = QItem.item;
List<String> result = query.from(item).list(item.name);
์ฌ๋ฌ ์ปฌ๋ผ ๋ฐํ๊ณ ํํ
ํ๋ก์ ์
๋์์ด ์ฌ๋ฌ ํ๋๋ฅผ ์ ํํ๋ฉด QueryDSL์ ๊ธฐ๋ณธ์ ์ผ๋ก com.mysema.query.Tuple์ด๋ผ๋ ๋ด๋ถ ํ์
์ ์ฌ์ฉํ๋ค.
QItem item = QItem.item;
List<Tuple> result = query.from(item).list(item.name, item.price);
for (Tuple tuple : result) {
tuple.get(item.name); // name
tuple.get(item.price); // price
}
๋น ์์ฑ
์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ์ํฐํฐ๊ฐ ์๋ ํน์ ๊ฐ์ฒด๋ก ๋ฐ๊ณ ์ถ์ผ๋ฉด ๋น ์์ฑ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ค. QueryDSL์ ๋ค์์ ์ฌ์ฉํด ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
- ํ๋กํผํฐ ์ ๊ทผ
- ํ๋ ์ง์ ์ ๊ทผ
- ์์ฑ์ ์ฌ์ฉ
public class ItemDTO {
private String username;
private String price;
// ์์ฑ์ ...
// Getter, Setter ...
}
ํ๋กํผํฐ ์ ๊ทผ
QItem item = QItem.item;
List<ItemDTO> result = query.from(item)
.list(Projections.bean(ItemDTO.class, item.name.as("username"), item.price));
Projections.bean()๋ฉ์๋๋ Setter๋ฅผ ์ฌ์ฉํด์ ๊ฐ์ ์ฑ์ด๋ค.
ํ๋ ์ง์ ์ ๊ทผ
QItem item = QItem.item;
List<ItemDTO> result = query.from(item)
.list(Projections.fields(ItemDTO.class, item.name.as("username"), item.price));
Projections.fields()๋ฉ์๋๋ ํ๋์ ์ง์ ์ ๊ทผํด์ ๊ฐ์ ์ฑ์ด๋ค.
์์ฑ์ ์ฌ์ฉ
QItem item = QItem.item;
List<ItemDTO> result = query.from(item)
.list(Projections.constructor(ItemDTO.class, item.name, item.price));
์์ฑ์๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ์ง์ ํ ํ๋ก์ ์
๊ณผ ํ๋ผ๋ฏธํฐ ์์๊ฐ ๊ฐ์ ์์ฑ์๊ฐ ํ์ํ๋ค.
DISTINCT distinct๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ๋ค.
query.distinct().from(item)...
10.4.10 ์์ , ์ญ์ ๋ฐฐ์น ์ฟผ๋ฆฌ
QuertDSL๋ ์์ , ์ญ์ ๊ฐ์ ๋ฐฐ์น ์ฟผ๋ฆฌ๋ฅผ ์ง์ํ๋ค. JPQL ๋ฐฐ์น ์ฟผ๋ฆฌ์ ๊ฐ์ด ์์์ฑ ์ปจํ ์คํธ๋ฅผ ๋ฌด์ํ๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ง์ ์ฟผ๋ฆฌํ๋ค๋ ์ ์ ์ ์ํ์.
์์ ๋ฐฐ์น ์ฟผ๋ฆฌ
QItem item = QItem.item;
JPAUpdateClause updateClause = new JPAUpdateClause(em, item);
long count = updateClause.where(item.name.eq("์๊ณจ๊ฐ๋ฐ์์ JPA์ฑ
"))
.set(item.price, item.price.add(100))
.execute();
์ญ์ ๋ฐฐ์น ์ฟผ๋ฆฌ
QItem item = QItem.item;
JPADeleteClause deleteClause = new JPADeleteClause(em, item);
long count = deleteClause.where(item.name.eq("์๊ณจ๊ฐ๋ฐ์์ JPA์ฑ
")).execute();
10.4.11 ๋์ ์ฟผ๋ฆฌ
com.mysema.query.BooleanBuilder๋ฅผ ์ฌ์ฉํ๋ฉด ํน์ ์กฐ๊ฑด์ ๋ฐ๋ฅธ ๋์ ์ฟผ๋ฆฌ๋ฅผ ํธ๋ฆฌํ๊ฒ ์์ฑํ ์ ์๋ค.
SearchParam param = new SearchParam();
param.setName("์๊ณจ๊ฐ๋ฐ์");
param.setPrice(10000);
QItem item = QItem.item;
BooleanBuilder builder = new BooleanBuilder();
if (StringUtils.hasText(param.getName())) {
builder.and(item.name.contains(param.getName()));
}
if (param.getPrice != null) {
builder.and(item.price.gt(param.getPrice()));
}
List<Item> result = query.from(item).where(builder).list(item);
10.4.12 ๋ฉ์๋ ์์
๋ฉ์๋ ์์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ์ฟผ๋ฆฌ ํ์ ์ ๊ฒ์ ์กฐ๊ฑด์ ์ง์ ์ ์ํ ์ ์๋ค.
๊ฒ์ ์กฐ๊ฑด ์ ์
public class ItemExpression {
@QueryDelegate(Item.class)
public static BooleanExpression isExpensive(QItem item, Integer price) {
return item.price.gt(price);
}
}
- ๋ฉ์๋ ์์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ค๋ฉด ์ ์ (static) ๋ฉ์๋๋ฅผ ๋ง๋ค๊ณ @QueryDelegate ์ด๋ ธํ ์ด์ ์ ์์ฑ์ผ๋ก ์ด ๊ธฐ๋ฅ์ ์ ์ฉํ ์ํฐํฐ๋ฅผ ์ง์ ํ๋ค.
- ์ ์ ๋ฉ์๋์ ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์๋ ๋์ ์ํฐํฐ์ ์ฟผ๋ฆฌ ํ์ (Q)์ ์ง์ ํ๊ณ ๋๋จธ์ง๋ ํ์ํ ํ๋ผ๋ฏธํฐ๋ฅผ ์ ์ํ๋ค.
์ฟผ๋ฆฌ ํ์
(Q)์ ์์ฑ๋ ๊ฒฐ๊ณผ
QItem์ ์์ฑ๋ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ๋ค.
public class QItem extends EntityPathBase<ITem> {
...
public com.mysema.query.types.expr.BooleanExpression isExpensive(Integer price) {
return ItemExpression.isExpensive(this, price);
}
}
์ฌ์ฉ
query.from(item).where(item.isExpensive(30000)).list(item);
ํ์ํ๋ค๋ฉด String, Date ๊ฐ์ ์๋ฐ ๊ธฐ๋ณธ ๋ด์ฅ ํ์ ์๋ ๋ฉ์๋ ์์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๋ค.
@QueryDelegate(String.class)
public static BooleanExpression isHelloStart(StringPath stringPath) {
return stringPath.startsWith("Hello");
}
10.5 ๋ค์ดํฐ๋ธ SQL
JPQL์ ํ์ค SQL์ด ์ง์ํ๋ ๋๋ถ๋ถ์ ๋ฌธ๋ฒ๊ณผ SQL ํจ์๋ค์ ์ง์ํ์ง๋ง ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ข ์์ ์ธ ๊ธฐ๋ฅ์ ์ง์ํ์ง ์๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ฒ๋ค์ด๋ค.
- ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ง ์ง์ํ๋ ํจ์, ๋ฌธ๋ฒ, SQL์ฟผ๋ฆฌ ํํธ
- ์ธ๋ผ์ธ๋ทฐ(From ์ ์์ ์ฌ์ฉํ๋ ์๋ธ์ฟผ๋ฆฌ), UNION, INTERSECT
- ์คํ ์ด๋ ํ๋ก์์
๋๋ก๋ ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ข ์์ ์ธ ๊ธฐ๋ฅ์ด ํ์ํ๋ค. JPA ๊ตฌํ์ฒด๋ค์ JPA ํ์ค๋ณด๋ค ๋ ๋ค์ํ ๋ฐฉ๋ฒ์ ์ง์ํ๋ค.
- ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ง ์ฌ์ฉํ๋ ํจ์
- JPQL์์ ๋ค์ดํฐ๋ธ SQL ํจ์๋ฅผ ํธ์ถํ ์ ์๋ค.(JPA 2.1)
- ํ์ด๋ฒ๋ค์ดํธ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐฉ์ธ์ ๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ข ์์ ์ธ ํจ์๋ค์ ์ ์ํด๋์๋ค. ๋ํ ์ง์ ํธ์ถํ ํจ์๋ฅผ ์ ์ํ ์๋ ์๋ค.
- ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ง ์ง์ํ๋ SQL ์ฟผ๋ฆฌ ํํธ
- ํ์ด๋ฒ๋ค์ดํธ๋ฅผ ํฌํจํ ๋ช๋ช JPA ๊ตฌํ์ฒด๋ค์ด ์ง์ํ๋ค.
- ์ธ๋ผ์ธ ๋ทฐ(From ์ ์์ ์ฌ์ฉํ๋ ์๋ธ์ฟผ๋ฆฌ), UNION, INTERSECT
- ํ์ด๋ฒ๋ค์ดํธ๋ ์ง์ํ์ง ์์ง๋ง ๋ช๋ช JPA ๊ตฌํ์ฒด๋ค์ด ์ง์ํ๋ค.
- ์คํ ์ด ํ๋ก์์
- JPQL์์ ์คํ ์ด๋ ํ๋ก์์ ๋ฅผ ํธ์ถํ ์ ์๋ค.(JPA 2.1)
- ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ง ์ง์ํ๋ ๋ฌธ๋ฒ
- ์ด๋๋ ๋ค์ดํฐ๋ธ SQL์ ์ฌ์ฉํด์ผ ํ๋ค.
๋ค์ํ ์ด์ ๋ก JPQL์ ์ฌ์ฉํ ์ ์์ ๋ JPA๋ SQL์ ์ง์ ์ฌ์ฉํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋๋ฐ ์ด๋ฅผ ๋ค์ดํฐ๋ธ SQL์ด๋ผ ํ๋ค.
๊ทธ๋ ๋ค๋ฉด ๋ค์ดํฐ๋ธ SQL๊ณผ JDBC API๋ฅผ ์ง์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ์ ์ฐจ์ด์ ์ ๋ฌด์์ผ๊น? ๋ค์ดํฐ๋ธ SQL์ ์ฌ์ฉํ๋ฉด ์ํฐํฐ๋ฅผ ์กฐํํ ์ ์๊ณ , JPA๊ฐ ์ง์ํ๋ ์์์ฑ ์ปจํ ์คํธ์ ๊ธฐ๋ฅ์ ๊ทธ๋๋ก ์ฌ์ฉํ ์ ์๋ค.
10.5.1 ๋ค์ดํฐ๋ธ SQL ์ฌ์ฉ
// ๊ฒฐ๊ณผ ํ์
์ ์
public Query createNativeQuery(String sqlString, Calss resultClass);
// ๊ฒฐ๊ณผ ํ์
์ ์ ์ํ ์ ์์ ๋
public Query createNativeQuery(String sqlString);
// ๊ฒฐ๊ณผ ๋งคํ ์ฌ์ฉ
public Query createNativeQuery(String sqlString, String resultSetMapping);
๋ด์ฉ ์๋ต ๐
10.6 ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ ์ฌํ
10.6.1 ๋ฒํฌ ์ฐ์ฐ
ํ ๋ฒ์ ์์ ํ๊ฑฐ๋ ์ญ์ ํ๋ ๋ฒํฌ ์ฐ์ฐ์ ์ฌ์ฉํ ์ ์๋ค.
UPDATE ๋ฒํฌ ์ฐ์ฐ
String sqlString;
"update Product p " +
"set p.price = p.price * 1.1 " +
"where p.stockAmount < :stockAmount";
int count = em.createQuery(sqlString)
.setParameter("stockAmount", 10)
.executeUpdate();
DELETE ๋ฒํฌ ์ฐ์ฐ
String sqlString;
"delete from Product p " +
"where p.price < :price";
int count = em.createQuery(sqlString)
.setParameter("price", 100)
.executeUpdate();
๋ฒํฌ ์ฐ์ฐ์ ์ฃผ์์
๋ฒํฌ ์ฐ์ฐ์ ์ฌ์ฉํ ๋๋ ์์์ฑ ์ปจํ
์คํธ๋ฅผ ๋ฌด์ํ๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ์ฟผ๋ฆฌํ๋ค๋ ์ ์ ์ฃผ์ํด์ผ ํ๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
- em.refresh() ์ฌ์ฉ
- ๋ฒํฌ ์ฐ์ฐ ๋จผ์ ์คํ
- ๋ฒํฌ ์ฐ์ฐ ์ํ ํ ์์์ฑ ์ปจํ ์คํธ ์ด๊ธฐํ
10.6.2 ์์์ฑ ์ปจํ ์คํธ์ JPQL
์ฟผ๋ฆฌ ํ ์์ ์ํ์ธ ๊ฒ๊ณผ ์๋ ๊ฒ
- ์์ ์ํ
- ์ํฐํฐ
- ์์ ์ํ ์๋ ๊ฒ
- ์๋ฒ ๋๋ ํ์
- ๋จ์ ํ๋ ์กฐํ
em.find() ๋์ ์์
- ์ํฐํฐ๋ฅผ ์์์ฑ ์ปจํ ์คํธ์์ ๋จผ์ ์ฐพ๊ณ
- ์์ผ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฐพ๋๋ค.
JPQL ๋์ ์์
- ์ต์ด ์กฐํ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํ
- ์ํฐํฐ๋ฅผ ์์์ฑ ์ปจํ ์คํธ์ ๋ฑ๋ก
- ๋ ๋ฒ์งธ ์กฐํ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํ
- ์์์ฑ ์ปจํ ์คํธ์ ๊ฐ์ ์ํฐํฐ ๋ฐ๊ฒฌ
- ์๋ก ๊ฒ์ํ ์ํฐํฐ๋ ๋ฒ๋ฆฌ๊ณ ์์์ฑ ์ปจํ ์คํธ์ ์๋ ๊ธฐ์กด ์ํฐํฐ ๋ฐํ
JPQL ํน์ง ์ ๋ฆฌ
- JPQL์ ํญ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์กฐํํ๋ค.
- JPQL๋ก ์กฐํํ ์ํฐํฐ๋ ์์ ์ํ๋ค.
- ์์์ฑ ์ปจํ ์คํธ์ ์ด๋ฏธ ์กด์ฌํ๋ ์ํฐํฐ๊ฐ ์์ผ๋ฉด ๊ธฐ์กด ์ํฐํฐ๋ฅผ ๋ฐํํ๋ค.
10.6.3 JPQL๊ณผ ํ๋ฌ์ ๋ชจ๋
ํ๋ฌ์ ๋ชจ๋
em.setFlushMode(FlushModeType.AUTO); // ์ปค๋ฐ, ์ฟผ๋ฆฌ ์คํ ์ ํ๋ฌ์ (๊ธฐ๋ณธ๊ฐ)
em.setFlushMode(FlushModeType.COMMIT); // ์ปค๋ฐ์์๋ง ํ๋ฌ์
- JPQL์ ์์์ฑ ์ปจํ ์คํธ์ ์๋ ๋ฐ์ดํฐ๋ฅผ ๊ณ ๋ คํ์ง ์๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ค.
- ๋ฐ๋ผ์ JPQL์ ์คํํ๊ธฐ ์ ์ ํ๋ฌ์๋ฅผ ํตํด ์์์ฑ ์ปจํ ์คํธ์ ๋ด์ฉ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ํด์ผ ํ๋ค.
10.7 ์ ๋ฆฌ
- JPQL์ SQL์ ์ถ์ํํด์ ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ธฐ์ ์ ์์กดํ์ง ์๋๋ค.
- Criteria๋ QueryDSL์ JPQL์ ๋ง๋ค์ด์ฃผ๋ ๋น๋ ์ญํ ์ ํ ๋ฟ์ด๋ฏ๋ก ํต์ฌ์ JPQL์ ์ ์์์ผ ํ๋ค.
- Criteria๋ QueryDSL์ ์ฌ์ฉํ๋ฉด ๋์ ์ผ๋ก ๋ณํ๋ ์ฟผ๋ฆฌ๋ฅผ ํธ๋ฆฌํ๊ฒ ์์ฑํ ์ ์๋ค.
- Criteria๋ JPA๊ฐ ๊ณต์ ์ง์ํ๋ ๊ธฐ๋ฅ์ด์ง๋ง ๋ถํธํ๋ค. QueryDSL์ JPA๊ฐ ๊ณต์ ์ง์ํ๋ ๊ธฐ๋ฅ์ ์๋์ง๋ง ํธ๋ฆฌํ๋ค.
- JPA๋ ๋ค์ดํฐ๋ธ SQL์ ์ ๊ณตํ๋ฏ๋ก ์ง์ SQL์ ์ฌ์ฉํ ์ ์๋ค. ํ์ง๋ง ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ข ์์ ์ด ๋๋ฏ๋ก ์ต๋ํ ์ฌ์ฉ์ ์์ ํ์.
- JPQL์ ๋๋์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฑฐ๋ ์ญ์ ํ๋ ๋ฒํฌ ์ฐ์ฐ์ ์ง์ํ๋ค.
11. ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์
๋ด์ฉ ์๋ต ๐
12. ์คํ๋ง ๋ฐ์ดํฐ JPA
12.1 ์คํ๋ง ๋ฐ์ดํฐ JPA ์๊ฐ
์คํ๋ง ๋ฐ์ดํฐ JPA ๋ ํผ๋ฐ์ค ๋ฌธ์
- ์คํ๋ง ๋ฐ์ดํฐ JPA๋ ์คํ๋ง ํ๋ ์์ํฌ์์ JPA๋ฅผ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ์ง์ํ๋ ํ๋ก์ ํธ๋ค.
- CURD๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๊ณตํต ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ๋ค.
- ์ธํฐํ์ด์ค๋ง ์์ฑํ๋ฉด ์คํ ์์ ์ ๊ตฌํ ๊ฐ์ฒด๋ฅผ ๋์ ์ผ๋ก ์์ฑํด์ ์ฃผ์ ํด์ค๋ค.
- ๋ฐ๋ผ์ ๋ฐ์ดํฐ ์ ๊ทผ ๊ณ์ธต์ ๊ฐ๋ฐํ ๋ ๊ตฌํ ํด๋์ค ์์ด ์ธํฐํ์ด์ค๋ง ์์ฑํด๋ ๊ฐ๋ฐ์ ์๋ฃํ ์ ์๋ค.
12.2 ์คํ๋ง ๋ฐ์ดํฐ JPA ์ค์
ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
<!-- ์คํ๋ง ๋ฐ์ดํฐ JPA -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring-data-jpa.version}</version>
</dependency>
ํ๊ฒฝ ์ค์
<!-- xml -->
<!-- ๋ฆฌํฌ์งํ ๋ฆฌ ์ธํฐํ์ด์ค๋ฅผ ํด๋น ํจํค์ง์ ๊ทธ ํ์ ํจํค์ง์์ ๊ฒ์ํ๋ค. -->
<jpa:repositories base-package="jpabook.jpashop.repository" />
// java config class
@Configuration
@EnableJpaRepositories(basePackage = "jpabook.jpashop.repository")
public class AppConfig {}
12.3 ๊ณตํต ์ธํฐํ์ด์ค ๊ธฐ๋ฅ
์คํ๋ง ๋ฐ์ดํฐ JPA๋ ๊ฐ๋จํ CRUD ๊ธฐ๋ฅ์ ๊ณตํต์ผ๋ก ์ฒ๋ฆฌํ๋ JpaRepository
์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ๋ค.
// JpaRepository ์ธํฐํ์ด์ค
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {...}
// JpaRepository ์ธํฐํ์ด์ค ์ฌ์ฉ
public interface MemberRepository extends JpaRepository<Member, Long> {}
12.4 ์ฟผ๋ฆฌ ๋ฉ์๋ ๊ธฐ๋ฅ
์คํ๋ง ๋ฐ์ดํฐ JPA๊ฐ ์ ๊ณตํ๋ ์ฟผ๋ฆฌ ๋ฉ์๋ ๊ธฐ๋ฅ
- ๋ฉ์๋ ์ด๋ฆ์ผ๋ก ์ฟผ๋ฆฌ ์์ฑ
- ๋ฉ์๋ ์ด๋ฆ์ผ๋ก
JPA NamedQuery
ํธ์ถ @Query
์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํด์ ๋ฆฌํฌ์งํฐ๋ฆฌ ์ธํฐํ์ด์ค์ ์ฟผ๋ฆฌ ์ง์ ์ ์
12.4.1 ๋ฉ์๋ ์ด๋ฆ์ผ๋ก ์ฟผ๋ฆฌ ์์ฑ
// ์ด๋ฉ์ผ๊ณผ ์ด๋ฆ์ผ๋ก ํ์์ ์กฐํ ๋ฉ์๋๋ฅผ ์ ์
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByEmailAndName(String email, String name);
}
-- ์คํ๋ง ๋ฐ์ดํฐ JPA๊ฐ ๋ฉ์๋๋ฅผ ๋ถ์ํด์ JPQL์ ์์ฑํ๊ณ ์คํํ๋ค.
select m from Member m where m.email =?1 and m.name = ?2
๊ท์น
์คํ๋ง ๋ฐ์ดํฐ JPA ์ฟผ๋ฆฌ ์์ฑ ๊ธฐ๋ฅ
ํค์๋ | ์ | JPQL ์ |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12.4.2 JPA NamedQuery
์คํ๋ง ๋ฐ์ดํฐ JPA๋ ๋ฉ์๋ ์ด๋ฆ์ผ๋ก JPA Named ์ฟผ๋ฆฌ๋ฅผ ํธ์ถํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
// @NamedQuery๋ก Named ์ฟผ๋ฆฌ ์ ์
@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
public class Member {
...
}
// ๋๋ฉ์ธ ํด๋์ค + .(์ ) + ๋ฉ์๋ ์ด๋ฆ ์ผ๋ก Named ์ฟผ๋ฆฌ๋ฅผ ์ฐพ์ ์คํํ๋ค.
// Named ์ฟผ๋ฆฌ๊ฐ ์์ผ๋ฉด ๋ฉ์๋ ์ด๋ฆ์ผ๋ก ์ฟผ๋ฆฌ ์์ฑ ์ ๋ต์ ์ฌ์ฉํ๋ค.
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsername(String username);
}
12.4.3 @Query, ๋ฆฌํฌ์งํ ๋ฆฌ ๋ฉ์๋์ ์ฟผ๋ฆฌ ์ ์
๋ฆฌํฌ์งํ ๋ฆฌ ๋ฉ์๋์ ์ง์ ์ฟผ๋ฆฌ๋ฅผ ์ ์ํ๋ ค๋ฉด @Query
์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ๋ค.
// ๋ฉ์๋์ JPQL ์ฟผ๋ฆฌ ์์ฑ
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = ?1")
Member findByUsername(String username);
}
// JPA ๋ค์ดํฐ๋ธ SQL ์ง์
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("SELECT * FROM MEMBER FROM WHERE USERNAME = ?0", nativeQuery = true)
Member findByUsername(String username);
}
12.4.4 ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ
์คํ๋ง ๋ฐ์ดํฐ JPA๋ ์์น ๊ธฐ๋ฐ ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ๊ณผ ์ด๋ฆ ๊ธฐ๋ฐ ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ์ ์ง์ํ๋ค. ๊ธฐ๋ณธ๊ฐ์ ์์น ๊ธฐ๋ฐ์ด๋ค.
-- ์์น ๊ธฐ๋ฐ, ๊ธฐ๋ณธ๊ฐ
select m from Member m where m.username = ?1
-- ์ด๋ฆ ๊ธฐ๋ฐ
select m from Member m where m.username = :name
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = :name")
Member findByUsername(@Param("name") String username);
}
12.4.5 ๋ฒํฌ์ฑ ์์ ์ฟผ๋ฆฌ
์คํ๋ง ๋ฐ์ดํฐ JPA๋ฅผ ์ฌ์ฉํ ๋ฒํฌ์ฑ ์์ , ์ญ์ ์ฟผ๋ฆฌ๋ @Modifying
์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ๋ค.
@Modifying
@Query("update Product p set p.price = price * 1.1 where p.stockAmount < :stockAmount")
int bulkPriceUp(@Param("stockAmount") String stockAmount);
๋ฒํฌ์ฑ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๊ณ ๋์ ์์์ฑ ์ปจํ
์คํธ๋ฅผ ์ด๊ธฐํํ๊ณ ์ถ์ผ๋ฉด @Modifying(clearAutomatically = true)
์ต์
์ ์ค๋ค.
12.4.6 ๋ฐํ ํ์
์คํ๋ง ๋ฐ์ดํฐ JPA๋ ์ ์ฐํ ๋ฐํ ํ์ ์ ์ง์ํ๋ค. ํ ๊ฑด ์ด์์ด๋ฉด ์ปฌ๋ ์ ์ ์ฌ์ฉํ๊ณ , ๋จ๊ฑด์ด๋ฉด ๋ฐํ ํ์ ์ ์ง์ ํ๋ค.
List<Member> findByName(String name); // ์ปฌ๋ ์
Member findByEmail(String email); // ๋จ๊ฑด
์กฐํ ๊ฒฐ๊ณผ๊ฐ ์์ ์
- ์ปฌ๋ ์ : ๋น ์ปฌ๋ ์ ๋ฐํ
- ๋จ๊ฑด:
null
๋ฐํ
์กฐํ ๊ฒฐ๊ณผ 1๊ฑด
- ์ปฌ๋ ์ : 1๊ฑด์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง ์ปฌ๋ ์ ๋ฐํ
- ๋จ๊ฑด: ๋ฐ์ดํฐ ๋ฐํ
์กฐํ ๊ฒฐ๊ณผ 2๊ฑด ์ด์
- ์ปฌ๋ ์ : 2๊ฑด ์ด์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง ์ปฌ๋ ์ ๋ฐํ
- ๋จ๊ฑด: ์์ธ ๋ฐ์
12.4.7 ํ์ด์ง๊ณผ ์ ๋ ฌ
์คํ๋ง ๋ฐ์ดํฐ JPA๋ ์ฟผ๋ฆฌ ๋ฉ์๋์ ํ์ด์ง๊ณผ ์ ๋ ฌ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๋๋ก 2๊ฐ์ง ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๊ณตํ๋ค.
org.springframework.data.domain.Sort
: ์ ๋ ฌ๊ธฐ๋ฅorg.springframework.data.domain.Pageable
: ํ์ด์ง ๊ธฐ๋ฅ(๋ด๋ถ์ Sort ํฌํจ)
ํ์ด์ง๊ณผ ์ ๋ ฌ ์ฌ์ฉ ์
// count ์ฟผ๋ฆฌ ์ฌ์ฉ
Page<Member> findByName(String name, Pageable pageable);
// count ์ฟผ๋ฆฌ ์ฌ์ฉ ์ ํจ
// total์ ์ ์ธํ Page์์ ์ ๊ณตํ๋ ์ธํฐํ์ด์ค ์ฌ์ฉ ๊ฐ๋ฅ
Slice<Member> findByName(String name, Pageable pageable);
// count ์ฟผ๋ฆฌ ์ฌ์ฉ ์ ํจ
List<Member> findByName(String name, Pageable pageable);
// ์ ๋ ฌ
List<Member> findByName(String name, Sort sort);
// ์ฌ์ฉ ๋ฐฉ๋ฒ
PageRequest pageRequest = new PageRequest(0, 10, new Sort(Direction.DESC, "name"));
Page<Member> result = memberRepository.findByName("๊น", pageRequest);
Slice ์ธํฐํ์ด์ค
public interface Slice<T> extends Streamable<T> {
int getNumber(); // ํ์ฌ ํ์ด์ง
int getSize(); //ํ์ด์ง ํฌ๊ธฐ
int getNumberOfElements(); // ํ์ฌ ํ์ด์ง์ ์กฐํํ ๋ฐ์ดํฐ ๊ฐ์
List<T> getContent(); // ํ์ฌ ํ์ด์ง์ ์กฐํํ ๋ฐ์ดํฐ
boolean hasContent(); // ํ์ฌ ํ์ด์ง์ ๋ฐ์ดํฐ๊ฐ ์๋์ง ์ฌ๋ถ
Sort getSort(); // ์ ๋ ฌ ์ฌ๋ถ
boolean isFirst(); // ์ฒซ ๋ฒ์งธ ํ์ด์ง์ธ์ง ์ฌ๋ถ
boolean isLast(); // ๋ง์ง๋ง ํ์ด์ง์ธ์ง ์ฌ๋ถ
boolean hasNext(); // ๋ค์ ํ์ด์ง๊ฐ ์๋์ง ์ฌ๋ถ
boolean hasPrevious(); // ์ด์ ํ์ด์ง๊ฐ ์๋์ง ์ฌ๋ถ
}
Page ์ธํฐํ์ด์ค
public interface Page<T> extends Slice<T> {
static <T> Page<T> empty() {
return empty(Pageable.unpaged());
}
static <T> Page<T> empty(Pageable pageable) {
return new PageImpl<>(Collections.emptyList(), pageable, 0);
}
int getTotalPages(); // ์ ์ฒด ํ์ด์ง ๊ฐ์
long getTotalElements(); // ์ ์ฒด ๋ฐ์ดํฐ ๊ฐ์
<U> Page<U> map(Function<? super T, ? extends U> converter);
}
12.4.8 ํํธ
JPA
์ฟผ๋ฆฌ ํํธ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด @QueryHint
์ด๋
ธ์ํฐ์
์ ์ฌ์ฉํ๋ค. ์ด๊ฒ์ SQL
ํํธ๊ฐ ์๋๋ผ JPA
๊ตฌํ์ฒด์๊ฒ ์ ๊ณตํ๋ ํํธ๋ค.
@QueryHints(value = {
@QueryHint(name = "org.hibernate.readOnly", value = true)
})
Page<Member> findByName(String name, Pageable pageable);
12.5 ๋ช ์ธ
์คํ๋ง ๋ฐ์ดํฐ JPA๋ JPA Criteria
๋ก ๋ช
์ธ(SPECIFICATION
)์ ์ฌ์ฉํ ์ ์๋๋ก ์ง์ํ๋ค.
์คํ๋ง ๋ฐ์ดํฐ JPA๋ฅผ ์ด์ฉํ ์กฐํ ๊ธฐ๋ฅ ์ฐธ๊ณ
12.6 ์ฌ์ฉ์ ์ ์ ๋ฆฌํฌ์งํ ๋ฆฌ ๊ตฌํ
์คํ๋ง ๋ฐ์ดํฐ JPA๋ก ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ๊ฐ๋ฐํ๋ฉด ์ธํฐํ์ด์ค๋ง ์ ์ํ๊ณ ๊ตฌํ์ฒด๋ ๋ง๋ค์ง ์๋๋ค. ํ์ง๋ง ๋ฉ์๋๋ฅผ ์ง์ ๊ตฌํํด์ผ ํ ๋๋ ์๋ค.
์ฌ์ฉ์ ์ ์ ์ธํฐํ์ด์ค ์์ฑ
// ์ธํฐํ์ด์ค ์ด๋ฆ์ ์์ ๋กญ๊ฒ ์์ฑ ๊ฐ๋ฅ
public interface MemberRepositoryCustom {
public List<Member> findMemberCustom();
}
์ฌ์ฉ์ ์ ์ ๊ตฌํ ํด๋์ค
/**
* ํด๋์ค ์ด๋ฆ์ ๋ฆฌํฌ์งํ ๋ฆฌ ์ธํฐํ์ด์ค ์ด๋ฆ + Impl๋ก ์ง์ด์ผ ํ๋ค.
* ๊ทธ๋ฌ๋ฉด ์คํ๋ง ๋ฐ์ดํฐ JPA๊ฐ ์ฌ์ฉ์ ์ ์ ๊ตฌํ ํด๋์ค๋ก ์ธ์ํ๋ค.
*/
public class MemberRepositoryImpl implements c {
@Override
public List<Member> findMemberCustom() {
// ์ฌ์ฉ์ ์ ์ ๊ตฌํ...
}
}
์ฌ์ฉ์ ์ ์ ์ธํฐํ์ด์ค ์์
public interface MemberRepository
extends JpaRepository<Member, Long>, MemberRepositoryCustom {}
์ฌ์ฉ์ ์ ์ ๊ตฌํ ํด๋์ค ์ด๋ฆ ๊ท์น ๋ณ๊ฒฝ
<jpa:repositories base-package="jpabook.jpashop.repository"
repository-impl-postfix="Impl" />
@Configuration
@EnableJpaRepositories(
basePackage = "jpabook.jpashop.repository",
repositoryImplPostfix = "Impl"
)
public class AppConfig {}
12.7 Web ํ์ฅ
์คํ๋ง ๋ฐ์ดํฐ ํ๋ก์ ํธ๋ ์คํ๋ง MVC์์ ์ฌ์ฉํ ์ ์๋ ํธ๋ฆฌํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
12.7.1 ์ค์
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration">
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport // ์ถ๊ฐ
public class WebAppConfig {...}
12.7.2 ๋๋ฉ์ธ ํด๋์ค ์ปจ๋ฒํฐ ๊ธฐ๋ฅ
๋๋ฉ์ธ ํด๋์ค ์ปจ๋ฒํฐ๋ HTTP ํ๋ผ๋ฏธํฐ๋ก ๋์ด์จ ์ํฐํฐ์ ์์ด๋๋ก ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ์ฐพ์์ ๋ฐ์ธ๋ฉ ํด์ค๋ค.
// URL: /member/memberUpdateForm?id=1
@Controller
public class MemberController {
@RequestMepping("member/memberUpdateForm")
public String memberUpdateForm(@RequestParam("id") Member member, Model model) {
model.addAttribute("member", member);
return "member/memberSaveForm";
}
}
โ ์ฃผ์
๋๋ฉ์ธ ํด๋์ค ์ปจ๋ฒํฐ๋ฅผ ํตํด ๋์ด์จ ์ํฐํฐ๋ฅผ ์ปจํธ๋กค๋ฌ์์ ์ง์ ์์ ํด๋ ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์๋ ๋ฐ์๋์ง ์๋๋ค. ์ด๊ฒ์ ์์์ฑ ์ปจํ
์คํธ์ ๋์ ๋ฐฉ์๊ณผ ๊ด๋ จ์ด ์๋ค. ์น ์ ํ๋ฆฌ์ผ์ด์
์์ ์์์ฑ ์ปจํ
์คํธ์ ๋์ ๋ฐฉ์๊ณผ OSIV์ ๊ดํ ๋ด์ฉ์ ์์์ผ ํ๋ค.
12.7.3 ํ์ด์ง๊ณผ ์ ๋ ฌ ๊ธฐ๋ฅ
์คํ๋ง ๋ฐ์ดํฐ๊ฐ ์ ๊ณตํ๋ ํ์ด์ง๊ณผ ์ ๋ ฌ ๊ธฐ๋ฅ์ ์คํ๋ง MVC์์ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
@Controller
public class MemberController {
// ํ๋ผ๋ฏธํฐ๋ก Pageable๋ฅผ ๋ฐ๋๋ค
@RequestMepping("members", method = RequestMethod.GET)
public String list(Pageable pageable, Model model) {
Page<Member> page = memberService.findMembers(pageable);
model.addAttribute("member", page.getContent());
return "member/memberList";
}
}
// ์) /members?page=0&size=20&sort=name,desc&sort=address.city
์์ฒญ ํ๋ผ๋ฏธํฐ
- page: ํ์ฌ ํ์ด์ง, 0๋ถํฐ ์์
- size: ํ ํ์ด์ง์ ๋ ธ์ถํ ๋ฐ์ดํฐ ๊ฑด์
- sort: ์ ๋ ฌ ์กฐ๊ฑด์ ์ ์
์ฐธ๊ณ
ํ์ด์ง๋ฅผ 1๋ถํฐ ์์ํ๊ณ ์ถ์ผ๋ฉด PageableHandlerMethodArgumentResolver๋ฅผ ์คํ๋ง ๋น์ผ๋ก ์ง์ ๋ฑ๋กํ๊ณ setOneIndexedParameters๋ฅผ true๋ก ์ค์ ํ๋ฉด ๋๋ค.
์ ๋์ฌ
์ฌ์ฉํด์ผ ํ ํ์ด์ง ์ ๋ณด๊ฐ ๋ ์ด์์ด๋ฉด ์ ๋์ฌ๋ฅผ ์ฌ์ฉํด์ ๊ตฌ๋ถํ ์ ์๋ค.
public String list(
@Qualifier("member") pageable memberPageable,
@Qualifier("order") pageable orderPageable.
...
)
// ์) /members?member_page=0&order_page=1
๊ธฐ๋ณธ๊ฐ
Pageable
์ ๊ธฐ๋ณธ๊ฐ์ page=0
, size=20
์ด๋ค. ๋ง์ฝ ๊ธฐ๋ณธ๊ฐ์ ๋ณ๊ฒฝํ๊ณ ์ถ์ผ๋ฉด @PageableDefault
์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ๋ค.
@RequestMepping("members_page", method = RequestMethod.GET)
public String list(@PageableDefault(size = 12, sort = "name",
direction = Sort.Direction.DESC) Pageable pageable {
...
}
12.8 ์คํ๋ง ๋ฐ์ดํฐ JPA๊ฐ ์ฌ์ฉํ๋ ๊ตฌํ์ฒด
๋ด์ฉ ์๋ต ๐
12.9 JPA ์ต์ ์ ์ฉ
๋ด์ฉ ์๋ต ๐
12.10 ์คํ๋ง ๋ฐ์ดํฐ JPA์ QueryDSL ํตํฉ
์คํ๋ง ๋ฐ์ดํฐ JPA๋ 2๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก QueryDSL์ ์ง์ํ๋ค.
12.10.1 QueryDslPredicateExecutor ์ฌ์ฉ
// ๋ ํฌ์งํ ๋ฆฌ์์ QueryDslPredicateExecutor ์์
public interface ItemRepository
extends JpaRepository<Item, Long>, QueryDslPredicateExecutor<Item> {...}
// ์ฌ์ฉ
QItem item = QItem.item;
Iterable<Item> result = itemRepository.findAll(
item.name.contains("์ฅ๋๊ฐ").and(item.price.between(10000, 20000))
);
โ ๏ธ๊ฒฝ๊ณ
QueryDslPredicateExecutor๋ ๊ธฐ๋ฅ์ ํ๊ณ๊ฐ ์๋ค. ์๋ฅผ ๋ค์ด join, fetch๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
12.10.2 QueryDslRepositorySupport ์ฌ์ฉ
QueryDSL
์ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ค๋ฉด JPAQuery
๊ฐ์ฒด๋ฅผ ์ง์ ์์ฑํด์ ์ฌ์ฉํ๋ฉด ๋๋ค. ์คํ๋ง ๋ฐ์ดํฐ JPA๊ฐ ์ ๊ณตํ๋ QueryDslRepositorySupport
๋ฅผ ์์ ๋ฐ์ ์ฌ์ฉํ๋ฉด ํธ๋ฆฌํ๊ฒ QueryDSL
์ ์ฌ์ฉํ ์ ์๋ค.
// ์ฌ์ฉ์ ์ ์ ๋ ํฌ์งํ ๋ฆฌ
public interface CustomOrderRepository {
public List<Order> search(OrderSearch orderSearch);
}
public class OrderRepositoryImpl extends QueryDslRepositorySupport
implements CustomOrderRepository {
public OrderRepositoryImpl() {
super(Order.class);
}
@Override
public List<Order> search(OrderSearch orderSearch) {
QOrder order = QOrder.order;
QMember member = QMember.member;
JPAQuery query = from(order);
// ๊ฒ์ ์กฐ๊ฑด ์ฟผ๋ฆฌ...
return query.list(order);
}
}
13. ์น ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ์์์ฑ ๊ด๋ฆฌ
์ปจํ
์ด๋ ํ๊ฒฝ์์ JPA๊ฐ ๋์ํ๋ ๋ฐฉ์์ ์ดํดํ๊ณ , ์ปจํ
์ด๋ ํ๊ฒฝ์์ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ๊ฐ๋ฐํ ๋ ๋ฐ์ํ ์ ์๋ ๋ฌธ์ ์ ๊ณผ ํด๊ฒฐ๋ฐฉ์์ ์์๋ณธ๋ค.
13.1 ํธ๋์ญ์ ๋ฒ์์ ์์์ฑ ์ปจํ ์คํธ
์คํ๋ง์ด๋ J2EE ์ปจํ
์ด๋ ํ๊ฒฝ์์ JPA๋ฅผ ์ฌ์ฉํ๋ฉด ์ปจํ
์ด๋๊ฐ ์ ๊ณตํ๋ ์ ๋ต์ ๋ฐ๋ผ์ผ ํ๋ค.
13.1.1 ์คํ๋ง ์ปจํ ์ด๋์ ๊ธฐ๋ณธ ์ ๋ต
์คํ๋ง ์ปจํ ์ด๋๋ ํธ๋์ญ์ ๋ฒ์์ ์์์ฑ ์ปจํ ์คํธ ์ ๋ต์ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๋ค.
์คํ๋ง ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ณดํต ๋น์ฆ๋์ค ๋ก์ง์ ์์ํ๋ ์๋น์ค ๊ณ์ธต์ @Transactional
์ด๋
ธํ
์ด์
์ ์ ์ธํด์ ํธ๋์ญ์
์ ์์ํ๋ค.
ํธ๋ ์ญ์ ์ด ๊ฐ์ผ๋ฉด ๊ฐ์ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ฌ์ฉํ๋ค.
ํธ๋์ญ์ ์ด ๋ค๋ฅด๋ฉด ๋ค๋ฅธ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ฌ์ฉํ๋ค.
13.2 ์ค์์ ์ํ์ ์ง์ฐ ๋ก๋ฉ
์ปจํ ์ด๋ ํ๊ฒฝ์ ๊ธฐ๋ณธ ์ ๋ต์ธ ํธ๋์ญ์ ๋ฒ์์ ์์์ฑ ์ปจํ ์คํธ ์ ๋ต์ ์ฌ์ฉํ๋ฉด ํธ๋์ญ์ ์ด ์๋ ํ๋ฆฌ์ ํ ์ด์ ๊ณ์ธต์์ ์ํฐํฐ๋ ์ค์์ ์ํ๋ค. ๋ฐ๋ผ์ ๋ณ๊ฒฝ ๊ฐ์ง์ ์ง์ฐ ๋ก๋ฉ์ด ๋์ํ์ง ์๋๋ค.
์ค์์ ์ํ์์ ์ง์ฐ๋ก๋ฉ์ ์๋ํ๋ฉด org.hibernate.LazyInitializationException
์์ธ๊ฐ ๋ฐ์ํ๋ค.
์ค์์ ์ํ์ ์ง์ฐ ๋ก๋ฉ ํด๊ฒฐ ๋ฐฉ๋ฒ
- ๋ทฐ๊ฐ ํ์ํ ์ํฐํฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ก๋ฉํด๋๋ ๋ฐฉ๋ฒ
- ๊ธ๋ก๋ฒ ํ์น ์ ๋ต ์์
- JPQL ํ์น ์กฐ์ธ (fetch join)
- ๊ฐ์ ๋ก ์ด๊ธฐํ
OSIV(open session in view)
๋ฅผ ์ฌ์ฉํด์ ์ํฐํฐ๋ฅผ ์์ ์ํ๋ก ์ ์งํ๋ ๋ฐฉ๋ฒ
13.2.1 ๊ธ๋ก๋ฒ ํ์น ์ ๋ต ์์
์ฆ์ ๋ก๋ฉ ์ค์
@ManyToOne(fetch = FetchType.EAGER)
ํ์ง๋ง ์ฌ์ฉํ์ง ์๋ ์ํฐํฐ๋ฅผ ๋ก๋ฉํ๋ ๋จ์ ์ด ์๋ค. ๋ JPQL์์ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.(ํ์น ์กฐ์ธ์ผ๋ก ํด๊ฒฐ ๊ฐ๋ฅ)
13.2.2 JPQL ํ์น ์กฐ์ธ
-- JPQL fetch
select o from Order o join fetch o.member
๋ฌด๋ถ๋ณํ๊ฒ ์ฌ์ฉํ๋ฉด ํ๋ฉด์ ๋ง์ถ ๋ฆฌํฌ์งํ ๋ฆฌ ๋ฉ์๋๊ฐ ์ฆ๊ฐํ ์ ์๋ค. ๊ฒฐ๊ตญ ํ๋ฆฌ์ ํ
์ด์
๊ณ์ธต์ด ์๊ฒ ๋ชจ๋ฅด๊ฒ ๋ฐ์ดํฐ ์ ๊ทผ ๊ณ์ธต์ ์นจ๋ฒํ๊ฒ ๋๋ค.
13.2.3 ๊ฐ์ ๋ก ์ด๊ธฐํ
@Transactional
public Order findOrder(id) {
Order order = orderRepository.findOrder(id);
order.getMember().getName(); // ํ๋ก์ ๊ฐ์ ๋ก ์ด๊ธฐํ
return order;
}
ํ์ง๋ง ํ๋ก์๋ฅผ ์ด๊ธฐํํ๋ ์ญํ ์ ์๋น์ค ๊ณ์ธต์ด ๋ด๋นํ๋ฉด ๋ทฐ๊ฐ ํ์ํ ์ํฐํฐ์ ๋ฐ๋ผ ์๋น์ค ๊ณ์ธต์ ๋ก์ง์ ๋ณ๊ฒฝํด์ผ ํ๋ค. ํ๋ฆฌ์ ํ
์ด์
๊ณ์ธต์ด ์๊ฒ ๋ชจ๋ฅด๊ฒ ์๋น์ค ๊ณ์ธต์ ์นจ๋ฒํ๊ฒ ๋๋ค.
13.2.4 FACADE ๊ณ์ธต ์ถ๊ฐ
FACADE ๊ณ์ธต์ ์ญํ ๊ณผ ํน์ง
- ํ๋ ์ ํ ์ด์ ๊ณ์ธต๊ณผ ๋๋ฉ์ธ ๋ชจ๋ธ ๊ณ์ธต ๊ฐ์ ๋ ผ๋ฆฌ์ ์์กด์ฑ์ ๋ถ๋ฆฌํด์ค๋ค.
- ํ๋ฆฌ์ ํ ์ด์ ๊ณ์ธต์์ ํ์ํ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ด๊ธฐํํ๋ค.
- ์๋น์ค ๊ณ์ธต์ ํธ์ถํด์ ๋น์ฆ๋์ค ๋ก์ง์ ์คํํ๋ค.
- ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์ง์ ํธ์ถํด์ ๋ทฐ๊ฐ ์๊ตฌํ๋ ์ํฐํฐ๋ฅผ ์ฐพ๋๋ค.
class OrderFacade {
@Autowired
OrderService orderService;
public Order findOrder(id) {
Order order = orderService.findOrder(id);
order.getMember().getName(); // ํ๋ก์ ๊ฐ์ ๋ก ์ด๊ธฐํ
return order;
}
}
class OrderService {
public Order findOrder(id) {
return orderRepository.findOrder(id);
}
}
ํ์ง๋ง ์ค๊ฐ์ ๊ณ์ธต์ด ํ๋ ๋ ๋ผ์ด๋ค๊ฒ ๋๊ณ , ๊ฒฐ๊ตญ ๋ ๋ง์ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋ค. ๊ทธ๋ฆฌ๊ณ FACADE
์๋ ๋จ์ํ ์๋น์ค ๊ณ์ธต์ ํธ์ถ๋ง ํ๋ ์์ ์ฝ๋๊ฐ ์๋นํ ๋ง์ ๊ฒ์ด๋ค.
13.2.5 ์ค์์ ์ํ์ ์ง์ฐ ๋ก๋ฉ์ ๋ฌธ์ ์
๊ฒฐ๊ตญ ๋ชจ๋ ๋ฌธ์ ๋ ์ํฐํฐ๊ฐ ํ๋ฆฌ์ ํ
์ด์
๊ณ์ธต์์ ์ค์์ ์ํ์ด๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ๋ค.
13.3 OSIV
OSIV(Open Session In View)๋ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ๋ทฐ๊น์ง ์ด์ด๋๋ค๋ ๋ป์ด๋ค.
์ฐธ๊ณ
OSIV๋ ํ์ด๋ฒ๋ค์ดํธ์์ ์ฌ์ฉํ๋ ์ฉ์ด๋ค. JPA์์๋ OEIV(Open EntityManager In View)๋ผ๊ณ ํ๋ค. ํ์ง๋ง ๊ด๋ก์ ๋ชจ๋ OSIV๋ก ๋ถ๋ฅธ๋ค.
13.3.1 ๊ณผ๊ฑฐ OSIV: ์์ฒญ ๋น ํธ๋์ญ์
ํด๋ผ์ด์ธํธ์ ์์ฒญ์ด ๋ค์ด์ค์๋ง์ ์๋ธ๋ฆฟ ํํฐ๋ ์คํ๋ง ์ธํฐ์ ํฐ์์ ํธ๋์ญ์ ์ ์์ํ๊ณ ์์ฒญ์ด ๋๋ ๋ ํธ๋์ญ์ ๋ ๋๋ด๋ ๊ฒ์ ์์ฒญ ๋น ํธ๋์ญ์ ๋ฐฉ์์ OSIV๋ผ ํ๋ค.
์์ฒญ ๋น ํธ๋์ญ์ ๋ฐฉ์์ OSIV ๋ฌธ์ ์
- ํ๋ฆฌ์ ํ ์ด์ ๊ณ์ธต์ด ์ํฐํฐ๋ฅผ ๋ณ๊ฒฝํ ์๋ ์๋ค.
- ์ํฐํฐ๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์์์ฑ ์ปจํ ์คํธ์ ๋ณ๊ฒฝ ๊ฐ์ง ๊ธฐ๋ฅ์ด ์๋ํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ํด ๋ฒ๋ฆฐ๋ค.
ํ๋ฆฌ์ ํ ์ด์ ๊ณ์ธต์์ ์ํฐํฐ๋ฅผ ์์ ํ์ง ๋ชปํ๊ฒ ๋ง๋ ๋ฐฉ๋ฒ
- ์ํฐํฐ๋ฅผ ์ฝ๊ธฐ ์ ์ฉ ์ธํฐํ์ด์ค๋ก ์ ๊ณต
- ์ฝ๊ธฐ ์ ์ฉ ๋ฉ์๋๋ง ๊ฐ์ง๊ณ ์๋๋ก ์ํฐํฐ ๋ํ
- DTO๋ง ๋ฐํ
ํ์ง๋ง ์ฝ๋๋์ด ์๋นํ ์ฆ๊ฐํ๋ ๋จ์ ์ด ์๋ค.
13.3.2 ์คํ๋ง OSIV: ๋น์ฆ๋์ค ๊ณ์ธต ํธ๋์ญ์
์คํ๋ง ํ๋ ์์ํฌ๊ฐ ์ ๊ณตํ๋ OSIV๋ ๋น์ฆ๋์ค ๊ณ์ธต์์ ํธ๋์ญ์ ์ ์ฌ์ฉํ๋ OSIV๋ค.
๋น์ฆ๋์ค ๊ณ์ธต ํธ๋์ญ์ OSIV ํน์ง
- ์์์ฑ ์ปจํ ์คํธ๋ฅผ ํ๋ฆฌ์ ํ ์ด์ ๊ณ์ธต๊น์ง ์ ์งํ๋ค.
- ํ๋ฆฌ์ ํ ์ด์ ๊ณ์ธต์๋ ํธ๋์ญ์ ์ด ์์ผ๋ฏ๋ก ์ํฐํฐ๋ฅผ ์์ ํ ์ ์๋ค.
- ํ๋ฆฌ์ ํ
์ด์
๊ณ์ธต์๋ ํธ๋์ญ์
์ด ์์ง๋ง ์ฝ์ ์๋ ์์ด์(ํธ๋์ญ์
์์ด ์ฝ๊ธฐ) ์ง์ฐ ๋ก๋ฉ์ ํ ์ ์๋ค.
์คํ๋ง OSIV ์ฃผ์์ฌํญ
ํ๋ฆฌ์ ํ
์ด์
๊ณ์ธต์์ ์ํฐํฐ๋ฅผ ์์ ํ ์งํ์ ํธ๋์ญ์
์ ์์ํ๋ ์๋น์ค ๊ณ์ธต์ ํธ์ถํ๋ฉด ์์์ฑ ์ปจํ
์คํธ๋ฅผ ํ๋ฌ์ํ๋ค. ์ด ๋ ํ๋ฆฌ์ ํ
์ด์
๊ณ์ธต์์์ ์ํฐํฐ ์์ ์ฌํญ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
class MemberController {
public String viewMember(Long id) {
Member member = memberService.getMember(id);
member.setName("๋ณ๊ฒฝ"); // ๋ณ๊ฒฝ
memberService.biz(); // ๋น์ค๋์ค ๋ก์ง ํธ์ถ
return "view";
}
}
class MemberService {
@Transactional
public void biz() {
// ...๋น์ฆ๋์ค ๋ก์ง ์คํ
}
}
ํด๊ฒฐ๋ฐฉ๋ฒ
๋น์ฆ๋์ค ๋ก์ง์ ๋จผ์ ์ํํ๋ค.
class MemberController {
public String viewMember(Long id) {
memberService.biz(); // ๋น์ค๋์ค ๋ก์ง ๋จผ์ ํธ์ถ
Member member = memberService.getMember(id);
member.setName("๋ณ๊ฒฝ"); // ๋ณ๊ฒฝ์ ๋์ค์ ์ํ
return "view";
}
}
13.3.3 OSIV ์ ๋ฆฌ
์คํ๋ง OSIV์ ํน์ง
- OSIV๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ด ๋ค์ด์ฌ ๋ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์์ฑํด์ ์์ฒญ์ด ๋๋ ๋๊น์ง ๊ฐ์ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ ์งํ๋ค.
- ์ํฐํฐ ์์ ์ ํธ๋์ญ์ ์ด ์๋ ๊ณ์ธต์์๋ง ๋์ํ๋ค.
- ํธ๋์ญ์
์ด ์๋ ํ๋ฆฌ์ ํ
์ด์
๊ณ์ธต์ ์ง์ฐ ๋ก๋ฉ์ ํฌํจํด์ ์กฐํ๋ง ํ ์ ์๋ค.
์คํ๋ง OSIV์ ๋จ์
- OSIV๋ฅผ ์ ์ฉํ๋ฉด ๊ฐ์ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ฌ๋ฌ ํธ๋์ญ์ ์ด ๊ณต์ ํ ์ ์๋ค๋ ์ ์ ์ฃผ์ํด์ผ ํ๋ค. ํนํ ๋กค๋ฐฑ ์ฃผ์
- ํ๋ฆฌ์ ํ ์ด์ ๊ณ์ธต์์ ์ํฐํฐ๋ฅผ ์์ ํ ๋น์ฆ๋์ค ๋ก์ง์ ์ํํ๋ฉด ์ํฐํฐ ์์ ์ฌํญ์ด ๋ฐ์๋ ์ ์๋ค.
- ์ง์ฐ ๋ก๋ฉ์ ์ํ ์ฑ๋ฅ ํ๋์์ ํ์ธํด์ผ ํ ๋ถ๋ถ์ด ๋๋ค.
OSIV vs FACADE vs DTO
FACADE๋ DTO๋ OSIV๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋น๊ตํด์ ์ง๋ฃจํ ์ฝ๋๋ฅผ ๋ง์ด ์์ฑํด์ผ ํ๋ค.
OSIV๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ๋ง๋ฅ์ ์๋๋ค
๋ณต์กํ ํ๋ฉด์์๋ ์ฒ์๋ถํฐ ํ๋ฉด์ ๋ง๋ JPQL๋ก ํ์ํ ๋ฐ์ดํฐ๋ค๋ง ์กฐํํด์ DTO๋ก ๋ฐํํ๋ ๊ฒ์ด ๋ ๋์ ํด๊ฒฐ์ฑ
์ผ ์ ์๋ค.
OSIV๋ ๊ฐ์ JVM์ ๋ฒ์ด๋ ์๊ฒฉ ์ํฉ์์๋ ์ฌ์ฉํ ์ ์๋ค
์๊ฒฉ์ง์์ ์ํฐํฐ๋ฅผ ์ง์ฐ ๋ก๋ฉํ๋ ๊ฒ์ ๋ถ๊ฐ๋ฅํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ํฐํฐ๋ ์๊ฐ๋ณด๋ค ์์ฃผ ๋ณ๊ฒฝ๋๋ค. ๋ฐ๋ผ์ ์ธ๋ถ API๋ ์ํฐํฐ๋ฅผ ์ง์ ๋
ธ์ถํ๊ธฐ๋ณด๋ค๋ ์์ถฉ ์ญํ ์ ํ ์ ์๋ DTO๋ก ๋ณํํด์ ๋
ธ์ถํ๋ ๊ฒ์ด ์์ ํ๋ค.
13.4 ๋๋ฌด ์๊ฒฉํ ๊ณ์ธต
OSIV๋ฅผ ์ฌ์ฉํ๋ฉด ์์์ฑ ์ปจํ ์คํธ๊ฐ ํ๋ฆฌ์ ํ ์ด์ ๊ณ์ธต๊น์ง ์ด์์์ผ๋ฏ๋ก ๋ฏธ๋ฆฌ ์ด๊ธฐํํ ํ์๊ฐ ์๋ค. ๋ฐ๋ผ์ ๋จ์ํ ์ํฐํฐ ์กฐํ๋ ์ปจํธ๋กค๋ฌ์์ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์ง์ ํธ์ถํด๋ ๋ฌธ์ ๊ฐ ์๋ค.
14. ์ปฌ๋ ์ ๊ณผ ๋ถ๊ฐ ๊ธฐ๋ฅ
์ด ์ฅ์์ ๋ค๋ฃฐ ๋ด์ฉ
- ์ปฌ๋ ์ : ๋ค์ํ ์ปฌ๋ ์ ๊ณผ ํน์ง์ ์ค๋ช ํ๋ค.
- ์ปจ๋ฒํฐ: ์ํฐํฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ณํํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๋ค.
- ๋ฆฌ์ค๋: ์ํฐํฐ์์ ๋ฐ์ํ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ค.
- ์ํฐํฐ ๊ทธ๋ํ: ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ์ฐ๊ด๋ ์ํฐํฐ๋ค์ ์ ํํด์ ํจ๊ป ์กฐํํ๋ค.
14. ์ปฌ๋ ์
14.1.1 List + @OrderColumn
List
์ธํฐํ์ด์ค์ @OrderColumn
์ ์ถ๊ฐํ๋ฉด ์์๊ฐ ์๋ ํน์ํ ์ปฌ๋ ์
์ผ๋ก ์ธ์ํ๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์ ๊ฐ์ ์ ์ฅํด์ ์กฐํํ ๋ ์ฌ์ฉํ๋ค.
@Entity
public class Board {
...
@OneToMany(mappedBy = "board")
@OrderColumn(name = "POSITION") // ์ถ๊ฐ
private List<Comment> comments = new ArrayList<Comment>();
}
@Entity
public class Comment {
private String comment;
@ManyToOne
@JoinColumn(name = "BOARD_ID")
private Board board;
}
์์๊ฐ ์๋ ์ปฌ๋ ์ ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์ ๊ฐ๋ ํจ๊ป ๊ด๋ฆฌํ๋ค.
@OrderColumn์ ๋จ์
@OrderColumn์ ๋ค์๊ณผ ๊ฐ์ ๋จ์ ๋ค ๋๋ฌธ์ ์ค๋ฌด์์ ์ ์ฌ์ฉํ์ง ์๋๋ค.
- @OrderColumn์ Board ์ํฐํฐ์ ๋งคํํ๋ฏ๋ก Comment๋ POSITION์ ๊ฐ์ ์ ์ ์๋ค.
- Comment๋ฅผ INSERTํ ๋ Board.comments์ ์์น ๊ฐ์ ์ฌ์ฉํด์ POSITION์ ๊ฐ์ UPDATE ํ๋ SQL์ด ์ถ๊ฐ๋ก ๋ฐ์ํ๋ค.
- List๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์ฐ๊ด๋ ์์น ๊ฐ์ ๋ค ๋ณ๊ฒฝํด์ผ ํ๋ค. ์๋ฅผ ๋ค์ด ๋๊ธ 2๋ฅผ ์ญ์ ํ๋ฉด ๋๊ธ 3, ๋๊ธ 4์ POSITION ๊ฐ์ ๋ณ๊ฒฝํ๋ UPDATE SQL์ด 2๋ฒ ์ถ๊ฐ๋ก ๋ฐ์ํ๋ค.
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฐ์ ๋ก ์ญ์ ํ๊ณ ๋ค๋ฅธ ๋๊ธ ๋ค์ POSITION ๊ฐ์ ์์ ํ์ง ์์ผ๋ฉด ์ปฌ๋ ์
์ ์ํํ ๋ NullPointException์ด ๋ฐ์ํ๋ค.
14.1.2 @OrderBy
@OrderBy๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ORDER BY์ ์ ์ฌ์ฉํด์ ์ปฌ๋ ์ ์ ์ ๋ ฌํ๋ค.
@Entity
public class Team {
...
@OneToMany(mappedBy = "team")
@OrderBy("username desc, id asc")
private Set<Member> members = new HashSet<Member>();
...
}
์ด๊ธฐํํ ๋ ์คํ๋ SQL๋ฅผ ๋ณด๋ฉด ORDER BY๊ฐ ์ฌ์ฉ๋๋ค.
SELECT M.* FROM MEMBER M
WHERE M.TEAM_ID = ?
ORDER BY M.MEMBER_NAME DESC, M.ID ASC
์ฐธ๊ณ
ํ์ด๋ฒ๋ค์ดํธ๋ Set
์ @OrderBy
๋ฅผ ์ ์ฉํด์ ๊ฒฐ๊ณผ๋ฅผ ์กฐํํ๋ฉด ์์๋ฅผ ์ ์งํ๊ธฐ ์ํด LinkedHashSet
์ ๋ด๋ถ์์ ์ฌ์ฉํ๋ค.
14.2 @Converter
์ปจ๋ฒํฐ๋ฅผ ์ฌ์ฉํ๋ฉด ์ํฐํฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ณํํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ ์ ์๋ค.
AttributeConverter๋ฅผ ์ด์ฉํ ๋ฐธ๋ฅ ๋งคํ ์ฒ๋ฆฌ ์ฐธ๊ณ
14.3 ๋ฆฌ์ค๋
JPA ๋ฆฌ์ค๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ์ํฐํฐ์ ์๋ช ์ฃผ๊ธฐ์ ๋ฐ๋ฅธ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ค.
14.3.1 ์ด๋ฒคํธ ์ข ๋ฅ
์ด๋ฒคํธ ์ข ๋ฅ์ ๋ฐ์ ์์
14.3.2 ์ด๋ฒคํธ ์ ์ฉ ์์น
์ด๋ฒคํธ๋ ์ํฐํฐ์์ ์ง์ ๋ฐ๊ฑฐ๋ ๋ณ๋์ ๋ฆฌ์ค๋๋ฅผ ๋ฑ๋กํด์ ๋ฐ์ ์ ์๋ค.
์ํฐํฐ์ ์ง์ ์ ์ฉ
@PrePersist
, @PostLoad
โฆ ๋ฑ์ ์ด์ฉํ๋ค.
@Entity
public class Duck {
@Id @GeneratedValue
private Long id;
private String name;
@PrePersist
public void prePersist(){
System.out.println("prePersist");
}
@PostPersist
public void PostPersist (){
System.out.println("PostPersist");
}
@PostLoad
public void PostLoad(){
System.out.println("PostLoad");
}
@PreRemove
public void PreRemove (){
System.out.println("PreRemove");
}
@PostRemove
public void PostRemove(){
System.out.println("PostRemove");
}
@PreUpdate
public void PreUpdate(){
System.out.println("PreUpdate");
}
@PostUpdate
public void PostUpdate (){
System.out.println("PostUpdate ");
}
}
๋ณ๋์ ๋ฆฌ์ค๋ ๋ฑ๋ก
@EntityListeners
๋ฅผ ์ฌ์ฉํ๋ค.
@Entity
@EntityListeners(DuckListener.class)
public class Duck {...}
public class DuckListener {
@PrePersist
// ํน์ ํ์
์ด ํ์คํ๋ค๋ฉด ํน์ ํ์
์ ๋ฐ์ ์ ์๋ค.
public void prePersist(Duck obj){
System.out.println("prePersist");
}
@PostPersist
public void PostPersist (Duck obj){
System.out.println("PostPersist");
}
@PreRemove
public void PreRemove (Object obj){
System.out.println("PreRemove");
}
@PostRemove
public void PostRemove(Object obj){
System.out.println("PostRemove");
}
}
๋ฆฌ์ค๋๋ ๋์ ์ํฐํฐ๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ ์ ์๋ค. ๋ฐํ ํ์
์ void๋ก ์ค์ ํด์ผ ํ๋ค.
๊ธฐ๋ณธ ๋ฆฌ์ค๋ ์ฌ์ฉ
๋ชจ๋ ์ํฐํฐ์์ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ์ํด ๊ธฐ๋ณธ ๋ฆฌ์ค๋๋ก ๋ฑ๋กํ ์ ์๋ค.
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings ...>
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener class="jpabook.jpashop.domain.test.listener.DefaultListener">
</entity-listener>
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
๋ ์ธ๋ฐํ ์ค์
javax.persistence.ExcludeDefaultListeners
: ๊ธฐ๋ณธ ๋ฆฌ์ค๋ ๋ฌด์java.persistence.ExcludeSuperclassListners
: ์์ ํด๋์ค ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฌด์
@Entity
@EntityListeners(DuckListener.class)
@ExcludeDefaultListeners
@ExcludeSuperclassListners
public class Duck extends BaseEntity {...}
์ฌ๋ฌ ๋ฆฌ์ค๋๋ฅผ ๋ฑ๋กํ์ ๋ ์ด๋ฒคํธ ํธ์ถ ์์
- ๊ธฐ๋ณธ ๋ฆฌ์ค๋
- ๋ถ๋ชจ ํด๋์ค ๋ฆฌ์ค๋
- ๋ฆฌ์ค๋
- ์ํฐํฐ
14.4 ์ํฐํฐ ๊ทธ๋ํ
์ํฐํฐ ๊ทธ๋ํ ๊ธฐ๋ฅ์ ์ํฐํฐ ์กฐํ์์ ์ ์ฐ๊ด๋ ์ํฐํฐ๋ค์ ํจ๊ป ์กฐํํ๋ ๊ธฐ๋ฅ์ด๋ค.
- ์ ์ ์ผ๋ก ์ ์ํ๋ Named ์ํฐํฐ ๊ทธ๋ํ
- ๋์ ์ผ๋ก ์ ์ํ๋ ์ํฐํฐ ๊ทธ๋ํ
14.4.1 Named ์ํฐํฐ ๊ทธ๋ํ (์ ์ )
// ์ฃผ๋ฌธ์ ์กฐํํ ๋ ์ฐ๊ด๋ ํ์๋ ํจ๊ป ์กฐํํ๋ ์ํฐํฐ ๊ทธ๋ํ
@NamedEntityGraph(
name = "Order.withMember",
attributeNodes = {@NamedAttributeNode("member")}
)
@Entity
@Table(name = "ORDERS")
public class Order {
@Id
@GeneratedValue
@Column(name = "ORDER_ID")
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "MEMBER_ID")
private Member member; // ์ฃผ๋ฌธ ํ์
...
}
@NamedEntityGraph
: Named ์ํฐํฐ ๊ทธ๋ํ ์ ์ํ๋ค.name
: ์ํฐํฐ ๊ทธ๋ํ์ ์ด๋ฆ์ ์ ์ํ๋ค.attributeNodes
: ํจ๊ป ์กฐํํ ์์ฑ์ ์ ํํ๋ค. ์ด๋@NamedAttributeNode
๋ฅผ ์ฌ์ฉํ๊ณ ๊ทธ ๊ฐ์ผ๋ก ํจ๊ป ์กฐํํ ์์ฑ์ ์ ํํ๋ฉด ๋๋ค.
Order.member
๊ฐ ์ง์ฐ ๋ก๋ฉ์ผ๋ก ์ค์ ๋์ด ์์ง๋ง, ์ํฐํฐ ๊ทธ๋ํ์์ ํจ๊ป ์กฐํํ ์์ฑ์ผ๋ก member
๋ฅผ ์ ํํ์ผ๋ฏ๋ก ์ด ์ํฐํฐ ๊ทธ๋ํ๋ฅผ ์ฌ์ฉํ๋ฉด Order๋ฅผ ์กฐํํ ๋ ์ฐ๊ด๋ member
๋ ํจ๊ป ์กฐํํ ์ ์๋ค.
์ํฐํฐ ๊ทธ๋ํ๋ฅผ ๋ ์ด์ ์ ์ํ ๊ฒฝ์ฐ @NamedEntityGraphs
์ฌ์ฉํ๋ค.
14.4.2 em.find()์์ ์ํฐํฐ ๊ทธ๋ํ ์ฌ์ฉ
// ์ ์ํ ์ํฐํฐ ๊ทธ๋ํ๋ฅผ ์ฐพ๊ณ
EntityGraph graph = em.getEntityGraph("Order.withMember");
// ์ํฐํฐ ๊ทธ๋ํ๋ JPA์ ํํธ ๊ธฐ๋ฅ์ ์ฌ์ฉํด์ ๋์
// ํํธ์ ๊ฐ์ผ๋ก ์ฐพ์์จ ์ํฐํฐ ๊ทธ๋ํ ์ฌ์ฉ
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
// ์กฐํํ ๋ ํํธ์ ๋ณด ํฌํจ
Order order = em.find(Order.class, orderId, hints);
14.4.3 subgraph
Order -> OrderItem -> Item ๊น์ง ํจ๊ป ์กฐํ
@NamedEntityGraph(
name = "Order.withAll",
attributeNodes = {
@NamedAttributeNode("member"),
@NamedAttributeNode(value = "orderItems",subgraph = "orderItems")
},
subgraphs = @NamedSubgraph(
name = "orderItems",
attributeNodes = {@NamedAttributeNode("item")}
)
)
@Entity
@Table(name = "ORDERS")
public class Order {
@Id
@GeneratedValue
@Column(name = "ORDER_ID")
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "MEMBER_ID")
private Member member; // ์ฃผ๋ฌธ ํ์
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<OrderItem>();
...
}
@Entity
@Table(name = "ORDER_ITEM")
public class OrderItem {
@Id
@GeneratedValue
@Column(name = "ORDER_ITEM_ID")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ITEM_ID")
private Item item; // ์ฃผ๋ฌธ ์ํ
...
}
Order.All
์ด๋ผ๋ Named
์ํฐํฐ ๊ทธ๋ํ ์ ์ํ๋ค. ์ด ์ํฐํฐ ๊ทธ๋ํ๋ ๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ฒด ๊ทธ๋ํ๋ฅผ ํจ๊ป ์กฐํํ๋ค.
Order
->Member
Order
->OrderItem
OrderItem
->Item
(Order
์ ๊ฐ์ฒด ๊ทธ๋ํ๊ฐ ์๋๋ฏ๋กsubgraphs
์์ฑ์ ์ ์)
JPQL์์ ์ํฐํฐ ๊ทธ๋ํ ์ฌ์ฉ
List<Order> resultList =
em.createQuery("select o from Order o where o.id = :orderId", Order.class)
.setParameter("orderId", orderId)
.setHint("javax.persistence.fetchgraph", em.getEntityGraph("Order.withAll"))
.getResultList();
14.4.5 ๋์ ์ํฐํฐ ๊ทธ๋ํ
์ํฐํฐ ๊ทธ๋ํ๋ฅผ ๋์ ์ผ๋ก ๊ตฌ์ฑํ๋ ค๋ฉด createEntityGraph()
๋ฉ์๋ ์ฌ์ฉํ๋ค.
๋์ ์ํฐํฐ ๊ทธ๋ํ
EntityGraph<Order> graph = em.createEntityGraph(Order.class);
graph.addAttributeNodes("member");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
Order order = em.find(Order.class, orderId, hints);
๋์ ์ํฐํฐ ๊ทธ๋ํ subgraph
EntityGraph<Order> graph = em.createEntityGraph(Order.class);
graph.addAttributeNodes("member");
Subgraph<OrderItem> orderItems = graph.addSubgraph("orderItems");
orderItems.addAttributeNodes("item");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
Order order = em.find(Order.class, orderId, hints);
14.4.6 ์ํฐํฐ ๊ทธ๋ํ ์ ๋ฆฌ
ROOT์์ ์์
์ํฐํฐ ๊ทธ๋ํํ ํญ์ ์กฐํํ๋ ์ํฐํฐ์ ROOT์์ ์์ํด์ผ ํ๋ค.
์ด๋ฏธ ๋ก๋ฉ๋ ์ํฐํฐ
์์์ฑ ์ปจํ
์คํธ์ ํด๋น ์ํฐํฐ๊ฐ ์ด๋ฏธ ๋ก๋ฉ๋์ด ์์ผ๋ฉด ์ํฐํฐ ๊ทธ๋ํ๊ฐ ์ ์ฉ๋์ง ์๋๋ค.
Order order1 = em.find(Order.class, orderId); // ์ด๋ฏธ ์กฐํ
hints.put("javax.persistence.fetchgraph", em.getEntityGraph("Order.withMember"));
// ์ํฐํฐ ๊ทธ๋ํ๊ฐ ์ ์ฉ๋์ง ์๊ณ order1๊ณผ ๊ฐ์ ์ธ์คํด์ค ๋ฐํ
Order order2 = em.find(Order.class, orderId);
fetchgraph, loadgraph ์ฐจ์ด
javax.persistence.fetchgraph
: ์ํฐํฐ ๊ทธ๋ํ์ ์ ํํ ์์ฑ๋ง ํจ๊ป ์กฐํjavax.persistence.loadgraph
: ์ํฐํฐ ๊ทธ๋ํ์ ์ ํํ ์์ฑ + FetchType.EAGER๋ก ์ค์ ๋ ์ฐ๊ด๊ด๊ณ ์กฐํ
14.5 ์ ๋ฆฌ
- JPA๊ฐ ์ง์ํ๋ ์ปฌ๋ ์ ์ ์ข ๋ฅ์ ํน์ง
- ์ปจ๋ฒํฐ๋ฅผ ์ฌ์ฉํ๋ฉด ์ํฐํฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ณํํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ ์ ์๋ค.
- ๋ฆฌ์ค๋๋ฅผ ์ฌ์ฉํ๋ฉด ์ํฐํฐ์์ ๋ฐ์ํ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ ์ฒ๋ฆฌํ ์ ์๋ค.
- ํ์น ์กฐ์ธ์ ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํ์ง๋ง ์ํฐํฐ ๊ทธ๋ํ๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ์ฒด ์งํฅ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์์๋ ์ํ๋ ๊ฐ์ฒด ๊ทธ๋ํ๋ฅผ ํ ๋ฒ์ ์กฐํํ ์ ์๋ค.
15. ๊ณ ๊ธ ์ฃผ์ ์ ์ฑ๋ฅ ์ต์ ํ
15.1 ์์ธ ์ฒ๋ฆฌ
- ํธ๋์ญ์ ์ ๋กค๋ฐฑํ๋ ๊ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์์ฌํญ๋ง ๋กค๋ฐฑํ๋ ๊ฒ์ด์ง ์์ ํ ์๋ฐ ๊ฐ์ฒด๊น์ง ์์ํ๋ก ๋ณต๊ตฌํด์ฃผ์ง ์๋๋ค.
- ์ํฐํฐ ์์ ์ค ์์ธ๊ฐ ๋ฐ์ํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ดํฐ๋ ๋กค๋ฐฑ๋์ง๋ง ๊ฐ์ฒด๋ ์์ ๋ ์ํ๋ก ์ํฐํฐ ์ปจํ ์คํธ์ ๋จ์์๋ค.
- ๋ฐ๋ผ์ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ด๊ธฐํํ ๋ค์์ ์ฌ์ฉํด์ผ ํ๋ค.
- ํธ๋์ญ์ ๋น ์์์ฑ ์ปจํ ์คํธ๋ ํธ๋์ญ์ ์ข ๋ฃ ์์ ์ ์์์ฑ ์ปจํ ์คํธ๋ ์ข ๋ฃ๋๋ฏ๋ก ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.
OSIV
์์ ์คํ๋ง ํ๋ ์์ํฌ๋ ์์์ฑ ์ปจํ ์คํธ์ ๋ฒ์๋ฅผ ํธ๋์ญ์ ์ ๋ฒ์๋ณด๋ค ๋๊ฒ ์ค์ ํ๋ฉด ํธ๋์ญ์ ๋กค๋ฐฑ์ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ด๊ธฐํํด์ ๋ฌธ์ ๋ฅผ ์๋ฐฉํ๋ค.
15.2 ์ํฐํฐ ๋น๊ต
- ๋์ผ์ฑ ๋น๊ต๋ ๊ฐ์ ์์์ฑ ์ปจํ ์คํธ์ ๊ด๋ฆฌ๋ฅผ ๋ฐ๋ ์์ ์ํ์ ์ํฐํฐ์๋ง ์ ์ฉํ ์ ์๋ค.
- ๊ทธ ์ธ์๋ ๋๋ฑ์ฑ ๋น๊ต๋ฅผ ํด์ผํ๋ค.
- ์ํฐํฐ๋ฅผ ๋น๊ตํ ๋๋
@Id
๋ฑ๊ณผ ๊ฐ์ ๋น์ฆ๋์คํค๋ฅผ ํ์ฉํ ๋๋ฑ์ฑ ๋น๊ต๋ฅผ ๊ถ์ฅํ๋ค.
15.3 ํ๋ก์ ์ฌํ
15.3.1 ์์์ฑ ์ปจํ ์คํธ์ ํ๋ก์
์์์ฑ ์ปจํ ์คํธ๋ ์์ ์ด ๊ด๋ฆฌํ๋ ์์ ์ํฐํฐ์ ๋์ผ์ฑ์ ๋ณด์ฅํ๋ค. ๊ทธ๋ผ ํ๋ก์๋ก ์กฐํํ ์ํฐํฐ์ ๋์ผ์ฑ๋ ๋ณด์ฅํ ๊น?
// ํ๋ก์ ์กฐํ
Member refMember = em.getReference(Member.class, "member1");
// find ์กฐํ
Member findMember = em.find(Member.class, "member1");
์ถ๋ ฅ ๊ฒฐ๊ณผ
refMember Type = ํ๋ก์ ์ํฐํฐ
findMember Type = ํ๋ก์ ์ํฐํฐ
refMember
๋ ํ๋ก์๊ณ findMember
๋ ์๋ณธ ์ํฐํฐ์ด๋ฏ๋ก ๋์ ์๋ก ๋ค๋ฅธ ์ธ์คํด์ค๋ก ์๊ฐํ ์ ์์ง๋ง, ์์ ์ํฐํฐ์ ๋์ผ์ฑ ๋ณด์ฅ์ ์ํด ํ๋ก์๋ก ์กฐํ๋ ์ํฐํฐ์ ๋ํด์ ๊ฐ์ ์ํฐํฐ๋ฅผ ์ฐพ์ผ๋ฉด ํ๋ก์๋ฅผ ๋ฐํํ๋ค.
// find ์กฐํ
Member findMember = em.find(Member.class, "member1");
// ํ๋ก์ ์กฐํ
Member refMember = em.getReference(Member.class, "member1");
์ถ๋ ฅ ๊ฒฐ๊ณผ
findMember Type = ์๋ณธ ์ํฐํฐ
refMember Type = ์๋ณธ ์ํฐํฐ
๋ฐ๋๋ก ์๋ณธ ์ํฐํฐ๋ฅผ ๋จผ์ ์กฐํํ๋ฉด ํ๋ก์๋ฅผ ๋ฐํํ ์ด์ ๊ฐ ์๋ค. ๋ฐ๋ผ์ refMember
๋ ์๋ณธ์ ๋ฐํํ๋ค.
15.3.2 ํ๋ก์ ๋น๊ต ํ์
ํ๋ก์๋ ์๋ณธ ์ํฐํฐ๋ฅผ ์์ ๋ฐ์์ ๋ง๋ค์ด์ง๋ฏ๋ก ํ๋ก์๋ก ์กฐํํ ์ํฐํฐ์ ํ์ ์ ๋น๊ตํ ๋๋ == ๋น๊ต๋ฅผ ํ๋ฉด ์๋๊ณ ๋์ ์ instanceof๋ฅผ ์ฌ์ฉํด์ผํ๋ค.
Member refMember = em.getReference(Member.class, "member1");
Assert.assertFalse(Member.class == refMember.getClass()); // false
Assert.assertTrue(refMember instanceof Member); // true
15.3.3 ํ๋ก์ ๋๋ฑ์ฑ ๋น๊ต
ํ๋ก์ ๋๋ฑ์ฑ ๋น๊ต ์ฃผ์์ฌํญ
- ํ๋ก์์ ํ์
๋น๊ต๋
==
๋์ ์instanceof
๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. - ํ๋ก์์ ๋งด๋ฒ๋ณ์์ ์ง์ ์ ๊ทผํ๋ฉด ์๋๊ณ ์ ๊ทผ์ ๋ฉ์๋(
Getter
)๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
15.3.4 ์์๊ด๊ณ์ ํ๋ก์
ํ๋ก์๋ฅผ ๋ถ๋ชจ ํ์ ์ผ๋ก ์กฐํํ๋ฉด ๋ถ๋ชจ ํ์ ์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ก์๊ฐ ์์ฑ๋๋ค.
Item proxyItem = em.getReference(Item.class, saveBook.getId());
// ์์ธ๋ฐ์ java.lang.ClassCastException
// Book book = (Book) proxyItem;
// ๊ฒ์ฆ
Assert.assertFalse(proxyItem.getClass() == Book.class);
Assert.assertFalse(proxyItem instanceof Book);
Assert.assertTrue(proxyItem instanceof Item);
๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๊ฐ ์๋ค.
instanceof
์ฐ์ฐ์ ์ฌ์ฉํ ์ ์๋ค.- ํ์ ํ์
์ผ๋ก ๋ค์ด์บ์คํ
์ ํ ์ ์๋ค.
๋ช ๊ฐ์ง ํด๊ฒฐ๋ฐฉ๋ฒ
1. JPQL๋ก ๋์ ์ง์ ์กฐํ
Book jpqlBook = em.createQuery("select b from Book b where b.id=:bookId", Book.class);
.setParameter("bookId", item.getId())
.getSingleResult();
ํ์ง๋ง ์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ฉด ๋คํ์ฑ์ ํ์ฉํ ์ ์๋ค.
2. ๊ธฐ๋ฅ์ ์ํ ๋ณ๋์ ์ธํฐํ์ด์ค ์ ๊ณต
// ํน์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ์ธํฐํ์ด์ค ์์ฑ
public interface TitleView {
String getTitle();
}
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item implements TitleView {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
...
// Getter, Setter
}
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {
private String director;
private String actor;
@Override
public String getTitle() {
return "[์ ๋ชฉ:" + getName() + " ๊ฐ๋
:" + director + " ๋ฐฐ์ฐ:" + actor + "]";
}
}
@Entity
@DiscriminatorValue("B")
public class Book extends Item {
private String author;
private String isbn;
@Override
public String getTitle() {
return "[์ ๋ชฉ:" + getName() + " ์ ์:" + author + "]";
}
}
// ํ๋ก์ ์ธํฐํ์ด์ค ์ ๊ณต ์ฌ์ฉ
@Entity
public class OrderItem {
@Id @GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColmn(name = "ITEM_ID")
private Item item;
...
public void printItem() {
System.out.println("TITLE=" + item.getTitle());
}
}
// Item์ ๊ตฌํ์ฒด์ ๋ฐ๋ผ ๊ฐ๊ฐ ๋ค๋ฅธ getTitle() ๋ฉ์๋๊ฐ ํธ์ถ๋๋ค.
OrderItem orderItem = em.find(OrderItem.class, saveOrderItem.getId());
orderItem.printItem();
3. ๋น์งํฐ ํจํด ์ฌ์ฉ
๊ตฌํ ๋ด์ฉ ์๋ต, ์ฑ
์ฐธ๊ณ ๐
์ฅ์
- ํ๋ก์์ ๋ํ ๊ฑฑ์ ์์ด ์์ ํ๊ฒ ์๋ณธ ์ํฐํฐ์ ์ ๊ทผํ ์ ์๋ค.
instanceof
์ ํ์ ์บ์คํ ์์ด ์ฝ๋๋ฅผ ๊ตฌํํ ์ ์๋ค.- ์๊ณ ๋ฆฌ์ฆ๊ณผ ๊ฐ์ฒด ๊ตฌ์กฐ๋ฅผ ๋ถ๋ฆฌํด์ ๊ตฌ์กฐ๋ฅผ ์์ ํ์ง ์๊ณ ์๋ก์ด ๋์์ ์ถ๊ฐํ ์ ์๋ค.
๋จ์
- ๋๋ฌด ๋ณต์กํ๊ณ ์ดํดํ๊ธฐ ์ด๋ ต๋ค.
- ๊ฐ์ฒด ๊ตฌ์กฐ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋ชจ๋
Visitor
๋ฅผ ์์ ํด์ผ ํ๋ค.
15.4 ์ฑ๋ฅ ์ต์ ํ
15.4.1 N+1 ๋ฌธ์
- ์ฆ์ ๋ก๋ฉ์ JPQL์ ์คํํ ๋ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
- ์ง์ฐ ๋ก๋ฉ์ ์ฐ๊ด๋ ์ปฌ๋ ์
์ ์ฌ์ฉํ ๋ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
1. ํ์น ์กฐ์ธ ์ฌ์ฉ
select distinct m from Member m join fetch m.orders
์ผ๋๋ค ์กฐ์ธ์ ํ๋ฉด ๊ฒฐ๊ณผ๊ฐ ๋์ด๋์ ์ค๋ณต๋ ๊ฒฐ๊ณผ๊ฐ ๋ํ๋ ์ ์๋ค. ๋ฐ๋ผ์ distinct
๋ฅผ ์ฌ์ฉํด์ ์ค๋ณต์ ์ ๊ฑฐํ๋ ๊ฒ์ด ์ข๋ค.
2. ํ์ด๋ฒ๋ค์ดํธ @BatchSize
ํ์ด๋ฒ๋ค์ดํธ๊ฐ ์ ๊ณตํ๋ @BatchSize
์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ๋ฉด ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ์ง์ ํ size
๋งํผ SQL
์ IN
์ ์ ์ฌ์ฉํด์ ์กฐํํ๋ค. ๋ง์ฝ ์กฐํํ ํ์์ด 10๋ช
์ธ๋ฐ size=5
๋ก ์ง์ ํ๋ฉด 2๋ฒ์ SQL
๋ง ์ถ๊ฐ๋ก ์คํํ๋ค.
@BatchSize
@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
private List<Order> orders = new ArrayList<Order>();
์ฐธ๊ณ hibernate.default_batch_fetch_size ์์ฑ์ ์ฌ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด์ ๊ธฐ๋ณธ์ผ๋ก @BatchSize๋ฅผ ์ ์ฉํ ์ ์๋ค.
3. ํ์ด๋ฒ๋ค์ดํธ @Fetch(FetchMode.SUBSELECT)
ํ์ด๋ฒ๋ค์ดํธ๊ฐ ์ ๊ณตํ๋ @Fetch
์ด๋
ธํ
์ด์
์ FetchMode.SUBSELECT
๋ก ์ฌ์ฉํ๋ฉด ์ฐ๊ด๋ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ๋ ์๋ธ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ N+1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค.
@Fetch(FetchMode.SUBSELECT)
@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
private List<Order> orders = new ArrayList<Order>();
-- JPQL
select m from Member m where m.id > 10
-- ์ฆ์ ๋ก๋ฉ์ผ๋ก ์ค์ ํ๋ฉด ์กฐํ ์์ ์
-- ์ง์ฐ ๋ก๋ฉ์ผ๋ก ์ค์ ํ๋ฉด ์ง์ฐ๋ก๋ฉ๋ ์ํฐํฐ๋ฅผ ์ฌ์ฉํ๋ ์์ ์
-- SQL
SELECT O FROM ORDERS O
WHERE O.MEMBER_ID IN (
SELECT
M.ID
FROM
MEMBER M
WHERE M.ID > 10
)
์ ๋ฆฌ
- ์ฆ์ ๋ก๋ฉ์ ์ฑ๋ฅ ์ต์ ํ๊ฐ ์ด๋ ต๋ค.
- ์ฆ์ ๋ก๋ฉ์ ์ฌ์ฉํ์ง ๋ง๊ณ ์ง์ฐ ๋ก๋ฉ๋ง ์ฌ์ฉํ๋ ๊ฒ์ ์ถ์ฒํ๋ค.
- ์ฑ๋ฅ ์ต์ ํ๊ฐ ๊ผญ ํ์ํ ๊ณณ์์๋ง JPQL ํ์น ์กฐ์ธ ์ฌ์ฉํ์.
15.4.2 ์ฝ๊ธฐ ์ ์ฉ ์ฟผ๋ฆฌ์ ์ฑ๋ฅ ์ต์ ํ
์์์ฑ ์ปจํ ์คํธ๋ ๋ณ๊ฒฝ ๊ฐ์ง๋ฅผ ์ํด ์ค๋ ์ท ์ธ์คํด์ค๋ฅผ ๋ณด๊ดํ๋ฏ๋ก ๋ ๋ง์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๋จ์ ์ด ์๋ค. ๋จ์ํ ์กฐํ๋ง ํ๊ณ , ์กฐํํ ์ํฐํฐ๋ฅผ ๋ค์ ์กฐํํ ์ผ๋ ์๊ณ ์์ ํ ์ผ๋ ์๋ค๋ฉด ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ์ํฐํฐ๋ฅผ ์กฐํํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ต์ ํํ ์ ์๋ค.
์ค์นผ๋ผ ํ์
์ผ๋ก ์กฐํ
์ค์นผ๋ผ ํ์
์ ์์์ฑ ์ปจํ
์คํธ๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๊ด๋ฆฌํ์ง ์๋๋ค.
select o.id, o.name, o.price from Order p
์ฝ๊ธฐ ์ ์ฉ ์ฟผ๋ฆฌ ํํธ ์ฌ์ฉ
ํ์ด๋ฒ๋ค์ดํธ ์ ์ฉ ํํธ์ธ org.hibernate.readOnly
๋ฅผ ์ฌ์ฉํ๋ฉด ์ํฐํฐ๋ฅผ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ์กฐํํ ์ ์๋ค. ์์์ฑ ์ปจํ
์คํธ๋ ์ค๋
์ท์ ๋ณด๊ดํ์ง ์๋๋ค.
TypedQuery<Order> query = em.createQuery("select o from Order o", Order.class);
query.setHint("org.hibernate.readOnly", true);
์ฝ๊ธฐ ์ ์ฉ ํธ๋์ญ์
์ฌ์ฉ
์คํ๋ง ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ฉด ํธ๋์ญ์
์ ์ฝ๊ธฐ ์ ์ฉ ๋ชจ๋๋ก ์ค์ ํ ์ ์๋ค. ์ด ์ต์
์ ํธ๋์ญ์
์ ์ปค๋ฐํด๋ ์์์ฑ ์ปจํ
์คํธ๋ฅผ ํ๋ฌ์ ํ์ง ์๋๋ค. ์ค๋
์ท ๋น๊ต์ ๊ฐ์ ๋ฌด๊ฑฐ์ด ๋ก์ง๋ค์ ์ํํ์ง ์์ผ๋ฏ๋ก ์ฑ๋ฅ์ด ํฅ์๋๋ค.
@Transactional(readOnly = true)
์ถ์ฒ ๋ฐฉ๋ฒ
- ์ฝ๊ธฐ ์ ์ฉ ํธ๋์ญ์ ์ฌ์ฉ: ํ๋ฌ์๋ฅผ ์๋ํ์ง ์๋๋ก ํด์ ์ฑ๋ฅ ํฅ์
- ์ฝ๊ธฐ ์ ์ฉ ์ํฐํฐ ์ฌ์ฉ: ์ํฐํฐ๋ฅผ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ์กฐํํด์ ๋ฉ๋ชจ๋ฆฌ ์ ์ฝ
15.4.3 ๋ฐฐ์น ์ฒ๋ฆฌ
- ์์์ฑ ์ปจํ ์คํธ์ ๋๋ฌด ๋ง์ ์ํฐํฐ๊ฐ ์ ์ฅ๋๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์๋ค.
- ๋ฐ๋ผ์ ์ด๋ฌํ ๋ฐฐ์น ์ฒ๋ฆฌ๋ฅผ ์ ์ ํ ๋จ์๋ก ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ด๊ธฐํํด์ผ ํ๋ค.
- 2์ฐจ ์บ์๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด 2์ฐจ ์บ์์ ์ํฐํฐ๋ฅผ ๋ณด๊ดํ์ง ์๋๋ก ์ฃผ์ํด์ผ ํ๋ค.
JPA ๋ฑ๋ก ๋ฐฐ์น
๋ง์ ์์ ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ๋ฑ๋กํ ๋ ์ฃผ์ํ ์ ์ ์์์ฑ ์ปจํ
์คํธ์ ์ํฐํฐ๊ฐ ๊ณ์ ์์ด์ง ์๋๋ก ์ผ์ ๋จ์๋ง๋ค ์์์ฑ ์ปจํ
์คํธ์ ์ํฐํฐ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ๋ฌ์ํ๊ณ ์์์ฑ ์ปจํ
์คํธ๋ฅผ ์ด๊ธฐํํด์ผ ํ๋ค.
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
for(int i=0; i<100000; ++i){
Product product = new Product("item"+ i, 10000);
em.persist(product);
//100๊ฑด๋ง๋ค ํ๋ฌ์์ ์์์ฑ ์ปจํ
์คํธ ์ด๊ธฐํ
if( i % 100 == 0 ) {
em.flush();
em.clear();
}
}
tx.commit();
em.close();โ
JPA ์์ ๋ฐฐ์น
๋ฐฐ์น ์ฒ๋ฆฌ๋ ์์ฃผ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํด์ ์์ ํ๋ค. ์ด๋ ์๋ง์ ๋ฐ์ดํฐ๋ค์ ํ ๋ฒ์ ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ ค๋ ์ ์์ด์ ๋ค์ ๋ฐฉ๋ฒ์ ์ฃผ๋ก ์ฌ์ฉํ๋ค.
1. JPA ํ์ด์ง ๋ฐฐ์น ์ฒ๋ฆฌ
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
int pageSize = 100;
for (int i = 0; i < 10; ++i) {
// ์กฐํ
List < Product > resultList = em.createQuery("select p from Product p", Product.class)
.setFirstResult(i * pageSize)
.setMaxResults(pageSize)
.getResultList();
//๋น์ฆ๋์ค ๋ก์ง ์คํ
for (Product product: resultList) {
product.setPrice(product.getPrice + 100);
}
// ํ๋ฌ์, ์ด๊ธฐํ
em.flush();
em.clear();
}
tx.commit();
em.close();
2. ํ์ด๋ฒ๋ค์ดํธ scroll ์ฌ์ฉ
JPA
๋ JDBC ์ปค์(CURSOR)
๋ฅผ ์ง์ํ์ง ์๋๋ค. ๋ฐ๋ผ์ ์ปค์
๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ํ์ด๋ฒ๋ค์ดํธ ์ธ์
(SESSION)
์ ์ฌ์ฉํด์ผ ํ๋ค. ํ์ด๋ฒ๋ค์ดํธ๋ scroll
์ด๋ผ๋ ์ด๋ฆ์ผ๋ก JDBC ์ปค์
๋ฅผ ์ง์ํ๋ค.
EntityTransaction tx = em.getTransaction();
Session session = em.unwrap(Session.class);
tx.begin();
ScrollableResults scroll = session.createQuery("select p from Product p")
.setCacheMode(CacheMode.IGNORE) // 2์ฐจ ์บ์ ๊ธฐ๋ฅ์ ๋๋ค.
.scroll(ScrollMode.FORWARD_ONLY);
int count = 0;
while (scroll.next()) {
Product p = (Product) scroll.get(0);
p.setPrice(p.getPrice() + 100);
count++;
if (count % 100 == 0) {
session.flush(); //ํ๋ฌ์
session.clear(); //์์์ฑ ์ปจํ
์คํธ ์ด๊ธฐํ
}
}
tx.commit();
session.close();
3. ํ์ด๋ฒ๋ค์ดํธ ๋ฌด์ํ ์ธ์ ์ฌ์ฉ
- ํ์ด๋ฒ๋ค์ดํธ๋ ๋ฌด์ํ ์ธ์ ์ด๋ผ๋ ํน๋ณํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
- ๋ฌด์ํ ์ธ์ ์ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ๋ง๋ค์ง ์๊ณ ์ฌ์ง์ด 2์ฐจ ์บ์๋ ์ฌ์ฉํ์ง ์๋๋ค.
- ์ํฐํฐ๋ฅผ ์์ ํ๋ ค๋ฉด ๋ฌด์ํ ์ธ์
์ด ์ ๊ณตํ๋
update()
๋ฉ์๋๋ฅผ ์ง์ ํธ์ถํด์ผ ํ๋ค.
SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();
ScrollableResults scroll = session.createQuery("select p from Product p").scroll();
while (scroll.next()) {
Product p = (Product) scroll.get(0);
p.setPrice(p.getPrice() + 100);
session.update(p); // ์ง์ update๋ฅผ ํธ์ถํด์ผ ํ๋ค.
}
tx.commit();
session.close();
15.4.4 SQL ์ฟผ๋ฆฌ ํํธ ์ฌ์ฉ
JPA๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค SQL ํํธ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ง ์๋๋ค. SQL ํํธ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ํ์ด๋ฒ๋ค์ดํธ๋ฅผ ์ง์ ์ฌ์ฉํด์ผ ํ๋ค. ์ฌ๊ธฐ์ ๋งํ๋ SQL ํํธ๋ JPA ๊ตฌํ์ฒด์๊ฒ ์ ๊ณตํ๋ ํํธ๊ฐ ์๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฒค๋์๊ฒ ์ ๊ณตํ๋ ํํธ๋ค.
SQL ์ฟผ๋ฆฌ ํํธ ์ฌ์ฉ
Session session = em.unwrap(Session.class); // ํ์ด๋ฒ๋ค์ดํธ ์ง์ ์ฌ์ฉ
List<Member> list = session.creatQuery("select m from Member m")
.addQueryHint("FULL (MEMBER)") // SQL ํํธ ์ถ๊ฐ
.list();
-- ์คํ๋ SQL์ ๋ณด๋ฉด ์ถ๊ฐํ ํํธ๊ฐ ์๋ค.
select
/*+ FULL (MEMBER) */ m.id, m.name
from
Member m
15.4.5 ํธ๋์ญ์ ์ ์ง์ํ๋ ์ฐ๊ธฐ ์ง์ฐ ์ฑ๋ฅ ์ต์ ํ
์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํด SQL ๋ฐฐ์น๋ฅผ ์ด์ฉํ๋ค. ํ์ด๋ฒ๋ค์ดํธ์์ ๋ฐฐ์น๋ฅผ ์ค์ ํ๋ฉด ๋ฐ์ดํฐ๋ฅผ ๋ฑ๋ก, ์์ ์ญ์ ํ ๋ SQL ๋ฐฐ์น ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ค
<property name="hibernate.jdbc.batch_size" value="50">
hibernate.jdbc.batch_size
์์ฑ์ ์์ฑ ๊ฐ์ 50์ผ๋ก ์ฃผ๋ฉด ์ต๋ 50๊ฑด์ฉ ๋ชจ์์ SQL ๋ฐฐ์น๋ฅผ ์คํํ๋ค. ํ์ง๋ง SQL ๋ฐฐ์น๋ ๊ฐ์ SQL์ผ ๋๋ง ์ ํจํ๋ค.
em.persist(ner Member()); // 1
em.persist(ner Member()); // 2
em.persist(ner Member()); // 3
em.persist(ner Member()); // 4
em.persist(ner Child()); // 5, ๋ค๋ฅธ ์ฐ์ฐ
em.persist(ner Member()); // 6
em.persist(ner Member()); // 7
์ด๋ ๊ฒ ํ๋ฉด 1,2,3,4๋ฅผ ๋ชจ์์ ํ๋์ SQL ๋ฐฐ์น๋ฅผ ์คํํ๊ณ 5๋ฅผ ํ ๋ฒ ์คํํ๊ณ 6,7์ ๋ชจ์์ ์คํํ๋ค. ์ด 3๋ฒ์ SQL ๋ฐฐ์น๋ฅผ ์คํํ๋ค.
15.5 ์ ๋ฆฌ
- JPA์ ์์ธ๋ ํธ๋์ญ์ ๋กค๋ฐฑ์ ํ์ํ๋ ์์ธ์ ํ์ํ์ง ์๋ ์์ธ๋ก ๋๋๋ค. ํธ๋์ญ์ ์ ๋กค๋ฐฑํ๋ ์์ธ๋ ์ฌ๊ฐํ ์์ธ์ด๋ฏ๋ก ํธ๋์ญ์ ์ ๊ฐ์ ๋ก ์ปค๋ฐํด๋ ์ปค๋ฐ๋์ง ์๊ณ ๋กค๋ฐฑ๋๋ค.
- ์์์ฑ ์ปจํ ์คํธ์ ์ํฐํฐ๋ ๋น์ฆ๋์ค ํค๋ฅผ ์ฌ์ฉํ ๋๋ฑ์ฑ ๋น๊ต๋ฅผ ํด์ผ ํ๋ค.
- ํ๋ก์๋ฅผ ์ฌ์ฉํ๋ ํด๋ผ์ด์ธํธ๋ ์กฐํํ ์ํฐํฐ๊ฐ ํ๋ก์์ธ์ง ์๋ณธ ์ํฐํฐ์ธ์ง ๊ตฌ๋ถํ์ง ์๊ณ ์ฌ์ฉํ ์ ์์ด์ผ ํ๋ค. ํ์ง๋ง ํ๋ก์๋ ๊ธฐ์ ์ ์ธ ํ๊ณ๊ฐ ์์ผ๋ฏ๋ก ํ๊ณ์ ์ ์ธ์ํ๊ณ ์ฌ์ฉํด์ผ ํ๋ค.
- JPA๋ฅผ ์ฌ์ฉํ ๋ N+1 ๋ฌธ์ ๋ฅผ ๊ฐ์ฅ ์กฐ์ฌํด์ผ ํ๋ค. ์ฃผ๋ก ํ์น ์กฐ์ธ์ผ๋ก ํด๊ฒฐํ๋ค.
- ์ํฐํฐ๋ฅผ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ์กฐํํ๋ฉด ์ค๋ ์ท์ ์ ์งํ ํ์๊ฐ ์๊ณ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ํ๋ฌ์ํ์ง ์์๋ ๋๋ค.
- ๋๋์ ์ํฐํฐ๋ฅผ ๋ฐฐ์น ์ฒ๋ฆฌํ๋ ค๋ฉด ์ ์ ํ ์์ ์ ๊ผญ ํ๋ฌ์๋ฅผ ํธ์ถํ๊ณ ์์์ฑ ์ปจํ ์คํธ๋ ์ด๊ธฐํํด์ผ ํ๋ค.
- JPA๋ SQL ์ฟผ๋ฆฌ ํํธ๋ฅผ ์ง์ํ์ง ์์ง๋ง ํ์ด๋ฒ๋ค์ดํธ ๊ตฌํ์ฒด๋ฅผ ์ฌ์ฉํ๋ฉด SQL ์ฟผ๋ฆฌ ํํธ๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
16. ํธ๋์ญ์ ๊ณผ ๋ฝ, 2์ฐจ ์บ์
16.1 ํธ๋์ญ์ ๊ณผ ๋ฝ
ํธ๋์ญ์
๊ธฐ์ด์ JPA๊ฐ ์ ๊ณตํ๋ ๋๊ด์ ๋ฝ๊ณผ ๋น๊ด์ ๋ฝ์ ๋ํด ์์๋ณธ๋ค.
16.1.1 ํธ๋์ญ์ ๊ณผ ๊ฒฉ๋ฆฌ ์์ค
ํธ๋์ญ์
์ ACID
๋ผ ํ๋ ์์์ฑ(Atomicty
), ์ผ๊ด์ฑ(Consistency
), ๊ฒฉ๋ฆฌ์ฑ(Isolation
), ์ง์์ฑ(Durability
)์ ๋ณด์ฅํด์ผ ํ๋ค.
- ์์์ฑ: ํธ๋์ญ์ ๋ด์์ ์คํํ ์์ ๋ค์ ๋ง์น ํ๋์ ์์ ์ธ ๊ฒ์ฒ๋ผ ๋ชจ๋ ์ฑ๊ณต ํ๋ ๊ฐ ๋ชจ๋ ์คํจํด์ผ ํ๋ค.
- ์ผ๊ด์ฑ: ๋ชจ๋ ํธ๋์ญ์ ์ ์ผ๊ด์ฑ ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํ๋ฅผ ์ ์งํด์ผ ํ๋ค.
- ๊ฒฉ๋ฆฌ์ฑ: ๋์์ ์คํ๋๋ ํธ๋์ญ์ ๋ค์ด ์๋ก์๊ฒ ์ํฅ์ ๋ฏธ์น์ง ์๋๋ก ๊ฒฉ๋ฆฌํ๋ค.
- ์ง์์ฑ: ํธ๋์ญ์
์ ์ฑ๊ณต์ ์ผ๋ก ๋๋ด๋ฉด ๊ทธ ๊ฒฐ๊ณผ๊ฐ ํญ์ ๊ธฐ๋ก๋์ด์ผ ํ๋ค.
ANSI
ํ์ค ํธ๋์ญ์
์ ๊ฒฉ๋ฆฌ ์์ค 4๋จ๊ณ
- READ UNCOMMITED
- ํธ๋์ญ์ ์์ ๋ณ๊ฒฝํ๋ ๋ด์ฉ์ด commit๊ณผ rollback์ฌ๋ถ์ ๊ด๊ณ ์์ด ๋ค๋ฅธ ํธ๋์ญ์ ์๊ฒ ๋ ธ์ถ๋๋ค.
- READ COMMITTED
- ํธ๋์ญ์ ์์ ๋ณ๊ฒฝํ ๋ ์ฝ๋๋ commit์ด ์๋ฃ๋ ๋ฐ์ดํฐ๋ง ์กฐํํ ์ ์๋ค.
- commit ์ ์ ์กฐํ๋ฅผ ์๋ํ๋ค๋ฉด UNDO์์ญ์ ๋ฐฑ์ ๋ ๋ ์ฝ๋๋ฅผ ์กฐํํ ์ ์๋ค.
- REPEATABLE READ
- InnoDB ์์ง์์ ์ฌ์ฉํ๋ ๊ธฐ๋ณธ ๊ฒฉ๋ฆฌ ์์ค.
- REPEATABLE READ๋ ํ๋์ ํธ๋์ญ์ ๋ด๋ถ์์ ๊ฐ์ SELECT ๋ฌธ์ ํญ์ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฒ.
- MySQL InnoDB์์๋ ํธ๋์ญ์ ๋ณ๋ก ์๋ณ์๋ฅผ ์ฃผ๊ณ ํธ๋์ญ์ ์์ ๋ณ๊ฒฝํ๋ ๋ฐ์ดํฐ๋ฅผ UNDO์์ญ์ ๋ฐฑ์ ํ๋ค.
- ์ด ๋ฐฑ์ ๋ ๋ฐ์ดํฐ์ ํธ๋์ญ์ ์๋ณ์๋ก ๋์ผ ํธ๋์ญ์ ์์ ๋์ผ ๊ฒฐ๊ณผ๊ฐ์ ๋ณด์ฌ์ค ์ ์๋๋ก ๋ณด์ฅํ๋ค.
- SERIALIZABLE
- ๊ฐ์ฅ ๋์ ๊ฒฉ๋ฆฌ ์์ค.
- ๋ ์ฝ๋๋ฅผ ์กฐํํ ๋ Shared Lock์ ํ๋ํด์ผ๋ง ์กฐํํ ์ ์๋ค.
- ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ ๋์๋ Exclusive Lock์ ํ๋ํด์ผ๋ง ๋ณ๊ฒฝํ ์ ์๋ค.
- ์ฆ ํ ํธ๋์ญ์ ์์ ์ฌ์ฉํ๋ ๋ฐ์ดํฐ๋ ๋ค๋ฅธ ํธ๋์ญ์ ์์ ์ ๊ทผํ ์ ์๊ฒ ๋ง๋ ๋ค.
- ๋ฐ์ดํฐ ์ ํฉ์ฑ์ ์งํค๋ ๋ฉด์ ๊ฐ์ฅ ์ฐ์ํ์ง๋ง ๋์ ์ฒ๋ฆฌ ์ฑ๋ฅ์ด ๋จ์ด์ง๋ค.
๊ฒฉ๋ฆฌ ์์ค์ ๋ฐ๋ฅธ ๋ฌธ์ ์
- DIRTY READ
- ํธ๋์ญ์ ์์ ์์ ์ด ๋ค ๋๋์ง ์์์ง๋ง ๋ค๋ฅธ ํธ๋์ญ์ ์์ ์์ ๋ด์ฉ์ ๋ณผ ์ ์๋ ๊ฒ์ DIRTY READ๋ผ๊ณ ํ๋ค.
- ๋ณ๊ฒฝ๋ ๋ฐ์ดํฐ๊ฐ ๋กค๋ฐฑ๋ ์ง, ์ปค๋ฐ๋ ์ง ๋ชจ๋ฅด๋ ์ํฉ์์ ์์ ๋ด์ฉ์ ์กฐํํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ ์ ํฉ์ฑ์ ํฐ ๋ฌธ์ ๋ฅผ ์ ๋ฐํ ์ ์๋ค.
- NON REPEATABLE READ
- ํ๋์ ํธ๋์ญ์ ์์ ๊ฐ์ SELECT๋ฌธ์ผ๋ก ์กฐํํ ๋ ๋ง๋ค ๋ค๋ฅธ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ ์ ์๋ค๋ฉด ์ด๋ฅผ NON REPEATABLE READ์ํ๋ผ๊ณ ๋ถ๋ฅธ๋ค.
- PHANTOM READ
- ํ๋์ ํธ๋์ญ์
์์ ๊ฐ์ SELECT๋ฌธ์ผ๋ก ์กฐํํ ๋ ์ด์ SELECT์์๋ ์กด์ฌํ์ง ์๋ ๊ฐ์ด ๋ค์ SELECT์ ์กฐํ๋๋ ๊ฒ์ ์๋ฏธํ๋ค.
- ํ๋์ ํธ๋์ญ์
์์ ๊ฐ์ SELECT๋ฌธ์ผ๋ก ์กฐํํ ๋ ์ด์ SELECT์์๋ ์กด์ฌํ์ง ์๋ ๊ฐ์ด ๋ค์ SELECT์ ์กฐํ๋๋ ๊ฒ์ ์๋ฏธํ๋ค.
ํธ๋์ญ์ ๊ฒฉ๋ฆฌ ์์ค๊ณผ ๋ฌธ์ ์ ์ ๋ฆฌ
๊ฒฉ๋ฆฌ ์์ค | DIRTY READ | NON-REPEATABLE READ | PHANTOM READ |
---|---|---|---|
READ UNCOMMITED | O | O | O |
READ COMMITTED | ย | O | O |
REPEATABLE READ | ย | ย | O |
SERIALIZABLE | ย | ย | ย |
16.1.2 ๋๊ด์ ๋ฝ๊ณผ ๋น๊ด์ ๋ฝ ๊ธฐ์ด
JPA
๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ๋์ญ์
๊ฒฉ๋ฆฌ ์์ค์ READ COMMITED
์ ๋๋ก ๊ฐ์ ํ๋ค. ๋ง์ฝ ์ผ๋ถ ๋ก์ง์ ๋ ๋์ ๊ฒฉ๋ฆฌ ์์ค์ด ํ์ํ๋ฉด ๋๊ด์ ๋ฝ๊ณผ ๋น๊ด์ ๋ฝ ์ค ํ๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
๋๊ด์ ๋ฝ
๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ ๊ณตํ๋ ๋ฝ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์๋๋ผ JPA๊ฐ ์ ๊ณตํ๋ ๋ฒ์ ๊ด๋ฆฌ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ค. ํธ๋์ญ์
์ ์ปค๋ฐํ๊ธฐ ์ ๊น์ง๋ ํธ๋์ญ์
์ ์ถฉ๋์ ์ ์ ์๋ค๋ ํน์ง์ด ์๋ค.
๋น๊ด์ ๋ฝ
ํธ๋์ญ์
์ ์ถฉ๋์ด ๋ฐ์ํ๋ค๊ณ ๊ฐ์ ํ๊ณ ์ฐ์ ๋ฝ์ ๊ฑธ๊ณ ๋ณด๋ ๋ฐฉ๋ฒ์ด๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ ๊ณตํ๋ ๋ฝ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ค. ๋ํ์ ์ผ๋ก select for update ๊ตฌ๋ฌธ์ด ์๋ค.
๋ ๋ฒ์ ๊ฐฑ์ ๋ถ์ค ๋ฌธ์
์ฌ์ฉ์ A๊ฐ ์์ ํ๊ณ ์ฌ์ฉ์ B๊ฐ 1์ด ๋ค์ ์์ ์์ฒญ์ ํ๊ฒ ๋๋ฉด ์ฌ์ฉ์ B์ ์์ ์ฌํญ๋ง ๋จ๊ฒ ๋๋ค. ์ด๊ฒ์ ๋ ๋ฒ์ ๊ฐฑ์ ๋ถ์ค ๋ฌธ์ ๋ผ ํ๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ 3๊ฐ์ง ์ ํ ๋ฐฉ๋ฒ์ด ์๋ค.
- ๋ง์ง๋ง ์ปค๋ฐ๋ง ์ธ์ ํ๊ธฐ: ์ฌ์ฉ์ A์ ๋ด์ฉ์ ๋ฌด์ํ๊ณ ๋ง์ง๋ง์ ์ปค๋ฐํ ์ฌ์ฉ์ B์ ๋ด์ฉ๋ง ์ธ์ ํ๋ค.
- ์ต์ด ์ปค๋ฐ๋ง ์ธ์ ํ๊ธฐ: ์ฌ์ฉ์ A๊ฐ ์ด๋ฏธ ์์ ์ ์๋ฃํ์ผ๋ฏ๋ก ์ฌ์ฉ์ B๊ฐ ์์ ์ ์๋ฃํ ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
- ์ถฉ๋ํ๋ ๊ฐฑ์ ๋ด์ฉ ๋ณํฉํ๊ธฐ: ์ฌ์ฉ์ A์ ์ฌ์ฉ์ B์ ์์ ์ฌํญ์ ๋ณํฉํ๋ค.
16.1.3 @Version
JPA๊ฐ ์ ๊ณตํ๋ ๋๊ด์ ๋ฝ์ ์ฌ์ฉํ๋ ค๋ฉด @Version
์ด๋
ธํ
์ด์
์ ์ฌ์ฉํด์ ๋ฒ์ ๊ด๋ฆฌ ๊ธฐ๋ฅ์ ์ถ๊ฐํด์ผ ํ๋ค.
@Version ์ ์ฉ ๊ฐ๋ฅ ํ์
- Long (long)
- Integer (int)
- Shoort (short)
- Timestamp
@Entity
public class Board {
@Id
private String id;
private String title;
@Version
private Integer version;
}
์ด์ ๋ถํฐ ์ํฐํฐ๋ฅผ ์์ ํ ๋ ๋ง๋ค ๋ฒ์ ์ด ํ๋์ฉ ์๋์ผ๋ก ์ฆ๊ฐํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ํฐํฐ๋ฅผ ์์ ํ ๋ ์กฐํ ์์ ์ ๋ฒ์ ๊ณผ ์์ ์์ ์ ๋ฒ์ ์ด ๋ค๋ฅด๋ฉด ์์ธ๊ฐ ๋ฐ์ํ๋ค.
// ํธ๋์ญ์
1 ์กฐํ title="์ ๋ชฉA", version=1
Board board = em.find(Board.class, id);
// ํธ๋์ญ์
2์์ ํด๋น ๊ฒ์๋ฌผ์ ์์ ํด์ title="์ ๋ชฉC", version=2๋ก ์ฆ๊ฐ
board.setTitle("์ ๋ชฉB"); // ํธ๋์ญ์
1 ๋ฐ์ดํฐ ์์
save(board);
tx.commit(); //์์ธ ๋ฐ์, ๋ฐ์ดํฐ๋ฒ ์ด์ค version=2, ์ํฐํฐ version=1
ํธ๋์ญ์ 1์ด ๋ฐ์ดํฐ๋ฅผ ์ ๋ชฉ B๋ก ๋ณ๊ฒฝํ๊ณ ํธ๋์ญ์ ์ ์ปค๋ฐํ๋ ์๊ฐ ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ๋ฒ์ ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ์ฌ ๋ฒ์ ์ ๋ณด๊ฐ ๋ค๋ฅด๋ฏ๋ก ์์ธ๊ฐ ๋ฐ์ํ๋ค. ๋ฐ๋ผ์ ๋ฒ์ ์ ๋ณด๋ฅผ ์ฌ์ฉํ๋ฉด ์ต์ด ์ปค๋ฐ๋ง ์ธ์ ํ๊ธฐ๊ฐ ์ ์ฉ๋๋ค.
@Version
์ผ๋ก ์ถ๊ฐํ ๋ฒ์ ๊ด๋ฆฌ ํ๋๋ JPA๊ฐ ์ง์ ๊ด๋ฆฌํ๋ฏ๋ก ๊ฐ๋ฐ์๊ฐ ์์๋ก ์์ ํ๋ฉด ์ ๋๋ค(๋ฒํฌ ์ฐ์ฐ ์ ์ธ) ๋ง์ฝ ๋ฒ์ ๊ฐ์ ๊ฐ์ ๋ก ์ฆ๊ฐ ํ๋ ค๋ฉด ํน๋ณํ ๋ฝ ์ต์
์ ์ ํํ๋ฉด ๋๋ค.
์ฐธ๊ณ
๋ฒํฌ ์ฐ์ฐ์ ๋ฒ์ ์ ๋ฌด์ํ๋ค. ๋ฒํฌ ์ฐ์ฐ์์ ๋ฒ์ ์ ์ฆ๊ฐํ๋ ค๋ฉด ๋ฒ์ ํ๋๋ฅผ ๊ฐ์ ๋ก ์ฆ๊ฐ์์ผ์ผ ํ๋ค.
update Member m set m.name = '๋ณ๊ฒฝ', m.version = m.version + 1
16.1.4 JPA ๋ฝ ์ฌ์ฉ
์ฐธ๊ณ
JPA๋ฅผ ์ฌ์ฉํ ๋ ์ถ์ฒํ๋ ์ ๋ต์ READ COMMITTED
ํธ๋์ญ์
๊ฒฉ๋ฆฌ ์์ค + ๋๊ด์ ๋ฒ์ ๊ด๋ฆฌ๋ค. (๋ ๋ฒ์ ๊ฐฑ์ ๋ด์ญ ๋ถ์ค ๋ฌธ์ ์๋ฐฉ)
๋ฝ์ ๋ค์ ์์น์ ์ ์ฉํ ์ ์๋ค.
- EntityManager.lock(), EntityManager.find(), EntityManager.refresh()
- Query.setLockMode() (TypeQuery ํฌํจ)
- @NamedQuery
JPA๊ฐ ์ ๊ณตํ๋ ๋ฝ ์ต์
์ javax.persistence.LockModeType
์ ์ ์๋์ด ์๋ค.
๋ฝ ๋ชจ๋ | ํ์ | ์ค๋ช |
---|---|---|
๋๊ด์ ๋ฝ | OPTIMISTIC | ๋๊ด์ ๋ฝ์ ์ฌ์ฉํ๋ค. |
๋๊ด์ ๋ฝ | OPTIMISTIC_FORCE_INCREMENT | ๋๊ด์ ๋ฝ + ๋ฒ์ ์ ๋ณด๋ฅผ ๊ฐ์ ๋ก ์ฆ๊ฐํ๋ค. |
๋น๊ด์ ๋ฝ | PESSIMISTIC_READ | ๋น๊ด์ ๋ฝ, ์ฝ๊ธฐ ๋ฝ์ ์ฌ์ฉํ๋ค. |
๋น๊ด์ ๋ฝ | PESSIMISTIC_WRITE | ๋น๊ด์ ๋ฝ, ์ฐ๊ธฐ ๋ฝ์ ์ฌ์ฉํ๋ค. |
๋น๊ด์ ๋ฝ | PESSIMISTIC_FORCE_INCREMENT | ๋น๊ด์ ๋ฝ, ๋ฒ์ ์ ๋ณด๋ฅผ ๊ฐ์ ๋ก ์ฆ๊ฐํ๋ค. |
๊ธฐํ | NONE | ๋ฝ์ ๊ฑธ์ง ์๋๋ค. |
๊ธฐํ | READ | JPA1.0 ํธํ ๊ธฐ๋ฅ์ด๋ค. OPTIMISTIC๊ณผ ๊ฐ์ผ๋ฏ๋ก OPTIMISTIC์ ์ฌ์ฉํ๋ฉด ๋๋ค. |
๊ธฐํ | WRITE | JPA1.0 ํธํ ๊ธฐ๋ฅ์ด๋ค. OPTIMISTIC_FORCE_INCREMENT์ ๊ฐ๋ค |
16.1.5 JPA ๋๊ด์ ๋ฝ
JPA๊ฐ ์ ๊ณตํ๋ ๋๊ด์ ๋ฝ์ ๋ฒ์ ์ ์ฌ์ฉํ๋ค. ๋ฐ๋ผ์ ๋๊ด์ ๋ฝ์ ์ฌ์ฉํ๋ ค๋ฉด ๋ฒ์ ์ด ์์ด์ผ ํ๋ค. ๋๊ด์ ๋ฝ์ ํธ๋์ญ์ ์ ์ปค๋ฐํ๋ ์์ ์ ์ถฉ๋์ ์ ์ ์๋ค๋ ํน์ง์ด ์๋ค. ๋๊ด์ ๋ฝ์ ์ต์ ์ ๋ฐ๋ฅธ ํจ๊ณผ๋ฅผ ์์๋ณด์.
NONE
๋ฝ ์ต์
์ ์ ์ฉํ์ง ์์๋ ์ํฐํฐ์ @Version
์ด ์ ์ฉ๋ ํ๋๋ง ์์ผ๋ฉด ๋๊ด์ ๋ฝ์ด ์ ์ฉ๋๋ค.
- ์ฉ๋: ์กฐํํ ์ํฐํฐ๋ฅผ ์์ ํ ๋ ๋ค๋ฅธ ํธ๋์ญ์ ์ ์ํด ๋ณ๊ฒฝ(์ญ์ )๋์ง ์์์ผ ํ๋ค. ์กฐํ ์์ ๋ถํฐ ์์ ์์ ๊น์ง๋ฅผ ๋ณด์ฅํ๋ค.
- ๋์: ์ํฐํฐ๋ฅผ ์์ ํ ๋ ๋ฒ์ ์ ์ฒดํฌํ๋ฉด์ ๋ฒ์ ์ ์ฆ๊ฐํ๋ค.(UPDATE ์ฟผ๋ฆฌ ์ฌ์ฉ). ์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฒ์ ๊ฐ์ด ํ์ฌ ๋ฒ์ ์ด ์๋๋ฉด ์์ธ๊ฐ ๋ฐ์ํ๋ค.
- ์ด์ : ๋ ๋ฒ์ ๊ฐฑ์ ๋ถ์ค ๋ฌธ์ ๋ฅผ ์๋ฐฉํ๋ค.
OPTIMISTIC
@Version
๋ง ์ ์ฉํ์ ๋๋ ์ํฐํฐ๋ฅผ ์์ ํด์ผ ๋ฒ์ ์ ์ฒดํฌํ์ง๋ง ์ด ์ต์
์ ์ถ๊ฐํ๋ฉด ์ํฐํฐ๋ฅผ ์กฐํ๋ง ํด๋ ๋ฒ์ ์ ์ฒดํฌํ๋ค. ํ ๋ฒ ์กฐํํ ์ํฐํฐ๋ ํธ๋์ญ์
์ ์ข
๋ฃํ ๋๊น์ง ๋ค๋ฅธ ํธ๋์ญ์
์์ ๋ณ๊ฒฝ๋์ง ์์์ ๋ณด์ฅํ๋ค.
- ์ฉ๋: ์กฐํ ์์ ๋ถํฐ ํธ๋์ญ์ ์ด ๋๋ ๋๊น์ง ์กฐํํ ์ํฐํฐ๊ฐ ๋ณ๊ฒฝ๋์ง ์์์ ๋ณด์ฅํ๋ค.
- ๋์: ํธ๋์ญ์ ์ ์ปค๋ฐํ ๋ ๋ฒ์ ์ ๋ณด๋ฅผ ์กฐํํด์ ํ์ฌ ์ํฐํฐ์ ๋ฒ์ ๊ณผ ๊ฐ์์ง ๊ฒ์ฆํ๋ค. ๋ง์ฝ ๊ฐ์ง ์์ผ๋ฉด ์์ธ๊ฐ ๋ฐ์ํ๋ค.
- ์ด์ :
OPTIMISTIC
์ต์ ์DIRTY READ
์NON-REPEATABLE READ
๋ฅผ ๋ฐฉ์งํ๋ค.
OPTIMISTIC_FORCE_INCREMENT
ํธ๋์ญ์
์ ์ปค๋ฐํ ๋ UPDATE ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ ๋ฒ์ ์ ๋ณด๋ฅผ ๊ฐ์ ๋ก ์ฆ๊ฐ์ํจ๋ค.
- ์ฉ๋: ๋
ผ๋ฆฌ์ ์ธ ๋จ์์ ์ํฐํฐ ๋ฌถ์์ ๊ด๋ฆฌํ ์ ์๋ค. ๊ฒ์๋ฌผ๊ณผ ์ฒจ๋ถํ์ผ์ด ์ผ๋๋ค, ๋ค๋์ผ์ ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ์ด๊ณ ์ฒจ๋ถํ์ผ์ด ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ด๋ค. ๊ฒ์๋ฌผ์ ์์ ํ๋ ๋ฐ ๋จ์ํ ์ฒจ๋ถํ์ผ๋ง ์ถ๊ฐํ๋ฉด ๊ฒ์๋ฌผ์ ๋ฒ์ ์ ์ฆ๊ฐํ์ง ์๋๋ค. ์ด๋ ๊ฒ์๋ฌผ์ ๋ฒ์ ๋ ๊ฐ์ ๋ก ์ฆ๊ฐํ๋ ค๋ฉด
OPTIMISTIC_FORCE_INCREMENT
๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค. - ๋์: ํธ๋์ญ์ ์ ์ปค๋ฐํ ๋ UPDATE ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ ๋ฒ์ ์ ๋ณด๋ฅผ ๊ฐ์ ๋ก ์ฆ๊ฐ์ํจ๋ค. ์ถ๊ฐ๋ก ์ํฐํฐ๋ฅผ ์์ ํ๋ฉด ์์ ์ ๋ฒ์ UPDATE๊ฐ ๋ฐ์ํ๋ค. ๋ฐ๋ผ์ ์ด 2๋ฒ์ ๋ฒ์ ์ฆ๊ฐ๊ฐ ๋ํ๋ ์ ์๋ค.
- ์ด์ : ๊ฐ์ ๋ก ๋ฒ์ ์ ์ฆ๊ฐํด์ ๋
ผ๋ฆฌ์ ์ธ ๋จ์์ ์ํฐํฐ ๋ฌถ์์ ๋ฒ์ ๊ด๋ฆฌํ ์ ์๋ค.
16.1.6 JPA ๋น๊ด์ ๋ฝ
JPA๊ฐ ์ ๊ณตํ๋ ๋น๊ด์ ๋ฝ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ๋์ญ์
๋ฝ ๋ฉ์ปค๋์ฆ์ ์์กดํ๋ ๋ฐฉ๋ฒ์ด๋ค. ์ฃผ๋ก SQL ์ฟผ๋ฆฌ์ select for update
๊ตฌ๋ฌธ์ ์ฌ์ฉํ๋ฉด์ ์์ํ๊ณ ๋ฒ์ ์ ๋ณด๋ ์ฌ์ฉํ์ง ์๋๋ค.
๋น๊ด์ ๋ฝ์ ํน์ง
- ์ํฐํฐ๊ฐ ์๋ ์ค์นผ๋ผ ํ์ ์ ์กฐํํ ๋๋ ์ฌ์ฉํ ์ ์๋ค.
- ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ ์ฆ์ ํธ๋์ญ์
์ถฉ๋์ ๊ฐ์งํ ์ ์๋ค.
PESSIMISTIC_WRITE
- ์ฉ๋: ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๊ธฐ ๋ฝ์ ๊ฑด๋ค
- ๋์: ๋ฐ์ดํฐ๋ฒ ์ด์ค
select for update
๋ฅผ ์ฌ์ฉํด์ ๋ฝ์ ๊ฑด๋ค. - ์ด์ :
NON-REPEATABLE READ
๋ฅผ ๋ฐฉ์งํ๋ค. ๋ฝ์ด ๊ฑธ๋ฆฐ ๋ก์ฐ๋ ๋ค๋ฅธ ํธ๋์ญ์ ์ด ์์ ํ ์ ์๋ค.
PESSIMISTIC_READ
๋ฐ์ดํฐ๋ฅผ ๋ฐ๋ณต ์ฝ๊ธฐ๋ง ํ๊ณ ์์ ํ์ง ์๋ ์ฉ๋๋ก ๋ฝ์ ๊ฑธ ๋ ์ฌ์ฉํ๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋๋ถ๋ถ์ PESSIMISTIC_WRITE
๋ก ๋์ํ๋ค.
- MySQL: lock in share mode
- PostgreSQL: for share
PESSIMISTIC_FORCE_INCREMENT
๋น๊ด์ ๋ฝ์ค ์ ์ผํ๊ฒ ๋ฒ์ ์ ๋ณด๋ฅผ ์ฌ์ฉํ๋ค. ํ์ด๋ฒ๋ค์ดํธ๋ nowait
๋ฅผ ์ง์ํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํด์ for update nowait
์ต์
์ ์ ์ฉํ๋ค.
- ์ค๋ผํด: for update nowait
- PostreSQL: for update nowait
- nowait๋ฅผ ์ง์ํ์ง ์์ผ๋ฉด for update๊ฐ ์ฌ์ฉ๋๋ค.
16.1.7 ๋น๊ด์ ๋ฝ๊ณผ ํ์์์
๋น๊ด์ ๋ฝ์ ์ฌ์ฉํ๋ฉด ๋ฝ์ ํ๋ํ ๋๊น์ง ํธ๋์ญ์ ์ด ๋๊ธฐํ๋ค. ๋ฌดํ์ ๊ธฐ๋ค๋ฆด ์๋ ์์ผ๋ฏ๋ก ํ์์์ ์๊ฐ์ ์ค ์ ์๋ค.
Map<String, Object> properties = new HashMap<String, Object>();
// ํ์์์ 10์ด๊น์ง ๋๊ธฐ ์ค์
properties.put("java.persistence.lock.timeout", 10000);
Board board = em.find(Board.class, "boardId", LockModeType.PESSIMISTIC_WRITE, properties);
16.2 2์ฐจ ์บ์
JPA๊ฐ ์ ๊ณตํ๋ ์ ํ๋ฆฌ์ผ์ด์ ๋ฒ์์ ์บ์์ ๋ํด ์์๋ณด๊ณ ํ์ด๋ฒ๋ค์ดํธ์ EHCACHE๋ฅผ ์ฌ์ฉํด์ ์ค์ ์บ์๋ฅผ ์ ์ฉํด๋ณด์.
16.2.1 1์ฐจ ์บ์์ 2์ฐจ ์บ์
์์์ฑ ์ปจํ ์คํธ๋ก ์ป์ ์ ์๋ ์ด์ ์ด ๋ง์ง๋ง, ์ผ๋ฐ์ ์ธ ์น ์ ํ๋ฆฌ์ผ์ด์ ํ๊ฒฝ์ ํธ๋์ญ์ ์ ์์ํ๊ณ ์ข ๋ฃํ ๋๊น์ง๋ง 1์ฐจ ์บ์๊ฐ ์ ํจํ๋ค. OSIV๋ฅผ ์ฌ์ฉํด๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ด ๋ค์ด์ฌ ๋๋ถํฐ ๋๋ ๋๊น์ง๋ง 1์ฐจ ์บ์๊ฐ ์ ํจํ๋ค. ๋ฐ๋ผ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด๋ก ๋ณด๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ ํ์๋ฅผ ํ๊ธฐ์ ์ผ๋ก ์ค์ด์ง๋ ๋ชปํ๋ค. ํ์ด๋ฒ๋ค์ดํธ๋ฅผ ํฌํจํ ๋๋ถ๋ถ์ JPA ๊ตฌํ์ฒด๋ค์ ์ ํ๋ฆฌ์ผ์ด์ ๋ฒ์์ ์บ์๋ฅผ ์ง์ํ๋๋ฐ ์ด๊ฒ์ ๊ณต์ ์บ์ ๋๋ 2์ฐจ ์บ์๋ผ ํ๋ค.
1์ฐจ ์บ์
1์ฐจ ์บ์๋ ์์์ฑ ์ปจํ
์คํธ ๋ด๋ถ์ ์๋ค. ์ํฐํฐ ๋งค๋์ ๋ก ์กฐํํ๊ฑฐ๋ ๋ณ๊ฒฝํ๋ ๋ชจ๋ ์ํฐํฐ๋ 1์ฐจ ์บ์์ ์ ์ฅ๋๋ค. ํธ๋์ญ์
์ ์ปค๋ฐํ๊ฑฐ๋ ํ๋ฌ์๋ฅผ ํธ์ถํ๋ฉด 1์ฐจ ์บ์์ ์๋ ์ํฐํฐ์ ๋ณ๊ฒฝ ๋ด์ญ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋๊ธฐํ ํ๋ค.
2์ฐจ ์บ์
2์ฐจ ์บ์๋ ์ ํ๋ฆฌ์ผ์ด์
๋ฒ์์ ์บ์๋ค. ๋ฐ๋ผ์ ์ ํ๋ฆฌ์ผ์ด์
์ ์ข
๋ฃํ ๋๊น์ง ์บ์๊ฐ ์ ์ง๋๋ค. ๋ถ์ฐ ์บ์๋ ํด๋ฌ์คํฐ๋ง ํ๊ฒฝ์ ์บ์๋ ์ ํ๋ฆฌ์ผ์ด์
๋ณด๋ค ๋ ์ค๋ ์ ์ง๋ ์๋ ์๋ค. 2์ฐจ ์บ์๋ฅผ ์ ์ ํ ํ์ฉํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ ํ์๋ฅผ ํ๊ธฐ์ ์ผ๋ก ์ค์ผ ์ ์๋ค.
2์ฐจ ์บ์์ ํน์ง
- 2์ฐจ ์บ์๋ ์์์ฑ ์ ๋ ๋ฒ์์ ์บ์๋ค
- 2์ฐจ ์บ์๋ ์กฐํํ ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ๋ฐํํ๋ ๊ฒ์ด ์๋๋ผ ๋ณต์ฌ๋ณธ์ ๋ง๋ค์ด์ ๋ฐํํ๋ค.
- 2์ฐจ ์บ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ธฐ๋ณธ ํค๋ฅผ ๊ธฐ์ค์ผ๋ก ์บ์ํ์ง๋ง ์์์ฑ ์ปจํ
์คํธ๊ฐ ๋ค๋ฅด๋ฉด ๊ฐ์ฒด
๋์ผ์ฑ(a==b)
์ ๋ณด์ฅํ์ง ์๋๋ค.
16.2.2 JPA 2์ฐจ ์บ์ ๊ธฐ๋ฅ
์บ์ ๋ชจ๋ ์ค์
2์ฐจ ์บ์๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด javax.persistence.Cacheable
์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ๋ฉด ๋๋ค. @Cacheable
์ @Cacheable(true)
, @Cacheable(false)
๋ฅผ ์ค์ ํ ์ ์๋๋ฐ ๊ธฐ๋ณธ๊ฐ์ true
๋ค.
@Cacheable
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
...
}
shared-cache-mode
๋ฅผ ์ค์ ํด์ ์ ํ๋ฆฌ์ผ์ด์
์ ์ฒด(์ ํํ๋ ์์์ฑ ์ ๋ ๋จ์)์ ์บ์๋ฅผ ์ด๋ป๊ฒ ์ ์ฉํ ์ง ์ต์
์ ์ค์ ํด์ผ ํ๋ค.
<!-- ์คํ๋ง ํ๋ ์์ํฌ ์บ์ ๋ชจ๋ ์ค์ -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="sharedCacheMode" value="ENABLE_SELECTIVE">
...
์บ์ ๋ชจ๋๋ javax.persistence.SharedCacheMode
์ ์ ์๋์ด ์๋ค.
์บ์ ๋ชจ๋ | ์ค๋ช |
---|---|
ALL | ๋ชจ๋ ์ํฐํฐ๋ฅผ ์บ์ํ๋ค. |
NONE | ์บ์๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค. |
ENABLE_SELECTIVE | Cacheable(true)๋ก ์ค์ ๋ ์ํฐํฐ๋ง ์บ์๋ฅผ ์ ์ฉํ๋ค. |
DISABLE_SELECTIVE | ๋ชจ๋ ์ํฐํฐ๋ฅผ ์บ์ํ๋๋ฐ Cacheable(false)๋ก ๋ช ์๋ ์ํฐํฐ๋ ์บ์ํ์ง ์๋๋ค. |
UNSPECIFIED | JPA ๊ตฌํ์ฒด๊ฐ ์ ์ํ ์ค์ ์ ๋ฐ๋ฅธ๋ค. |
16.2.3 ํ์ด๋ฒ๋ค์ดํธ์ EHCACHE ์ ์ฉ
ํ์ด๋ฒ๋ค์ดํธ๊ฐ ์ง์ํ๋ ์บ์๋ ํฌ๊ฒ 3๊ฐ์ง๊ฐ ์๋ค.
- ์ํฐํฐ ์บ์: ์ํฐํฐ ๋จ์๋ก ์บ์ํ๋ค. ์๋ณ์๋ก ์ํฐํฐ๋ฅผ ์กฐํํ๊ฑฐ๋ ์ปฌ๋ ์ ์ด ์๋ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ๋ก๋ฉํ ๋ ์ฌ์ฉํ๋ค.
- ์ปฌ๋ ์ ์บ์: ์ํฐํฐ์ ์ฐ๊ด๋ ์ปฌ๋ ์ ์ ์บ์ํ๋ค. ์ปฌ๋ ์ ์ด ์ํฐํฐ๋ฅผ ๋ด๊ณ ์์ผ๋ฉด ์๋ณ์ ๊ฐ๋ง ์บ์ํ๋ค(ํ์ด๋ฒ๋ค์ดํธ ๊ธฐ๋ฅ)
- ์ฟผ๋ฆฌ ์บ์: ์ฟผ๋ฆฌ์ ํ๋ผ๋ฏธํฐ ์ ๋ณด๋ฅผ ํค๋ก ์ฌ์ฉํด์ ์บ์ํ๋ค. ๊ฒฐ๊ณผ๊ฐ ์ํฐํฐ๋ฉด ์๋ณ์ ๊ฐ๋ง ์บ์ํ๋ค.(ํ์ด๋ฒ๋ค์ดํธ ๊ธฐ๋ฅ)
ํ๊ฒฝ์ค์
๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>4.3.10.Final</version>
</dependency>
์บ์ ์ ์ฑ ์ค์
<!-- src/main/resources/ehcache.xml -->
<ehcache>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="1200"
timeToLiveSeconds="1200"
diskExpiryThreadIntervalSeconds="1200"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
ํ์ด๋ฒ๋ค์ดํธ ์บ์ ์ฌ์ฉ ์ ๋ณด๋ฅผ ์ ์
# application.yml
spring:
jpa:
properties:
hibernate:
generate_statistics: true
format_sql: true
cache:
use_second_level_cache: true
region:
factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory // EHCACHE
javax:
persistence:
sharedCache:
mode: ENABLE_SELECTIVE
- hibernate.cache.use_second_level_cache: 2์ฐจ ์บ์๋ฅผ ํ์ฑํ ํ๋ค. ์ํฐํฐ ์บ์์ ์ปฌ๋ ์ ์บ์๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
- hibernate.cache.use_query_cach: ์ฟผ๋ฆฌ ์บ์๋ฅผ ํ์ฑํ ํ๋ค.
- hibernate.cache.region.factory_class: 2์ฐจ ์บ์๋ฅผ ์ฒ๋ฆฌํ ํด๋์ค๋ฅผ ์ง์ ํ๋ค.
- hibernate.generate_statistics: ์ด ์์ฑ์ true๋ก ์ค์ ํ๋ฉด ํ์ด๋ฒ๋ค์ดํธ๊ฐ ์ฌ๋ฌ ํต๊ณ์ ๋ณด๋ฅผ ์ถ๋ ฅํด์ฃผ๋๋ฐ ์บ์ ์ ์ฉ ์ฌ๋ถ๋ฅผ ํ์ธํ ์ ์๋ค.(์ฑ๋ฅ์ ์ํฅ์ ์ฃผ๋ฏ๋ก ๊ฐ๋ฐ ํ๊ฒฝ์์๋ง ์ ์ฉํ๋ ๊ฒ์ด ์ข๋ค)
@Cache
ํ์ด๋ฒ๋ค์ดํธ ์ ์ฉ์ธ @Cache
์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ๋ฉด ์ธ๋ฐํ ์บ์ ์ค์ ์ด ๊ฐ๋ฅํ๋ค.
@Cacheable // ์ํฐํฐ ์บ์
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) // ํ์ด๋ฒ๋ค์ดํธ ์ ์ฉ, ์บ์์ ๊ด๋ จ๋ ๋ ์ธ๋ฐํ ์ค์ ๊ฐ๋ฅ
@Entity
public class ParentMember {
@Id @GeneratedValue
private Long id;
private String name;
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) // ์ปฌ๋ ์
๋ ์บ์ ์ ์ฉ ๊ฐ๋ฅ
@OneToMany(mappedBy = "parentMember", cascade = CascadeType.ALL)
private List<ChildMember> childMembers;
}
ํ์ด๋ฒ๋ค์ดํธ @Cache
์์ฑ
์์ฑ | ์ค๋ช |
---|---|
usage | CacheConcurrencyStrategy๋ฅผ ์ฌ์ฉํด์ ์บ์ ๋์์ฑ ์ ๋ต์ ์ค์ ํ๋ค. |
region | ์บ์ ์ง์ญ ์ค์ |
include | ์ฐ๊ด ๊ฐ์ฒด๋ฅผ ์บ์์ ํฌํจํ ์ง ์ ํํ๋ค. all, non-laze ์ต์ ์ ์ ํํ ์ ์๋ค.(๊ธฐ๋ณธ๊ฐ all) |
CacheConcurrencyStrategy
์์ฑ
์์ฑ | ์ค๋ช |
---|---|
NONE | ์บ์๋ฅผ ์ค์ ํ์ง ์๋๋ค. |
READ_ONLY | ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ์ค์ ํ๋ค. ๋ฑ๋ก, ์ญ์ ๋ ๊ฐ๋ฅํ์ง๋ง ์์ ์ ๋ถ๊ฐ๋ฅํ๋ค. ์ฐธ๊ณ ๋ก ์ฝ๊ธฐ ์ ์ฉ์ธ ๋ถ๋ณ ๊ฐ์ฒด๋ ์์ ๋์ง ์์ผ๋ฏ๋ก ํ์ด๋ฒ๋ค์ดํธ๋ 2์ฐจ ์บ์๋ฅผ ์กฐํํ ๋ ๊ฐ์ฒด๋ฅผ ๋ณต์ฌํ์ง ์๊ณ ์๋ณธ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค. |
NONSTRICT_READ_WRITE | ์๊ฒฉํ์ง ์์ ์ฝ๊ณ ์ฐ๊ธฐ ์ ๋ต์ด๋ค. ๋์์ ๊ฐ์ ์ํฐํฐ๋ฅผ ์์ ํ๋ฉด ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ด ๊นจ์ง ์ ์๋ค. EHCACHE๋ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ฉด ์บ์ ๋ฐ์ดํฐ๋ฅผ ๋ฌดํจํํ๋ค. |
READ_WRITE | ์ฝ๊ธฐ ์ฐ๊ธฐ๊ฐ ๊ฐ๋ฅํ๊ณ READ COMMITTED ์ ๋์ ๊ฒฉ๋ฆฌ ์์ค์ ๋ณด์ฅํ๋ค. |
TRANSACTIONAL | ์ปจํ ์ด๋ ๊ด๋ฆฌ ํ๊ฒฝ์์ ์ฌ์ฉํ ์ ์๋ค. ์ค์ ์ ๋ฐ๋ผ REPEATABLE READ ์ ๋์ ๊ฒฉ๋ฆฌ ์์ค์ ๋ณด์ฅ๋ฐ์ ์ ์๋ค. |
์บ์ ๋์์ฑ ์ ๋ต ์ง์ ์ฌ๋ถ
Cache | read-only | nonstrict-read-write | nonstrict-read-write | transactional |
---|---|---|---|---|
ConcurrentHashMap | yes | yes | yes | ย |
EHCache | yes | yes | yes | yes |
Infinispan | yes | ย | ย | yes |
์บ์ ์์ญ
์์์ ์บ์๋ฅผ ์ ์ฉํ ์ฝ๋(์ํฐํฐ ์ฝ๋)๋ ๋ค์ ์บ์ ์์ญ์ ์ ์ฅ๋๋ค.
- ์ํฐํฐ ์บ์ ์์ญ
jpabook.jpashop.domain.test.cache.ParentMember
- ๊ธฐ๋ณธ๊ฐ์ผ๋ก [ํจํค์ง ๋ช + ํด๋์ค ๋ช ]์ ์ฌ์ฉ
- ์ปฌ๋ ์
์บ์ ์์ญ
jpabook.jpashop.domain.test.cache.ParentMember.childMembers
- ์ํฐํฐ ์บ์ ์์ญ ์ด๋ฆ + ์บ์ํ ์ปฌ๋ ์ ์ ํ๋ ๋ช
ํ์ํ๋ค๋ฉด @Cache(region = "customRegion", ...)
์ฒ๋ผ region
์์ฑ์ ์ฌ์ฉํด์ ์บ์ ์์ญ์ ์ง์ ์ง์ ํ ์ ์๋ค.
์บ์ ์์ญ์ด ์ ํด์ ธ ์์ผ๋ฉด ์์ญ๋ณ๋ก ์ธ๋ถ ์ค์ ์ ํ ์ ์๋ค.
<ehcache>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="1200"
timeToLiveSeconds="1200"
diskExpiryThreadIntervalSeconds="1200"
memoryStoreEvictionPolicy="LRU" />
<!-- ParentMember๋ฅผ 600์ด ๋ง๋ค ์บ์์์ ์ ๊ฑฐ -->
<cache
name="jpabook.jpashop.domain.test.cache.ParentMember"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
overflowToDisk="false" />
</ehcache>
์ฟผ๋ฆฌ ์บ์
- ์ฟผ๋ฆฌ ์บ์๋ ์ฟผ๋ฆฌ์ ํ๋ผ๋ฏธํฐ ์ ๋ณด๋ฅผ ํค๋ก ์ฌ์ฉํด์ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ์บ์ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
- ์ฟผ๋ฆฌ ์บ์๋ฅผ ์ ์ฉํ๋ ค๋ฉด ์์์ฑ ์ ๋ ์ค์ ์
hibernate.cache.use_query_cache
์ต์ ์ ๊ผญtrue
๋ก ์ค์ ํด์ผ ํ๋ค. - ๊ทธ๋ฆฌ๊ณ ์ฟผ๋ฆฌ ์บ์๋ฅผ ์ ์ฉํ๋ ค๋ ์ฟผ๋ฆฌ๋ง๋ค
org.hibernate.cacheable
์true
๋ก ์ค์ ํ๋ ํํธ๋ฅผ ์ฃผ๋ฉด ๋๋ค
// ์ฟผ๋ฆฌ ์บ์ ์ฌ์ฉ
em.createQuery("select i from Item i", Item.class)
.setHint("org.hibernate.cacheable", true)
.getResultList();
// @NamedQuery์ ์ฟผ๋ฆฌ ์บ์ ์ ์ฉ
@Entity
@NamedQuery(
hints = @QueryHint(name = "org.hibernate.cacheable", value = "true"),
name = "Member.findByUsername",
query = "select m.address from Member m where m.name = :username"
)
public class Member {...}
์ฟผ๋ฆฌ ์บ์ ์์ญ
hibernate.cache.use_query_cache
์ต์
์ true
๋ก ์ค์ ํด์ ์ฟผ๋ฆฌ ์บ์๋ฅผ ํ์ฑํ ํ๋ฉด ๋ค์ ๋ ์บ์ ์์ญ์ด ์ถ๊ฐ๋๋ค.
org.hibernate.cache.internal.StandardQueryCache
: ์ฟผ๋ฆฌ ์บ์๋ฅผ ์ ์ฅํ๋ ์์ญ์ด๋ค. ์ด๊ณณ์๋ ์ฟผ๋ฆฌ, ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ์งํฉ, ์ฟผ๋ฆฌ๋ฅผ ์คํํ ์์ ์ ํ์์คํฌํ๋ฅผ ๋ณด๊ดํ๋ค.org.hibernate.cache.spi.UpdateTimestampsCache
: ์ฟผ๋ฆฌ ์บ์๊ฐ ์ ํจํ์ง ํ์ธํ๊ธฐ ์ํด ์ฟผ๋ฆฌ ๋์ ํ ์ด๋ธ์ ๊ฐ์ฅ ์ต๊ทผ ๋ณ๊ฒฝ(๋ฑ๋ก, ์์ , ์ญ์ ) ์๊ฐ์ ์ ์ฅํ๋ ์์ญ์ด๋ค. ์ด๊ณณ์๋ ํ ์ด๋ธ ๋ช ๊ณผ ํด๋น ํ ์ด๋ธ์ ์ต๊ทผ ๋ณ๊ฒฝ๋ ํ์์คํฌํ๋ฅผ ๋ณด๊ดํ๋ค.
์ฟผ๋ฆฌ ์บ์๋ ์บ์ํ ๋ฐ์ดํฐ ์งํฉ์ ์ต์ ๋ฐ์ดํฐ๋ก ์ ์งํ๋ ค๊ณ ์ฟผ๋ฆฌ ์บ์๋ฅผ ์คํํ๋ ์๊ฐ๊ณผ ์ฟผ๋ฆฌ ์บ์๊ฐ ์ฌ์ฉํ๋ ํ ์ด๋ธ๋ค์ด ๊ฐ์ฅ ์ต๊ทผ์ ๋ณ๊ฒฝ๋ ์๊ฐ์ ๋น๊ตํ๋ค. ์ฟผ๋ฆฌ ์บ์๋ฅผ ์ ์ฉํ๊ณ ๋ ํ์ ์ฟผ๋ฆฌ ์บ์๊ฐ ์ฌ์ฉํ๋ ํ ์ด๋ธ์ ์กฐ๊ธ์ด๋ผ๋ ๋ณ๊ฒฝ์ด ์์ผ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด์์ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ ์บ์ํ๋ค.
์ฟผ๋ฆฌ ์บ์๋ฅผ ์ ํ์ฉํ๋ฉด ๊ทน์ ์ธ ์ฑ๋ฅ ํฅ์์ด ์์ง๋ง ๋น๋ฒํ๊ฒ ๋ณ๊ฒฝ์ด ์๋ ํ ์ด๋ธ์ ์ฌ์ฉํ๋ฉด ์คํ๋ ค ์ฑ๋ฅ์ด ๋ ์ ํ๋๋ค. ๋ฐ๋ผ์ ์์ ์ด ๊ฑฐ์ ์ผ์ด๋์ง ์๋ ํ ์ด๋ธ์ ์ฌ์ฉํด์ผ ํจ๊ณผ๋ฅผ ๋ณผ ์ ์๋ค.
์ฃผ์
org.hibernate.cache.spi.UpdateTimestampsCache
์ฟผ๋ฆฌ ์บ์ ์์ญ์ ๋ง๋ฃ๋์ง ์๋๋ก ์ค์ ํด์ผ ํ๋ค. ํด๋น ์์ญ์ด ๋ง๋ฃ๋๋ฉด ๋ชจ๋ ์ฟผ๋ฆฌ ์บ์๊ฐ ๋ฌดํจํ๋๋ค. EHCACHE
์ eternal="true"
์ต์
์ ์ฌ์ฉํ๋ฉด ์บ์์์ ์ญ์ ๋์ง ์๋๋ค.
<cache
name="org.hibernate.cache.spi.UpdateTimestampsCache"
maxElementsInMemory="10000"
eternal="true" />
์ฟผ๋ฆฌ ์บ์์ ์ปฌ๋ ์
์บ์์ ์ฃผ์์
์ํฐํฐ ์บ์๋ฅผ ์ฌ์ฉํด์ ์ํฐํฐ๋ฅผ ์บ์ํ๋ฉด ์ํฐํฐ ์ ๋ณด๋ฅผ ๋ชจ๋ ์บ์ํ์ง๋ง ์ฟผ๋ฆฌ ์บ์์ ์ปฌ๋ ์
์บ์๋ ๊ฒฐ๊ณผ ์งํฉ์ ์๋ณ์ ๊ฐ๋ง ์บ์ํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ์๋ณ์ ๊ฐ์ ํ๋์ฉ ์ํฐํฐ ์บ์์ ์กฐํํด์ ์ค์ ์ํฐํฐ๋ฅผ ์ฐพ๋๋ค.
๋ฌธ์ ๋ ์ฟผ๋ฆฌ ์บ์๋ ์ปฌ๋ ์ ์บ์๋ง ์ฌ์ฉํ๊ณ ๋์ ์ํฐํฐ์ ์ํฐํฐ ์บ์๋ฅผ ์ ์ฉํ์ง ์์ผ๋ฉด ์ฑ๋ฅ์ ์ฌ๊ฐํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
select m from Member m
์ฟผ๋ฆฌ๋ฅผ ์คํํ๋๋ฐ ์ฟผ๋ฆฌ ์บ์๊ฐ ์ ์ฉ๋์ด ์๋ค. ์กฐํ ๊ฒฐ๊ณผ๋ 100๊ฑด์ด๋ค.- ๊ฒฐ๊ณผ ์งํฉ์๋ ์๋ณ์๋ง ์๋ฅด๋ฏ๋ก ํ ๊ฑด์ฉ ์ํฐํฐ ์บ์ ์์ญ์์ ์กฐํํ๋ค.
Member
์ํฐํฐ๋ ์ํฐํฐ ์บ์๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก ํ ๊ฑด์ฉ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํํ๋ค.- 100๊ฑด์ SQL์ด ์คํ๋๋ค.
๋ฐ๋ผ์ ์ฟผ๋ฆฌ ์บ์๋ ์ปฌ๋ ์ ์บ์๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฒฐ๊ณผ ๋์ ์ํฐํฐ์๋ ๊ผญ ์ํฐํฐ ์บ์๋ฅผ ์ ์ฉํด์ผ ํ๋ค.
16.3 ์ ๋ฆฌ
- ํธ๋์ญ์ ์ ๊ฒฉ๋ฆฌ ์์ค์ 4๋จ๊ณ๊ฐ ์๋ค. ๊ฒฉ๋ฆฌ ์์ค์ด ๋ฎ์์๋ก ๋์์ฑ์ ์ฆ๊ฐํ์ง๋ง ๊ฒฉ๋ฆฌ ์์ค์ ๋ฐ๋ฅธ ๋ค์ํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
- ์์์ฑ ์ปจํ
์คํธ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ๋์ญ์
์ด
READ COMMITTED
๊ฒฉ๋ฆฌ ์์ค์ด์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ๋ ๋ฒจ์์ ๋ฐ๋ณต ๊ฐ๋ฅํ ์ฝ๊ธฐ(REPEATABLE READ
)๋ฅผ ์ ๊ณตํ๋ค. - JPA๋ ๋๊ด์ ๋ฝ๊ณผ ๋น๊ด์ ๋ฝ์ ์ง์ํ๋ค. ๋๊ด์ ๋ฝ์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ง์ํ๋ ๋ฝ์ด๊ณ , ๋น๊ด์ ๋ฝ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ๋์ญ์ ๋ฝ ๋ฉ์ปค๋์ฆ์ ์์กดํ๋ค.
- 2์ฐจ ์บ์๋ฅผ ์ฌ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ์กฐํ ์ฑ๋ฅ ๊ทน์ ์ผ๋ก ๋์ด์ฌ๋ฆด ์ ์๋ค.
๊ฐ์ฌํฉ๋๋ค ๐๐ปโโ๏ธ
๋๊ธ๋จ๊ธฐ๊ธฐ