JPA Programming Basic

JPA Programming Basic

์˜ํ•œ๋‹˜์˜ ์ž๋ฐ” ORM ํ‘œ์ค€ JPA ํ”„๋กœ๊ทธ๋ž˜๋ฐ - ๊ธฐ๋ณธํŽธ ๊ฐ•์˜๋ฅผ ์š”์•ฝํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

Project

Intro

JPA

  • Java Persistence API

  • ์ž๋ฐ” ์ง„์˜์˜ ORM ๊ธฐ์ˆ  ํ‘œ์ค€ (์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ชจ์Œ)

    • ๊ตฌํ˜„์ฒด๋กœ๋Š” Hibernate, EclipseLink, DataNucleus..

  • Application๊ณผ JDBC ์‚ฌ์ด์—์„œ ๋™์ž‘

ROM

  • Object-Relational Mapping

  • Object๋Š” Object๋Œ€๋กœ, RDBMS๋Š” RDBMS๋Œ€๋กœ ์„ค๊ณ„

  • ORM ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ค‘๊ฐ„์—์„œ ๋งคํ•‘

EntityManagerFactory

  • persistence.xml ์„ค์ • ์ •๋ณด ํ™•์ธ ํ›„ persistence-unit name ์— ๋งž๋Š” EntityManagerFactory ์ƒ์„ฑ

  • Web Server ๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ์‹œ์ ์— ํ•˜๋‚˜๋งŒ ์ƒ์„ฑํ•ด์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์—์„œ ๊ณต์œ 

    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

EntityManager

  • ์š”์ฒญ ๊ฑด๋งˆ๋‹ค ์ƒ์„ฑ

  • ์“ฐ๋ ˆ๋“œ๊ฐ„ ๊ณต์œ ํ•˜๋ฉด ์•ˆ ๋˜๊ณ , ์‚ฌ์šฉ ํ›„ ์ข…๋ฃŒ

    EntityManager em = emf.createEntityManager();

EntityTransaction

  • JPA์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์€ ํŠธ๋žœ์ ์…˜ ์•ˆ์—์„œ ์‹คํ–‰

    EntityTransaction tx = em.getTransaction();
    tx.begin();

์˜์†์„ฑ ๊ด€๋ฆฌ

์˜์†์„ฑ ์ปจํ…์ŠคํŠธ

PersistenceContext (์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜๊ตฌ ์ €์žฅํ•˜๋Š” ํ™˜๊ฒฝ)

  • EntityManager ๋ฅผ ํ†ตํ•ด PersistenceContext ์— ์ ‘๊ทผ

    • EntityManager, PersistenceContext ๋Š” 1:1, N:1 ๊ด€๊ณ„ ์กด์žฌ

  • ์—”ํ‹ฐํ‹ฐ ์ƒ๋ช…์ฃผ๊ธฐ

    • ๋น„์˜์† (new / transient)

      • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์™€ ์ „ํ˜€ ๊ด€๊ณ„๊ฐ€ ์—†๋Š” ์ƒˆ๋กœ์šด ์ƒํƒœ

        Member member = new Member();
        member.setId(1L);
        member.setName("Aaron");
    • ์˜์† (managed)

      • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ๊ด€๋ฆฌ๋˜๋Š” ์ƒํƒœ

        entityManager.persist(member);
    • ์ค€์˜์† (detached)

      • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์ €์žฅ๋˜์—ˆ๋‹ค๊ฐ€ ๋ถ„๋ฆฌ๋œ ์ƒํƒœ

        entityManager.detach(member);
        entityManager.clear()
        entityManager.close()
    • ์‚ญ์ œ (removed)

      • ์‚ญ์ œ๋œ ์ƒํƒœ

        entityManager.remove(member);

.

์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ์ด์ 

  • 1์ฐจ ์บ์‹œ์—์„œ์˜ ์กฐํšŒ

    • ์‚ฌ์šฉ์ž์˜ ํ•˜๋‚˜์˜ ์š”์ฒญ-์‘๋‹ต(ํ•˜๋‚˜์˜ ํŠธ๋žœ์ ์…˜) ๋‚ด์—์„œ๋งŒ ํšจ๊ณผ๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ์„ฑ๋Šฅ ์ด์ ์„ ๊ธฐ๋Œ€ํ•˜์ง€๋Š” ์•Š์Œ.

    • ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ์‹œ ๋จผ์ € 1์ฐจ ์บ์‹œ์—์„œ ์กฐํšŒ ํ›„, ์—†์„ ๊ฒฝ์šฐ DB์—์„œ ์กฐํšŒ

  • ์˜์† ์—”ํ‹ฐํ‹ฐ์˜ ๋™์ผ์„ฑ(identity) ๋ณด์žฅ

    • 1์ฐจ ์บ์‹œ๋กœ ๋ฐ˜๋ณต ๊ฐ€๋Šฅํ•œ ์ฝ๊ธฐ(REPEATABLE READ) ๋“ฑ๊ธ‰์˜ ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์•„๋‹Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฐจ์›์—์„œ ์ œ๊ณต

  • ํŠธ๋žœ์žญ์…˜์„ ์ง€์›ํ•˜๋Š” ์“ฐ๊ธฐ ์ง€์—ฐ(transactional write-behind)

    • Query๋ฅผ ์Œ“์•„ ๋‘๋‹ค๊ฐ€ transaction.commit() ์„ ํ•˜๋Š” ์ˆœ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— Query ์ „์†ก

  • ๋ณ€๊ฒฝ ๊ฐ์ง€(Dirty Checking)

    • transaction.commit() ์‹œ์ ์— ์—”ํ‹ฐํ‹ฐ์™€ ์Šค๋ƒ…์ƒท(์ฒ˜์Œ ์ฝ์–ด ์˜จ ์—”ํ‹ฐํ‹ฐ ์ƒํƒœ) ๋น„๊ต ํ›„ ๋ณ€๊ฒฝ์ด ๊ฐ์ง€๋˜๋ฉด Update Query ๋ฅผ ์“ฐ๊ธฐ ์ง€์—ฐ SQL ์ €์žฅ์†Œ์— ์ €์žฅ

    • ์ดํ›„ DB์— Query ์ „์†ก ๋ฐ Commit

  • ์ง€์—ฐ ๋กœ๋”ฉ(Lazy Loading)

.

Flush

์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ๋ณ€๊ฒฝ๋‚ด์šฉ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ˜์˜ํ•˜๋Š” ์—ญํ• 

๋ฐœ์ƒ ์‹œ์ 

  • ๋ณ€๊ฒฝ ๊ฐ์ง€

  • ์“ฐ๊ธฐ ์ง€์—ฐ SQL ์ €์žฅ์†Œ์˜ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ „์†ก (CUD Query)

ํ˜ธ์ถœ ๋ฐฉ๋ฒ•

  • ์ง์ ‘ ํ˜ธ์ถœ : em.flush()

  • ์ž๋™ ํ˜ธ์ถœ : ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹, JPQL ์ฟผ๋ฆฌ ์‹คํ–‰

์—”ํ‹ฐํ‹ฐ ๋งคํ•‘

๊ฐ์ฒด์™€ ํ…Œ์ด๋ธ”

๊ฐ์ฒด์™€ ํ…Œ์ด๋ธ” ๋งคํ•‘

  • @Entity: JPA๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค (๊ธฐ๋ณธ ์ƒ์„ฑ์ž ํ•„์ˆ˜)

  • @Table: ์—”ํ‹ฐํ‹ฐ์™€ ๋งคํ•‘ํ•  ํ…Œ์ด๋ธ” ์ง€์ •

ํ•„๋“œ์™€ ์ปฌ๋Ÿผ ๋งคํ•‘

  • @Column

๊ธฐ๋ณธ ํ‚ค ๋งคํ•‘

  • @Id

์—ฐ๊ด€๊ด€๊ณ„ ๋งคํ•‘

  • @ManyToOne

  • @JoinColumn

.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ์ž๋™ ์ƒ์„ฑ

@Entity๊ฐ€ ์žˆ๋Š” ํด๋ž˜์Šค DDL์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์‹œ์ ์— ์ž๋™ ์ƒ์„ฑ (๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ ์‚ฌ์šฉ)

<property name="hibernate.hbm2ddl.auto" value="create" />
  • create: ๊ธฐ์กดํ…Œ์ด๋ธ” ์‚ญ์ œ ํ›„ ๋‹ค์‹œ ์ƒ์„ฑ (DROP + CREATE)

  • create-drop: create์™€ ๊ฐ™์œผ๋‚˜ ์ข…๋ฃŒ์‹œ์ ์— ํ…Œ์ด๋ธ” DROP

  • update: ๋ณ€๊ฒฝ๋ถ„๋งŒ ๋ฐ˜์˜(์šด์˜DB์—๋Š” ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋จ)

  • validate ์—”ํ‹ฐํ‹ฐ์™€ ํ…Œ์ด๋ธ”์ด ์ •์ƒ ๋งคํ•‘๋˜์—ˆ๋Š”์ง€๋งŒ ํ™•์ธ

  • none: ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ

์ฃผ์˜

  • ์šด์˜ ์žฅ๋น„์—๋Š” ์ ˆ๋Œ€ create, create-drop, update ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋จ.

    • ๊ฐœ๋ฐœ ์ดˆ๊ธฐ ๋‹จ๊ณ„๋Š” create ๋˜๋Š” update

    • ํ…Œ์ŠคํŠธ ์„œ๋ฒ„๋Š” update ๋˜๋Š” validate

    • ์Šคํ…Œ์ด์ง•๊ณผ ์šด์˜ ์„œ๋ฒ„๋Š” validate ๋˜๋Š” none

.

ํ•„๋“œ์™€ ์ปฌ๋Ÿผ

@Entity
public class Member {

    @Id
    private Long id;

    /**
     * @Column : ์ปฌ๋Ÿผ ๋งคํ•‘
     * - name : ํ•„๋“œ์™€ ๋งคํ•‘ํ•  ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ๋ช… (default. ๊ฐ์ฒด ํ•„๋“œ๋ช…)
     * - insertable : ๋“ฑ๋ก ๊ฐ€๋Šฅ ์—ฌ๋ถ€ (default. TRUE)
     * - updatable : ์ˆ˜์ • ๊ฐ€๋Šฅ ์—ฌ๋ถ€ (default. TRUE)
     *
     * ์•„๋ž˜๋Š” DDL ์กฐ๊ฑด
     * - nullable : null ํ—ˆ์šฉ ์—ฌ๋ถ€ (default. TRUE)
     * - unique : ์œ ๋‹ˆํฌ ์ œ์•ฝ ์กฐ๊ฑด, ์ œ์•ฝ์กฐ๊ฑด๋ช…์ด ๋žœ๋คํ‚ค๋กœ ์ƒ์„ฑ๋˜์–ด ์ฃผ๋กœ ์‚ฌ์šฉํ•˜์ง€๋Š” ์•Š์Œ (default. FALSE)
     * - columnDefinition : ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปฌ๋Ÿผ ์ •๋ณด ์ง์ ‘ ์„ค์ • (ex. varchar(100) default โ€˜EMPTY')
     * - length : ๋ฌธ์ž(String) ๊ธธ์ด ์ œ์•ฝ์กฐ๊ฑด (default. 255)
     *
     * BigDecimal, BigInteger ํƒ€์ž…์— ์‚ฌ์šฉ
     * - precision : ์†Œ์ˆ˜์ ์„ ํฌํ•จํ•œ ์ „์ฒด ์ž๋ฆฟ์ˆ˜ (default. 19)
     * - scale : ์†Œ์ˆ˜ ์ž๋ฆฟ์ˆ˜ (default. 2)
     */
    @Column(name = "name")
    private String username;

    private Integer age;

    /**
     * @Enumerated : enum ํƒ€์ž… ๋งคํ•‘
     * ์ฃผ์˜. ORDINAL ์‚ฌ์šฉ X! (enum ์ˆœ์„œ๋ฅผ ์ €์žฅ)
     */
    @Enumerated(EnumType.STRING)
    private RoleType roleType;

    /**
     * @Temporal : ๋‚ ์งœ ํƒ€์ž… ๋งคํ•‘
     * DATE, TIME, TIMESTAMP
     * (LocalDate, LocalDateTime ์‚ฌ์šฉํ•  ์‹œ ์ƒ๋žต)
     */
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;

    /**
     * @Lob : BLOB, CLOB ๋งคํ•‘
     * ๋งคํ•‘ ํ•„๋“œ ํƒ€์ž…์ด ๋ฌธ์ž๋ฉด CLOB, ๋‚˜๋จธ์ง€๋Š” BLOB์œผ๋กœ ๋งคํ•‘
     * - CLOB: String, char[], java.sql.CLOB
     * โ€ข BLOB: byte[], java.sql. BLOB
     */
    @Lob
    private String description;

    /**
     * @Transient : ํ•ด๋‹น ํ•„๋“œ๋ฅผ ์ปฌ๋Ÿผ์— ๋งคํ•‘ํ•˜์ง€ ์•Š์Œ
     * ๋ฉ”๋ชจ๋ฆฌ์ƒ์—์„œ๋งŒ ์ž„์‹œ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๊ด€ํ•  ๊ฒฝ์šฐ ์‚ฌ์šฉ
     */
    @Transient
    private String temp;
}

.

๊ธฐ๋ณธ ํ‚ค

  • @Id : ์ง์ ‘ ํ• ๋‹นํ•  ๊ฒฝ์šฐ

  • @GeneratedValue : ์ž๋™ ์ƒ์„ฑํ•  ๊ฒฝ์šฐ

    • AUTO : ๋ฐฉ์–ธ์— ๋”ฐ๋ผ ์ž๋™ ์ง€์ • (default)

    • IDENTITY : ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์œ„์ž„ (MYSQL)

      • ์ฃผ๋กœ MySQL, PostgreSQL, SQL Server, DB2 ์—์„œ ์‚ฌ์šฉ

      • ์ฐธ๊ณ ) DB INSERT Query ์‹คํ–‰ ํ›„์— ID ๊ฐ’์„ ์•Œ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, em.persist() ์‹œ์ ์— ์ฆ‰์‹œ INSERT Query ์‹คํ–‰ ๋ฐ DB ์‹๋ณ„์ž ์กฐํšŒ

    • SEQUENCE : ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‹œํ€€์Šค ์˜ค๋ธŒ์ ํŠธ ์‚ฌ์šฉ (ORACLE, @SequenceGenerator)

      • ์ฃผ๋กœ ์˜ค๋ผํด, PostgreSQL, DB2, H2 ์—์„œ ์‚ฌ์šฉ

    • TABLE : ํ‚ค ์ƒ์„ฑ์šฉ ํ…Œ์ด๋ธ” ์‚ฌ์šฉ, (๋ชจ๋“  DB, @TableGenerator)

Long Type + ๋Œ€์ฒดํ‚ค + ํ‚ค ์ƒ์„ฑ์ „๋žต ์‚ฌ์šฉ ๊ถŒ์žฅ

AUTO & IDENTITY

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

SEQUENCE

allocationSize

  • ์‹œํ€€์Šค๋ฅผ ํ•œ ๋ฒˆ ํ˜ธ์ถœํ•  ๋•Œ ์ฆ๊ฐ€ํ•˜๋Š” ์ˆ˜ (์„ฑ๋Šฅ ์ตœ์ ํ™”์— ์‚ฌ์šฉ, default. 50)

    • ์›น ์„œ๋ฒ„๋ฅผ ๋‚ด๋ฆฌ๋Š” ์‹œ์ ์— ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋˜์–ด์žˆ๋˜ ์‹œํ€€์Šค๋“ค์ด ๋‚ ๋ผ๊ฐ€์„œ ๊ตฌ๋ฉ์ด ์ƒ๊ธฐ๋ฏ€๋กœ, 50~100์ด ์ ์ ˆ

    • DB ์‹œํ€€์Šค ๊ฐ’์ด ํ•˜๋‚˜์”ฉ ์ฆ๊ฐ€ํ•˜๋„๋ก ์„ค์ •๋˜์–ด ์žˆ๋‹ค๋ฉด, ์ด ๊ฐ’์„ ๋ฐ˜๋“œ์‹œ 1๋กœ ์„ค์ •

  • ex) ์ดˆ๊ธฐ 1 ~ 51 ๊นŒ์ง€ ์กฐํšŒ, ์ดํ›„ ์‹œํ€€์Šค๋Š” DB์—์„œ ์กฐํšŒํ•˜์ง€ ์•Š๊ณ  ๋ฉ”๋ชจ๋ฆฌ์ƒ์—์„œ ์กฐํšŒ

    • ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์‹œํ€€์Šค 51์„ ๋งŒ๋‚˜๋Š” ์ˆœ๊ฐ„ ๋‹ค์‹œ DB์—์„œ ์กฐํšŒ(next call)

  • ๋ฏธ๋ฆฌ ์‹œํ€€์Šค ๊ฐ’์„ ์˜ฌ๋ ค๋‘๋ฏ€๋กœ ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์Œ

  • ์ด ๋ถ€๋ถ„์€ Table ์ „๋žต๋„ ์œ ์‚ฌ

@Entity
@SequenceGenerator(
        name = "MEMBER_SEQ_GENERATOR", //์ƒ์„ฑ ์ด๋ฆ„
        sequenceName = "MEMBER_SEQ", //๋งคํ•‘ํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‹œํ€€์Šค ์ด๋ฆ„
        initialValue = 1, allocationSize = 1)
public class Member {
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE,
          generator = "MEMBER_SEQ_GENERATOR")
  private Long id;
}

์—ฐ๊ด€๊ด€๊ณ„ ๋งคํ•‘

JPA๋Š” ๊ฐ์ฒด์˜ ์ฐธ์กฐ์™€ ํ…Œ์ด๋ธ”์˜ ์™ธ๋ž˜ ํ‚ค๋ฅผ ๋งคํ•‘

๋ฐฉํ–ฅ(Direction)

๋‹จ๋ฐฉํ–ฅ

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    @Column(name = "USERNAME")
    private String name;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

//...
//๋‹จ๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„ ์„ค์ • (์ฐธ์กฐ ์ €์žฅ)
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");
member.setTeam(team);

em.persist(member);

์–‘๋ฐฉํ–ฅ

  • ๊ฐ์ฒด์˜ ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„๋Š” ์‚ฌ์‹ค ์„œ๋กœ ๋‹ค๋ฅธ ๋‹จ๋ฑกํ–ฅ ๊ด€๊ณ„ 2๊ฐœ๋ผ๋Š” ์‚ฌ์‹ค.

    • ๊ฐ์ฒด๋ฅผ ์–‘๋ฐฉํ–ฅ์œผ๋กœ ์ฐธ์กฐํ•˜๋ ค๋ฉด ๋‹จ๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ 2๊ฐœ ๋งŒ๋“ค์–ด์•ผ ํ•จ

  • ํ…Œ์ด๋ธ”์€ ์™ธ๋ž˜ ํ‚ค ํ•˜๋‚˜๋กœ ๋‘ ํ…Œ์ด๋ธ”์˜ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๊ด€๋ฆฌ

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
     
    @OneToMany(mappedBy = "team")
    List<Member> members = new ArrayList<Member>();
}

์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ(Owner)

์™ธ๋ž˜ํ‚ค๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ฐธ์กฐ

์–‘๋ฐฉํ–ฅ ๋งคํ•‘ ๊ทœ์น™

  • ๊ด€๊ณ„๋ฅผ ๊ฐ–๋Š” ๋‘ ๊ฐ์ฒด ์ค‘ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋ฅผ ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์œผ๋กœ ์ง€์ •

    • ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ๋งŒ์ด ์™ธ๋ž˜ ํ‚ค๋ฅผ ๊ด€๋ฆฌ(๋“ฑ๋ก, ์ˆ˜์ •)ํ•˜๊ณ , ์ฃผ์ธ์ด ์•„๋‹Œ ์ชฝ์€ ์กฐํšŒ๋งŒ ๊ฐ€๋Šฅ

    • ์ฃผ์ธ์ด ์•„๋‹Œ ๊ฐ์ฒด์˜ ํ•„๋“œ์— mappedBy ์†์„ฑ์œผ๋กœ ์ฃผ์ธ ํ•„๋“œ๋ฅผ ์ง€์ •

  • ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์€ ๋‹ค(N:1)์— ํ•ด๋‹นํ•˜๋Š” ๊ฐ์ฒด์ชฝ์ด ๊ฐ–๋„๋ก(์™ธ๋ž˜ํ‚ค๋ฅผ ๊ฐ–๋Š” ํ…Œ์ด๋ธ” ๊ธฐ์ค€)

    • ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์— ๊ฐ’ ์„ค์ •ํ•˜๊ธฐ

      Team team = new Team();
      team.setName("TeamA");
      em.persist(team);
      
      Member member = new Member();
      member.setName("member1");
      //์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์— ๊ฐ’ ์„ค์ •
      member.setTeam(team);
      //์ˆœ์ˆ˜ ๊ฐ์ฒด ์ƒํƒœ๋ฅผ ๊ณ ๋ คํ•ด์„œ ํ•ญ์ƒ ์–‘์ชฝ์— ๊ฐ’ ์„ค์ •ํ•˜๊ธฐ
      team.getMembers().add(member);
      em.persist(member);
    • ์—ฐ๊ด€๊ด€๊ณ„ ํŽธ์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ํŽธ๋ฆฌ

      @Entity
      public class Member {
        //..
      
        public void changeTeam(Team team) {
          this.tema = team;
          team.getMembers().add(this);
        }
      }
      
      // OR (๋‘ ๊ฐ์ฒด ์ค‘ ํ•œ ๊ฐ์ฒด๋ฅผ ์„ ํƒ)
      
      @Entity
      public class Team {
        //...
      
        public void addMember(Member member) {
          member.setTeam(this);
          members.add(member);
        }
      }
  • ์–‘๋ฐฉํ–ฅ ๋งคํ•‘์‹œ์— ๋ฌดํ•œ ๋ฃจํ”„๋กœ ์ธํ•œ StackOverflow ์กฐ์‹ฌํ•˜๊ธฐ

    • toString(), lombok, JSON ์ƒ์„ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(=> Controller DTO ๋ฐ˜ํ™˜์œผ๋กœ ํ•ด๊ฒฐ)

๋‹จ๋ฐฉํ–ฅ ๋งคํ•‘๋งŒ์œผ๋กœ๋„ ์—ฐ๊ด€๊ด€๊ณ„ ๋งคํ•‘์€ ์™„๋ฃŒ๋œ ์ƒํƒœ.

์ถ”ํ›„ ์—ญ๋ฐฉํ–ฅ ํƒ์ƒ‰์ด ํ•„์š”ํ•  ๊ฒฝ์šฐ์— ์ถ”๊ฐ€ํ•˜๊ธฐ!(ํ…Œ์ด๋ธ”์— ์˜ํ–ฅ X)

๋‹ค์ค‘์„ฑ(Multiplicity)

๋‹ค๋Œ€์ผ(N:1) - @ManyToOne

  • ํ…Œ์ด๋ธ” ์™ธ๋ž˜ํ‚ค ๊ธฐ์ค€์œผ๋กœ ์—ฐ๊ด€๋œ ์ฐธ์กฐ๋ฅผ ์„ค์ •(N์ด ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ)

  • ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ์„ ํ•  ๊ฒฝ์šฐ, ๋ฐ˜๋Œ€ ๊ฐ์ฒด์—๋„ OneToMany ๋ฐฉํ–ฅ ์„ค์ • ์ถ”๊ฐ€(์กฐํšŒ๋งŒ ๊ฐ€๋Šฅ)

์ผ๋Œ€๋‹ค(1:N) - @OneToMany

  • ์ผ๋Œ€๋‹ค ๋‹จ๋ฐฉํ–ฅ

    • ์œ„์™€ ๋ฐ˜๋Œ€ ์ผ€์ด์Šค๋กœ ์ผ(1)์ด ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์ด ๋  ๊ฒฝ์šฐ, A(1) ํ…Œ์ด๋ธ” ์—…๋ฐ์ดํŠธ๋ฅผ ์‹œ๋„ํ–ˆ์ง€๋งŒ B(N) ํ…Œ์ด๋ธ”๋„ ํ•จ๊ป˜ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•ด์•ผํ•˜๋Š” ์ƒํ™ฉ์œผ๋กœ ์—ฌ๋Ÿฌ ์ด์Šˆ ๋ฐœ์ƒ ์š”์†Œ๊ฐ€ ์ƒ๊น€

      • ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ์™ธ๋ž˜ํ‚ค๊ฐ€ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์žˆ์œผ๋ฏ€๋กœ ์—ฐ๊ด€๊ด€๊ณ„ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ์ถ”๊ฐ€ ์—…๋ฐ์ดํŠธ ์ฟผ๋ฆฌ ๋ฐœ์ƒ

      @OneToMany
      @JoinColumn(name = "team_id")
      private List<Member> members = new ArrayList<>();
  • ์ผ๋Œ€๋‹ค ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ์€ ๊ณต์‹์ ์œผ๋กœ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋งคํ•‘

์ผ๋Œ€๋‹ค ๋‹จ๋ฑกํ–ฅ ๋งคํ•‘๋ณด๋‹ค ๋‹ค๋Œ€์ผ ์–‘๋ฐฉํ–ฅ ๋งคํ•‘์„ ์‚ฌ์šฉํ•˜์ž

์ผ๋Œ€์ผ(1:1) - @OneToOne

ex) ํšŒ์›๊ณผ ๊ฐœ์ธ ๋ฝ์ปค์˜ ๊ด€๊ณ„

  • ์™ธ๋ž˜ํ‚ค์— DB ์œ ๋‹ˆํฌ(UNI) ์ œ์•ฝ์กฐ๊ฑด ํ•„์š”

  • ์„ค์ •์€ ๋‹ค๋Œ€์ผ ๋งคํ•‘๊ณผ ์œ ์‚ฌ

  • ์ฃผ/๋Œ€์ƒ ํ…Œ์ด๋ธ” ์ค‘์— ์™ธ๋ž˜ํ‚ค ์„ ํƒ ๊ฐ€๋Šฅ

    • ์ฃผ ํ…Œ์ด๋ธ” ์„ ํƒ

      • JPA ๋งคํ•‘์ด ํŽธ๋ฆฌํ•˜์—ฌ ๊ฐ์ฒด์ง€ํ–ฅ ๊ฐœ๋ฐœ์ž ์„ ํ˜ธ

      • ์žฅ์ . ์ฃผ ํ…Œ์ด๋ธ”๋งŒ ์กฐํšŒํ•ด์„œ ๋Œ€์ƒ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ํ™•์ธ ๊ฐ€๋Šฅ

      • ๋‹จ์ . ๊ฐ’์ด ์—†์œผ๋ฉด ์™ธ๋ž˜ํ‚ค์— null ํ—ˆ์šฉ

    • ๋Œ€์ƒ ํ…Œ์ด๋ธ” ์„ ํƒ

      • ์ „ํ†ต DB ๊ฐœ๋ฐœ์ž ์„ ํ˜ธ

      • ์žฅ์ . ์ฃผ/๋Œ€์ƒ ํ…Œ์ด๋ธ”์„ 1:1 ๊ด€๊ณ„์—์„œ 1:N ๊ด€๊ณ„๋กœ ๋ณ€๊ฒฝ ์‹œ ํ…Œ์ด๋ธ” ๊ตฌ์กฐ ์œ ์ง€ ๊ฐ€๋Šฅ

      • ๋‹จ์ . ํ”„๋ก์‹œ ๊ธฐ๋Šฅ์˜ ํ•œ๊ณ„๋กœ ์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ ์„ค์ •ํ•ด๋„ ํ•ญ์ƒ ์ฆ‰์‹œ ๋กœ๋”ฉ

๋‹จ๋ฐฉํ–ฅ

@Entity
  public class Member {
    //..
    @OneToOne
    @JoinColumn(name = "locker_id")
    private Locker locker;
  }

์–‘๋ฐฉํ–ฅ

@Entity
public class Locker {
    //..
    @OneToOne(mappedBy = "Locker")
    private Member member;
}

๋‹ค๋Œ€๋‹ค(N:M) - @ManyToMany

  • RDB๋Š” ์ •๊ทœํ™”๋œ ํ…Œ์ด๋ธ” 2๊ฐœ๋กœ ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„๋ฅผ ํ‘œํ˜„ํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์ค‘๊ฐ„ ํ…Œ์ด๋ธ”์ด ํ•„์š”

  • ๊ฐ์ฒด๋Š” @ManyToMany, @JoinTable๋กœ ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„๋ฅผ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ธฐํƒ€ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์—†๋Š” ํ•œ๊ณ„๋กœ ์‹ค๋ฌด์—์„œ๋Š” ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ค์›€ (๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์€ N:N ๊ด€๊ณ„ ์„ค๊ณ„๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ์—”ํ‹ฐํ‹ฐ์™€ ํ…Œ์ด๋ธ” ๋ถˆ์ผ์น˜ ๋ฌธ์ œ๋„ ๋ฐœ์ƒ)

    • ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์šฉ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ• ์‚ฌ์šฉ

  • @ManyToMany -> @OneToMany, @ManyToOne ๋กœ ํ’€์–ด์„œ ์‚ฌ์šฉํ•˜์ž.

Member.java

@Entity
public class Member {
  //...
  @OneToMany(mappedBy = "member")
  private List<MemberProduct> memberProducts = new ArrayList()<>;
}

MemberProduct.java

id ๋Œ€์‹  (member, product)๋ฅผ ๋ฌถ์–ด์„œ PK, FK๋กœ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ,

ํ–ฅํ›„ ๋น„์ฆˆ๋‹ˆ์Šค์ ์ธ ์กฐ๊ฑด์ด ์ถ”๊ฐ€๋  ๊ฒฝ์šฐ๋ฅผ ๊ณ ๋ คํ•˜๋ฉด, GeneratedValue ID๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์œ ์—ฐํ•˜๊ณ  ๊ฐœ๋ฐœ์ด ์‰ฌ์›Œ์ง€๋Š” ์žฅ์ ์ด ์žˆ์Œ

@Entity
public class MemberProduct {

    @Id @GeneratedValue
    @Column(name = "member_product_id ")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;

    private int orderAmount;
    private int price;
    private LocalDataTime orderDateTime;
}

Product.java

@Entity
public class Product {
    //...
    @OneToMany(mappedBy = "product")
    private List<MemberProduct> memberProducts = new ArrayList()<>;
}

๊ณ ๊ธ‰ ๋งคํ•‘

์ƒ์†๊ด€๊ณ„ ๋งคํ•‘

  • ๊ฐ์ฒด์˜ ์ƒ์† ๊ตฌ์กฐ์™€ DB์˜ ์Šˆํผ/์„œ๋ธŒํƒ€์ž… ๊ด€๊ณ„๋ฅผ ๋งคํ•‘

  • DB ์Šˆํผ/์„œ๋ธŒํƒ€์ž… ๋…ผ๋ฆฌ ๋ชจ๋ธ์„ ๋ฌผ๋ฆฌ ๋ชจ๋ธ๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•

    • @DiscriminatorColumn(name=โ€œDTYPEโ€) / ์ž์‹ ํƒ€์ž… ํ•„๋“œ ์‚ฌ์šฉ (default. DTYPE)

    • @DiscriminatorValue(โ€œXXXโ€) / ์ž์‹ ํƒ€์ž…๋ช… ์ˆ˜์ • ์‹œ (default. entity name)

    • @Inheritance(strategy=InheritanceType.XXX) / ์ƒ์† ํƒ€์ž…

์กฐ์ธ ์ „๋žต JOINED

  • ๊ธฐ๋ณธ ์ •์„์œผ๋กœ ์‚ฌ์šฉ

  • ์žฅ์ 

    • ํ…Œ์ด๋ธ” ์ •๊ทœํ™” (์ €์žฅ๊ณต๊ฐ„ ํšจ์œจํ™”)

    • ์™ธ๋ž˜ํ‚ค ์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ ์ œ์•ฝ์กฐ๊ฑด ํ™œ์šฉ

  • ๋‹จ์ 

    • ์กฐํšŒ ์ฟผ๋ฆฌ๊ฐ€ ๋ณต์žกํ•ด์ง€๊ณ , ์กฐ์ธ์„ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์–ด ์„ฑ๋Šฅ ์ €ํ•˜

    • ๋ฐ์ดํ„ฐ ์ €์žฅ ์‹œ INSERT Query ๋‘ ๋ฒˆ ํ˜ธ์ถœ

//Item.java
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name=โ€œDTYPEโ€)
public class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private Integer price;
}
//Album.java
@Entity
@DiscriminatorValue("A")
public class Album extends Item {

    private String artist;
}
//Movie.java
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {

    private String director;
    private String actor;
}
//Book.java
@Entity
@DiscriminatorValue("B")
public class Book extends Item {

    private String author;
    private String isbn;
}

๋‹จ์ผ ํ…Œ์ด๋ธ” ์ „๋žต SINGLE_TABLE

  • ๋‹จ์ˆœํ•˜๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅ์„ฑ์ด ์—†์„ ๊ฒฝ์šฐ ์‚ฌ์šฉ

  • ์žฅ์ 

    • ์กฐํšŒ ์‹œ ์กฐ์ธ์ด ํ•„์š” ์—†์œผ๋ฏ€๋กœ ์ผ๋ฐ˜์ ์œผ๋กœ ์กฐํšŒ ์„ฑ๋Šฅ์ด ๋น ๋ฅด๊ณ  ๋‹จ์ˆœ

  • ๋‹จ์ 

    • ์ž์‹ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋งคํ•‘ํ•œ ์ปฌ๋Ÿผ์€ ๋ชจ๋‘ null ํ—ˆ์šฉ

    • ๋‹จ์ผ ํ…Œ์ด๋ธ”์— ๋งŽ์€ ํ•„๋“œ๋ฅผ ์ €์žฅํ•˜๋ฏ€๋กœ ํ…Œ์ด๋ธ”์ด ์ปค์งˆ ์ˆ˜ ์žˆ๊ณ , ์ƒํ™ฉ์— ๋”ฐ๋ผ ์กฐํšŒ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ์ˆ˜ ์žˆ์Œ

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
public class Item {
}

๊ตฌํ˜„ ํด๋ž˜์Šค๋งˆ๋‹ค ํ…Œ์ด๋ธ” ์ „๋žต TABLE_PER_CLASS

  • ๋ถ€๋ชจ ํด๋ž˜์Šค๋Š” ์ถ”์ƒ(abstract) ํด๋ž˜์Šค๋กœ ์ƒ์„ฑ

  • ์œ ์ง€ ๋ณด์ˆ˜ ๋ฐ ๊ด€๋ฆฌ ์ตœ์•…์œผ๋กœ ๋น„์ถ”ํ•˜๋Š” ์ „๋žต..

  • ์žฅ์ 

    • ์„œ๋ธŒ ํƒ€์ž…์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•ด์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ ํšจ๊ณผ์ 

    • not null ์ œ์•ฝ์กฐ๊ฑด ์‚ฌ์šฉ ๊ฐ€๋Šฅ

  • ๋‹จ์ 

    • ์ž์‹ ํ…Œ์ด๋ธ”์„ ํ•จ๊ป˜ ์กฐํšŒํ•  ๋•Œ ์„ฑ๋Šฅ ์ €ํ•˜(UNION Query)

    • ์ž์‹ ํ…Œ์ด๋ธ”์„ ํ†ตํ•ฉํ•ด์„œ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์–ด๋ ค์›€

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {
}

.

@MappedSuperclass

  • ๊ณตํ†ต ๋งคํ•‘ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•  ๊ฒฝ์šฐ ์‚ฌ์šฉ

  • ์ถ”์ƒ ํด๋ž˜์Šค ๊ถŒ์žฅ(์ง์ ‘ ์ƒ์„ฑํ•ด์„œ ์‚ฌ์šฉํ•  ์ผ์ด ์—†์Œ)

  • ex. ๋“ฑ๋ก์ผ, ์ˆ˜์ •์ผ, ๋“ฑ๋ก์ž, ์ˆ˜์ •์ž ๋“ฑ..

  • ํ—ท๊ฐˆ๋ฆฌ์ง€ ์•Š๊ธฐ!

    • ์ƒ์†๊ด€๊ณ„ ๋งคํ•‘X - ์ž์‹ ํด๋ž˜์Šค์— ๋งคํ•‘ ์ •๋ณด๋งŒ ์ œ๊ณต)

    • ์—”ํ‹ฐํ‹ฐ/ํ…Œ์ด๋ธ” ๋งคํ•‘X - ์กฐํšŒ, ๊ฒ€์ƒ‰(em.find(BaseEntity)) ๋ถˆ๊ฐ€

ํ”„๋ก์‹œ & ์—ฐ๊ด€๊ด€๊ณ„ ๊ด€๋ฆฌ

ํ”„๋ก์‹œ

DB ์กฐํšŒ๋ฅผ ๋ฏธ๋ฃจ๋Š”(Lazy) ๊ฐ€์งœ(Proxy) ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด ์กฐํšŒ

em.getReference()

  • ์‹ค์ œ ํด๋ž˜์Šค๋ฅผ ์ƒ์† ๋ฐ›์•„ ๋งŒ๋“ค์–ด์ง€๊ณ , ์‹ค์ œ์™€ ๊ฒ‰ ๋ชจ์–‘๋งŒ ๊ฐ™์€ ๋นˆ ๊นกํ†ต

  • ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ์˜ ์ฐธ์กฐ(target)๋ฅผ ๋ณด๊ด€ํ•˜๊ณ , ํ”„๋ก์‹œ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ

ํ”„๋ก์‹œ ๊ฐ์ฒด ํŠน์ง•

  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์ฒ˜์Œ ์‚ฌ์šฉํ•  ๋•Œ ํ•œ ๋ฒˆ๋งŒ ์ดˆ๊ธฐํ™”

    • ์ดˆ๊ธฐํ™” ์‹œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ์— ์ ‘๊ทผ ๊ฐ€๋Šฅ

  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์›๋ณธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ƒ์†๋ฐ›์œผ๋ฏ€๋กœ, ํƒ€์ž… ์ฒดํฌ ์‹œ instance of ์‚ฌ์šฉ

  • ํ•œ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ์•ˆ์—์„œ ๋™์ผํ•œ ID ์กฐํšŒ ์‹œ, JPA๋Š” ํ•ญ์ƒ ๊ฐ™์€ ํƒ€์ž…์˜ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐ˜ํ™˜

    • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์ฐพ๋Š” ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ด๋ฏธ ์žˆ๋‹ค๋ฉด, em.getReference()๋ฅผ ํ˜ธ์ถœํ•ด๋„ ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐ˜ํ™˜

    • ๋ฐ˜๋Œ€๋กœ ํ”„๋ก์‹œ ์กฐํšŒ ํ›„, ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ๋ฅผ ํ•ด๋„ ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์•„๋‹Œ porxy ๋ฐ˜ํ™˜

  • ์ค€์˜์† ์ƒํƒœ์ผ ๋•Œ(em.clear() / em.close() / em.detach()), ํ”„๋ก์‹œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ฉด LazyInitializationException ๋ฐœ์ƒ

//ํ”„๋ก์‹œ ์ธ์Šคํ„ด์Šค์˜ ์ดˆ๊ธฐํ™” ์—ฌ๋ถ€ ํ™•์ธ
emf.getPersistenceUnitUtil().isLoaded(entity);
//ํ”„๋ก์‹œ ํด๋ž˜์Šค ํ™•์ธ
entity.getClass();
//ํ”„๋ก์‹œ ๊ฐ•์ œ ์ดˆ๊ธฐํ™”
org.hibernate.Hibernate.initialize(entity);
entity.getName() //JPA๋Š” ํ˜ธ์ถœ ์‹œ ์ดˆ๊ธฐํ™”

์ฆ‰์‹œ ๋กœ๋”ฉ๊ณผ ์ง€์—ฐ ๋กœ๋”ฉ

์ฆ‰์‹œ ๋กœ๋”ฉ

@ManyToOne(fetch = FetchType.EAGER)
  • ์—ฐ๊ด€ ๊ฐ์ฒด๋ฅผ ์กฐ์ธ์œผ๋กœ ํ•จ๊ป˜ ์กฐํšŒ

์ง€์—ฐ ๋กœ๋”ฉ

@ManyToOne(fetch = FetchType.LAZY)
  • ์—ฐ๊ด€ ๊ฐ์ฒด๋ฅผ ํ”„๋ก์‹œ๋กœ ์กฐํšŒ

  • ํ”„๋ก์‹œ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์‹œ์ ์— ์ดˆ๊ธฐํ™”(์กฐํžˆ)

์ฆ‰์‹œ/์ง€์—ฐ ๋กœ๋”ฉ ์ฃผ์˜ ์‚ฌํ•ญ

  • ์‹ค๋ฌด์—์„œ๋Š” ์ง€์—ฐ ๋กœ๋”ฉ๋งŒ ์‚ฌ์šฉํ•˜์ž

    • ์ฆ‰์‹œ ๋กœ๋”ฉ ์ ์šฉ ์‹œ, ์—ฐ๊ด€ ๊ด€๊ณ„๊ฐ€ ๋งŽ์•„์ง€๊ฒŒ ๋˜๋ฉด ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ SQL ๋ฐœ์ƒ

    • ๋˜ํ•œ, JPQL์—์„œ N+1 ๋ฌธ์ œ ๋ฐœ์ƒ

  • N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ์€ JPQL fetch join ํ˜น์€ Entity Graph ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์ž.

  • @ManyToOne, @OneToOne์˜ default๋Š” ์ฆ‰์‹œ ๋กœ๋”ฉ์ด๋ฏ€๋กœ LAZY ์„ค์ • ํ•„์š”

    • @OneToMany, @ManyToMany default : ์ง€์—ฐ ๋กœ๋”ฉ

์˜์†์„ฑ ์ „์ด

CASCADE

@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)
  • ํŠน์ • ์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜์† ์ƒํƒœ๋กœ ๋งŒ๋“ค ๋•Œ, ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋„ ํ•จ๊ป˜ ์˜์† ์ƒํƒœ๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ ์‚ฌ์šฉ

    • ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งคํ•‘ํ•˜๋Š” ๊ฒƒ๊ณผ๋Š” ์•„๋ฌด ๊ด€๋ จ ์—†์Œ.

  • ์—”ํ‹ฐํ‹ฐ์˜ ์†Œ์œ ์ž๊ฐ€ ํ•˜๋‚˜์ผ ๋•Œ(๋‹จ์ผ ์—”ํ‹ฐํ‹ฐ์— ์ข…์†์ , ๋ผ์ดํ”„ ์‚ฌ์ดํด์ด ์œ ์‚ฌ)๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ.

    • ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ์—์„œ ๊ด€๋ฆฌ๋˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ X! (๊ด€๋ฆฌ๊ฐ€ ํž˜๋“ค์–ด์ง„๋‹ค..)

  • ์ข…๋ฅ˜ (๋ณดํ†ต ALL, PERSIST, REMOVE ์•ˆ์—์„œ ์‚ฌ์šฉํ•˜๋ฉฐ ๋ผ์ดํ”„ ์‚ฌ์ดํด์„ ๋™์ผํ•˜๊ฒŒ ์œ ์ง€)

    • ALL: ๋ชจ๋‘ ์ ์šฉ

    • PERSIST: ์˜์†

    • REMOVE: ์‚ญ์ œ

    • MERGE: ๋ณ‘ํ•ฉ

    • REFRESH: REFRESH

    • DETACH: DETACH

๊ณ ์•„ ๊ฐ์ฒด

@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST, orphanRemoval = true)
  • ๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ๋Š์–ด์ง„ ์ž์‹ ์—”ํ‹ฐํ‹ฐ

  • ์ฐธ์กฐ๊ฐ€ ์ œ๊ฑฐ๋œ ์—”ํ‹ฐํ‹ฐ๋Š” ๋‹ค๋ฅธ ๊ณณ์—์„œ ์ฐธ์กฐํ•˜์ง€ ์•Š๋Š” ๊ณ ์•„ ๊ฐ์ฒด๋กœ ๋ณด๊ณ  ์‚ญ์ œ

    • ๊ณ ์•„ ๊ฐ์ฒด ์ œ๊ฑฐ ์„ค์ • : orphanRemoval = true

    • ์˜์†์„ฑ ์ „์ด์™€ ๋™์ผํ•˜๊ฒŒ ํŠน์ • ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๊ฐœ์ธ ์†Œ์œ ํ•  ๋•Œ๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ

    • @OneToOne, @OneToMany๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

    • ๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ œ๊ฑฐํ•  ๋•Œ CascadeType.REMOVE์™€ ๋™์ผํ•˜๊ฒŒ ์ž์‹๋„ ํ•จ๊ป˜ ์ œ๊ฑฐ

  • ์˜์†์„ฑ ์ „์ด์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ (CascadeType.ALL + orphanRemovel=true)

    • ๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ†ตํ•ด ์ž์‹์˜ ์ƒ๋ช… ์ฃผ๊ธฐ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ

    • DDD Aggregate Root ๊ฐœ๋…์„ ๊ตฌํ˜„ํ•  ๋•Œ ์œ ์šฉ

JPA Data Type

์—”ํ‹ฐํ‹ฐ ํƒ€์ž…

  • @Entity๋กœ ์ •์˜ํ•˜๋Š” ๊ฐ์ฒด

  • ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€ํ•ด๋„ ์‹๋ณ„์ž๋กœ ์ถ”์  ๊ฐ€๋Šฅ

๊ฐ’ ํƒ€์ž…

  • ์‹๋ณ„์ž๊ฐ€ ์—†์œผ๋ฏ€๋กœ ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ์ถ”์  ๋ถˆ๊ฐ€

  • ์ƒ๋ช… ์ฃผ๊ธฐ๋ฅผ ์—”ํ‹ฐํ‹ฐ์— ์˜์กด

  • ๋ฐ์ดํ„ฐ ๊ณต์œ  X!

  • ๊ธฐ๋ณธ๊ฐ’ ํƒ€์ž…

    • Java Basic Type : int, double..

    • Wrapper Class : Integer, Long..

    • String

  • ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…

  • ์ปฌ๋ ‰์…˜ ๊ฐ’ ํƒ€์ž…

  • ์•ˆ์ „ํ•˜๊ฒŒ ๋ถˆ๋ณ€ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค๊ธฐ

Embedded Type

  • ์ƒˆ๋กœ์šด ๊ฐ’ ํƒ€์ž… ์ •์˜ (๊ธฐ๋ณธ ๊ฐ’ ํƒ€์ž…์„ ๋ชจ์•„์„œ ๋งŒ๋“  ๋ณตํ•ฉ ๊ฐ’ ํƒ€์ž…)

    • @Embeddable: ๊ฐ’ ํƒ€์ž… ์ •์˜

    • @Embedded: ๊ฐ’ ํƒ€์ž… ์‚ฌ์šฉ

์žฅ์ 

  • ๊ฐ’ ํƒ€์ž…์„ ๊ฐ์ฒด์ง€ํ–ฅ์ ์œผ๋กœ ์‚ฌ์šฉ (์žฌ์‚ฌ์šฉ, ๋†’์€ ์‘์ง‘๋„ ..)

  • ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ํด๋ž˜์Šค๋งŒ์ด ์‚ฌ์šฉํ•˜๋Š” ์œ ์šฉํ•œ ๋ฉ”์„œ๋“œ ์ƒ์„ฑ

  • ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์„ ์†Œ์œ ํ•œ ์—”ํ‹ฐํ‹ฐ์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ์˜์กด

ํŠน์ง•

  • ์ž˜ ์„ค๊ณ„๋œ ORM Application์€ ๋งคํ•‘ํ•œ ํ…Œ์ด๋ธ” ์ˆ˜๋ณด๋‹ค ํด๋ž˜์Šค ์ˆ˜๊ฐ€ ๋” ๋งŽ์Œ

  • ํ•œ ์—”ํ‹ฐํ‹ฐ์—์„œ ๊ฐ™์€ ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปฌ๋Ÿผ๋ช…์ด ์ค‘๋ณต๋  ๊ฒฝ์šฐ

    • @AttributeOverrides, @AttributeOverride ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ปฌ๋Ÿฌ๋ช… ์†์„ฑ ์žฌ์ •์˜

  • ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์˜ ๊ฐ’์ด null์ด๋ฉด, ๋งคํ•‘ ์ปฌ๋Ÿผ ๊ฐ’ ๋ชจ๋‘ null

@Embeddable
public class Address {

    @Column(length = 10)
    private String city;
    @Column(length = 20)
    private String street;
    @Column(length = 5)
    private String zipcode;

    public Address() {}

    private String fullAddress() {
        return getCity() + " " + getStreet() + " " + getZipcode();
    }

    //..
}


@Entity
public class Member extends BaseEntity{
  
  //..
  @Embedded
  private Address homeAddress;

  @AttributeOverrides({
      @AttributeOverride(name = "city",
              column = @Column(name = "work_city")),
      @AttributeOverride(name = "street",
              column = @Column(name = "work_street")),
      @AttributeOverride(name = "zipcode",
              column = @Column(name = "work_zipcode")),
  })
  private Address workAddress;
}

๊ฐ’ ํƒ€์ž…

๋ถˆ๋ณ€ ๊ฐ์ฒด

  • ๊ฐ’ ํƒ€์ž…์„ ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ์—์„œ ๊ณต์œ ํ•˜๋ฉด Side Effect(๋ถ€์ž‘์šฉ) ๋ฐœ์ƒ

    • ์ธ์Šคํ„ด์Šค ๊ฐ’์„ ๊ณต์œ ํ•˜๋Š” ๊ฒƒ์€ ์œ„ํ—˜ํ•˜๋ฏ€๋กœ ๊ฐ’์„ ๋ณต์‚ฌํ•ด์„œ ์‚ฌ์šฉํ•˜๊ธฐ

  • ๊ฐ’ ํƒ€์ž…์„ ๋ถˆ๋ณ€ ๊ฐ์ฒด๋กœ ์„ค๊ณ„ํ•˜์—ฌ ๊ฐ์ฒด ํƒ€์ž…์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๊ฒŒ ๋งŒ๋“ค๊ธฐ

    • ๋ถˆ๋ณ€ ๊ฐ์ฒด: ์ƒ์„ฑ ์‹œ์ ๋ฅผ ์ œ์™ธํ•˜๊ณ  ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” ๊ฐ์ฒด

      • ์ƒ์„ฑ์ž๋กœ๋งŒ ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ , Setter๋Š” ์ƒ์„ฑํ•˜์ง€ ์•Š๊ธฐ

      • Integer, String์€ ์ž๋ฐ”๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋Œ€ํ‘œ์ ์ธ ๋ถˆ๋ณ€ ๊ฐ์ฒด

    Address address = new Address("city", "street", "10000");
    
    Member member = new Member();
    member.setUsername("member1");
    member.setHomeAddress(address);
    em.persist(member);
    // ๊ฐ’์„ ๊ณต์œ ํ•˜์ง€ ์•Š๊ณ  ์ƒˆ๋กœ ์ƒ์„ฑ
    Address newAddress = new Address("NewCity", address.getStreet(), address.getZipcode());
    member.setHomeAddress(newAddress);

๊ฐ’ ํƒ€์ž… ๋น„๊ต

  • ๊ฐ’ ํƒ€์ž… ๋น„๊ต๋Š” equals๋ฅผ ์‚ฌ์šฉํ•œ ๋™๋“ฑ์„ฑ ๋น„๊ต๋ฅผ ์‚ฌ์šฉ

    • ๋™์ผ์„ฑ(identity) ๋น„๊ต: ์ธ์Šคํ„ด์Šค ์ฐธ์กฐ ๊ฐ’ ๋น„๊ต ==

    • ๋™๋“ฑ์„ฑ(equivalence) ๋น„๊ต: ์ธ์Šคํ„ด์Šค ๊ฐ’ ๋น„๊ต equals()

  • ๊ฐ’ ํƒ€์ž…์˜ equals() ๋ฉ”์†Œ๋“œ๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ์žฌ์ •์˜

    • ํ”„๋ก์‹œ ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•˜์—ฌ getter() ์‚ฌ์šฉ ์ถ”์ฒœ

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return Objects.equals(getCity(), address.getCity()) 
        && Objects.equals(getStreet(), address.getStreet()) 
        && Objects.equals(getZipcode(), address.getZipcode());
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(getCity(), getStreet(), getZipcode());
    }

๊ฐ’ ํƒ€์ž… ์ปฌ๋ ‰์…˜

  • ๊ฐ’ ํƒ€์ž…์„ ํ•˜๋‚˜ ์ด์ƒ ์ €์žฅํ•  ๊ฒฝ์šฐ ์‚ฌ์šฉ

    • ์…€๋ ‰ํŠธ ๋ฐ•์Šค์™€ ๊ฐ™์ด ๊ฐ’ ๋ณ€๊ฒฝ์ด ํ•„์š” ์—†๋Š” ๋‹จ์ˆœํ•œ ๊ฒฝ์šฐ ์‚ฌ์šฉ

    • @ElementCollection, @CollectionTable

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ์ปฌ๋ ‰์…˜์„ ๊ฐ™์€ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, ๋ณ„๋„์˜ ํ…Œ์ด๋ธ”์ด ํ•„์š”

    • ๊ฐ’ ํƒ€์ž… ์ปฌ๋ ‰์…˜์€ ์—”ํ‹ฐํ‹ฐ์™€ ์ƒ๋ช…์ฃผ๊ธฐ๊ฐ€ ๊ฐ™์Œ (Casecade.ALL + orphanRemoval=true)

    @Entity
    public class Member extends BaseEntity{
      //...
      @ElementCollection
      @CollectionTable(name = "FAVORITE_FOOD", joinColumns =
          @JoinColumn(name = "member_id")
      )
      @Column(name = "food_name")
      private Set<String> favoriteFoods = new HashSet<>();
    
      @ElementCollection
      @CollectionTable(name = "ADDRESS", joinColumns =
          @JoinColumn(name = "member_id")
      )
      private List<Address> addressHistory = new ArrayList<>();
    }
  • ์ €์žฅ

    //..
    member.getAddressHistory().add(new Address("city1", "street1", "zipCode1"));
    member.getAddressHistory().add(new Address("city2", "street2", "zipCode2"));
  • ์กฐํšŒ

    • default. FetchType.LAZY ์ „๋žต ์‚ฌ์šฉ

  • ์ˆ˜์ •

    findMember.getAddressHistory.remove(new Address("oldCity", "street", "12345"));
    findMember.getAddressHistory.add(new Address("newCity", "street", "12345"));
    
    fineMember.getFavoriteFoods().remove("์น˜ํ‚จ");
    fineMember.getFavoriteFoods().add("ํ–„๋ฒ„๊ฑฐ");

๊ฐ’ ํƒ€์ž… ์ปฌ๋ ‰์…˜์˜ ์ œ์•ฝ

  • ๊ฐ’ ํƒ€์ž… ์ปฌ๋ ‰์…˜์€ ์—”ํ‹ฐํ‹ฐ์™€ ๋‹ค๋ฅด๊ฒŒ ์‹๋ณ„์ž ๊ฐœ๋…์ด ์—†์œผ๋ฏ€๋กœ ๋ณ€๊ฒฝ ์‹œ ์ถ”์ ์ด ์–ด๋ ค์šด ํฐ ๋‹จ์  ์กด์žฌ

    • ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ๋ฐœ์ƒํ•˜๋ฉด, ์ฃผ์ธ ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ด€๋œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜๊ณ , ๊ฐ’ ํƒ€์ž… ์ปฌ๋ ‰์…˜์— ์žˆ๋Š” ๋ชจ๋“  ๊ฐ’์„ ๋‹ค์‹œ ์ €์žฅํ•˜๋Š” ๋น„ํšจ์œจ์ ์ธ ๋™์ž‘(์‹๋ณ„์ž๊ฐ€ ์—†์œผ๋ฏ€๋กœ..)

    • ๊ฐ’ ํƒ€์ž… ์ปฌ๋ ‰์…˜์„ ๋งคํ•‘ํ•˜๋Š” ํ…Œ์ด๋ธ”์€ ๋ชจ๋“  ์ปฌ๋Ÿผ์„ ๋ฌถ์–ด์„œ ๊ธฐ๋ณธ ํ‚ค๋กœ ๊ตฌ์„ฑํ•ด์•ผ ํ•จ

  • ๊ฒฐ๋ก ์ ์œผ๋กœ, ์…€๋ ‰ํŠธ ๋ฐ•์Šค์™€ ๊ฐ™์ด ๋ณ€๊ฒฝ์ด ํ•„์š” ์—†๋Š” ๋‹จ์ˆœํ•œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด, ๊ฐ’ ํƒ€์ž… ์ปฌ๋ ‰์…˜ ๋Œ€์‹  ์ผ๋Œ€๋‹ค ๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„๋ฅผ ์ถ”์ฒœ (์‹๋ณ„์ž, ์ง€์†์ ์ธ ๊ฐ’ ์ถ”์ , ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ)

    @Embeddable
    public class Address {
    
        private String city;
        private String street;
        private String zipcode;
        //...
    }
    
    @Entity
    @Table(name = "ADDRESS")
    public class AddressEntity {
    
      @Id @GeneratedValue
      private Long id;
    
      private Address address;
    
      public AddressEntity(String city, String street, String zipcode) {
        this.address = new Address(city, street, zipcode);
      }
        //..
    }
    
    @Entity
    public class Member extends BaseEntity{
      //...
      @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
      @JoinColumn(name = "member_id")
      private List<AddressEntity> addressHistory = new ArrayList<>();
    }

๊ฐ์ฒด์ง€ํ–ฅ ์ฟผ๋ฆฌ ์–ธ์–ด

JPQL (Java Persistence Query Language)

  • SQL์„ ์ถ”์ƒํ™”ํ•œ ๊ฐ์ฒด ์ง€ํ–ฅ ์ฟผ๋ฆฌ ์–ธ์–ด(ํŠน์ • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์˜์กด X)

  • ํ…Œ์ด๋ธ”์ด ์•„๋‹Œ ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด๋ฅผ ๋Œ€์ƒ์œผ๋กœ ์ฟผ๋ฆฌ

  • ๋ฌธ์ž๋กœ JPQL์ด ์ž‘์„ฑ๋˜๋‹ค๋ณด๋‹ˆ ๋™์  ์ฟผ๋ฆฌ ์ž‘์„ฑ์ด ์–ด๋ ค์šด ๋‹จ์ 

    List<Member> result = em.createQuery(
      "select m From Member m where m.name like โ€˜%park%'", Member.class
    ).getResultList();

QueryDSL

  • ๋ฌธ์ž๊ฐ€ ์•„๋‹Œ ์ž๋ฐ”์ฝ”๋“œ๋กœ JPQL ์ž‘์„ฑ

    • ์ปดํŒŒ์ผ ์‹œ์ ์— ๋ฌธ๋ฒ• ์˜ค๋ฅ˜ ์ฒดํฌs

    • ํŽธ๋ฆฌํ•œ ๋™์ ์ฟผ๋ฆฌ ์ž‘์„ฑ

  • JPQL ๋นŒ๋” ์—ญํ• 

    JPAFactoryQuery query = new JPAQueryFactory(em);
    QMember m = QMember.member;
    
    List<Member> list = 
        query.selectFrom(m)
              .where(m.age.gt(18))
              .orderBy(m.name.desc())
              .fetch();

Reference documentation

๋„ค์ดํ‹ฐ๋ธŒ SQL

  • JPQL๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์—†๋Š” ํŠน์ • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์˜์กด์ ์ธ ๊ธฐ๋Šฅ ์‚ฌ์šฉ ์‹œ SQL์„ ์ง์ ‘ ์ž‘์„ฑ

    String sql =
      "SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = โ€˜kimโ€™";
    
    List<Member> resultList =
      em.createNativeQuery(sql, Member.class).getResultList(); 

๊ธฐํƒ€

  • JPA๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ JDBC API, SpringJdbcTemplate, MyBatis ๋“ฑ์„ ํ•จ๊ป˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

  • ๋‹จ, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ์ ์ ˆํ•œ ์‹œ์ (SQL์„ ์‹คํ–‰ํ•˜๊ธฐ ์ง์ „)์— ๊ฐ•์ œ ํ”Œ๋Ÿฌ์‹œ ํ•„์š” (em.flush())

๊ธฐ๋ณธ ๋ฌธ๋ฒ•

๋ฐ˜ํ™˜ ํƒ€์ž…

  • TypeQuery: ๋ฐ˜ํ™˜ ํƒ€์ž…์ด ๋ช…ํ™•ํ•  ๋•Œ ์‚ฌ์šฉ

  • Query: ๋ฐ˜ํ™˜ ํƒ€์ž…์ด ๋ช…ํ™•ํ•˜์ง€ ์•Š์„ ๋•Œ ์‚ฌ์šฉ

์กฐํšŒ

  • query.getResultList(): ๊ฒฐ๊ณผ๊ฐ€ ํ•˜๋‚˜ ์ด์ƒ์ผ ๊ฒฝ์šฐ (๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜)

    • ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด ๋นˆ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜

  • query.getSingleResult(): ๊ฒฐ๊ณผ๊ฐ€ ์ •ํ™•ํžˆ ํ•˜๋‚˜์ผ ๊ฒฝ์šฐ (๋‹จ์ผ ๊ฐ์ฒด ๋ฐ˜ํ™˜)

    • ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด: javax.persistence.NoResultException

    • ๋‘˜ ์ด์ƒ์ด๋ฉด: javax.persistence.NonUniqueResultException

ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ

Member result = em.createQuery("select m from Member m where m.username = :username", Member.class)
                    .setParameter("username", "member1")
                    .getSingleResult();

ํ”„๋กœ์ ์…˜

  • SELECT ์ ˆ์— ์กฐํšŒํ•  ๋Œ€์ƒ์„ ์ง€์ •ํ•˜๋Š” ๋ฐฉ์‹

    • ์—”ํ‹ฐํ‹ฐ ํ”„๋กœ์ ์…˜, ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ํ”„๋กœ์ ์…˜, ์Šค์นผ๋ผ ํƒ€์ž… ํ”„๋กœ์ ์…˜

    • ์Šค์นผ๋ผ ํƒ€์ž… ํ”„๋กœ์ ์…˜์˜ ๊ฒฝ์šฐ ์—ฌ๋Ÿฌ ๊ฐ’ ์กฐํšŒ ์‹œ DTO ์กฐํšŒ ์ถ”์ฒœ

    List<MemberDto> result = 
          em.createQuery("select new jpql.MemberDto(m.username, m.age) from Member m", MemberDto.class)
          .getResultList();

ํŽ˜์ด์ง•

  • setFirstResult(int startPosition) : ์กฐํšŒ ์‹œ์ž‘ ์œ„์น˜

  • setMaxResults(int maxResult) : ์กฐํšŒํ•  ๋ฐ์ดํ„ฐ ์ˆ˜

String jpql = "select m from Member m order by m.name desc";
List<Member> resultList = em.createQuery(jpql, Member.class)
    .setFirstResult(0)
    .setMaxResults(10)
    .getResultList();

์กฐ์ธ

  • ๋‚ด๋ถ€ ์กฐ์ธ:

    • SELECT m FROM Member m [INNER] JOIN m.team t

  • ์™ธ๋ถ€ ์กฐ์ธ

    • SELECT m FROM Member m LEFT [OUTER] JOIN m.team t

  • ์„ธํƒ€ ์กฐ์ธ

    • select count(m) from Member m, Team t where m.username = t.name

.

  • Join On (JPA 2.1, Hibernate 5.1 ์ด์ƒ)

    • ์กฐ์ธ ๋Œ€์ƒ ํ•„ํ„ฐ๋ง

      • SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'

    • ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์—†๋Š” ์—”ํ‹ฐํ‹ฐ ์™ธ๋ถ€ ์กฐ์ธ

      • SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name

์„œ๋ธŒ ์ฟผ๋ฆฌ

select m from Member m
where m.age > (select avg(m2.age) from Member m2)
  • ์ง€์› ํ•จ์ˆ˜

    • [NOT] EXISTS (Subquery): ์„œ๋ธŒ์ฟผ๋ฆฌ์— ๊ฒฐ๊ณผ๊ฐ€ ์กด์žฌํ•˜๋ฉด ์ฐธ

    • {ALL | ANY | SOME} (Subquery)

      • ALL: ๋ชจ๋‘ ๋งŒ์กฑํ•˜๋ฉด ์ฐธ

      • ANY, SOME: ์กฐ๊ฑด์„ ํ•˜๋‚˜๋ผ๋„ ๋งŒ์กฑํ•˜๋ฉด ์ฐธ

    • [NOT] IN (Subquery): ์„œ๋ธŒ์ฟผ๋ฆฌ์˜ ๊ฒฐ๊ณผ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ๊ฐ™์€ ๊ฒƒ์ด ์žˆ์œผ๋ฉด ์ฐธ

.

  • ํ•œ๊ณ„

    • JPA๋Š” WHERE, HAVING ์ ˆ์—์„œ๋งŒ ์„œ๋ธŒ ์ฟผ๋ฆฌ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

      • Hibernate๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ SELECT ์ ˆ๋„ ๊ฐ€๋Šฅ

    • FROM ์ ˆ์˜ ์„œ๋ธŒ ์ฟผ๋ฆฌ๋Š” ํ˜„์žฌ JPQL์—์„œ ๋ถˆ๊ฐ€๋Šฅ (์กฐ์ธ์œผ๋กœ ํ’€์–ด๋ณด๊ธฐ)

ํƒ€์ž… ํ‘œํ˜„

  • ๋ฌธ์ž: 'She''s'

  • ์ˆซ์ž: 10L(Long), 10D(Double), 10F(Float)

  • Boolean: true, false

  • ENUM: jpql.MemberType.Admin (ํŒจํ‚ค์ง€๋ช… ํฌํ•จ) or query.setParameter()

    Member result = em.createQuery("select m from Member m where m.type = :userType", Member.class)
                    .setParameter("userType", MemberType.ADMIN)
                    .getResultList();
  • ์—”ํ‹ฐํ‹ฐ ํƒ€์ž…: ์ƒ์† ๊ด€๊ณ„์—์„œ ์‚ฌ์šฉ

    • ์กฐํšŒ ๋Œ€์ƒ์„ ํŠน์ • ์ž์‹์œผ๋กœ ํ•œ์ •

      --JPQL
      select i from Item i where type(i) IN (Book, Movie) 
      
      --SQL
      select i from i where i.DTYPE in ('Book', 'Movie')
    • ํƒ€์ž… ์บ์ŠคํŒ…

      --JPQL
      select i from Item i where treat(i as Book).auther = 'kim'
      
      --SQL
      select i.* from Item i where i.DTYPE = 'B' and i.auther = 'kim'
  • COALESCE : ํŠน์ • ์ปฌ๋Ÿผ์ด Null์ผ ๊ฒฝ์šฐ ๋Œ€์ฒด ๊ฐ’ ๋ฐ˜ํ™˜

    select coalesce(m.username,'์ด๋ฆ„ ์—†๋Š” ํšŒ์›') from Member m
  • NULLIF : ์ง€์ •๋œ ๋‘ ์‹์ด ๊ฐ™์œผ๋ฉด Null ๋ฐ˜ํ™˜

    select NULLIF(m.username, '๊ด€๋ฆฌ์ž') from Member m

์ค‘๊ธ‰ ๋ฌธ๋ฒ•

๊ฒฝ๋กœ ํ‘œํ˜„์‹

  • ์ƒํƒœ ํ•„๋“œ (m.username): ๊ฒฝ๋กœ ํƒ์ƒ‰์˜ ์ข…์  (ํƒ์ƒ‰ ๋ถˆ๊ฐ€)

  • ๋‹จ์ผ ๊ฐ’ ์—ฐ๊ด€ ๊ฒฝ๋กœ (m.team): ๋ฌต์‹œ์  ๋‚ด๋ถ€ ์กฐ์ธ(inner join) ๋ฐœ์ƒ (ํƒ์ƒ‰ ๊ฐ€๋Šฅ)

  • ์ปฌ๋ ‰์…˜ ๊ฐ’ ์—ฐ๊ด€ ๊ฒฝ๋กœ (m.orders): ๋ฌต์‹œ์  ๋‚ด๋ถ€ ์กฐ์ธ ๋ฐœ์ƒ (ํƒ์ƒ‰ ๋ถˆ๊ฐ€)

    • ๋ช…์‹œ์  ์กฐ์ธ์„ ํ†ตํ•ด ๋ณ„์นญ์œผ๋กœ ํƒ์ƒ‰ ๊ฐ€๋Šฅ

.

  • ๋ช…์‹œ์  ์กฐ์ธ: JOIN ํ‚ค์›Œ๋“œ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉ

    • ์กฐ์ธ์ด ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ์„ ํ•œ ๋ˆˆ์— ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ฟผ๋ฆฌ ํŠœ๋‹์ด ํŽธ๋ฆฌ

    • ์กฐ์ธ์€ SQL ํŠœ๋‹์— ์ค‘์š”ํ•œ ํฌ์ธํŠธ์ด๋ฏ€๋กœ, ๊ฐ€๊ธ‰์  ๋ช…์‹œ์  ์กฐ์ธ์„ ์‚ฌ์šฉํ•˜์ž !!

    select m, t from Member m join m.team t
  • ๋ฌต์‹œ์  ์กฐ์ธ: ๊ฒฝ๋กœ ํ‘œํ˜„์‹์— ์˜ํ•ด ๋ฌต์‹œ์ ์œผ๋กœ ์กฐ์ธ ๋ฐœ์ƒ

    • ๋‚ด๋ถ€ ์กฐ์ธ๋งŒ ๊ฐ€๋Šฅ

    • ์กฐ์ธ ์ฟผ๋ฆฌ๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ค์›€

    select m.team from Member m

์—”ํ‹ฐํ‹ฐ ์ง์ ‘ ์‚ฌ์šฉ

  • JPQL์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋ฉด SQL์—์„œ ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์˜ ๊ธฐ๋ณธ ํ‚ค ๊ฐ’์„ ์‚ฌ์šฉ

Named Query

  • ๋ฏธ๋ฆฌ ์ •์˜ํ•ด๋‘๊ณ  ์‚ฌ์šฉํ•˜๋Š” JPQL ์ •์  ์ฟผ๋ฆฌ

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ๋”ฉ ์‹œ์ ์— ์ฟผ๋ฆฌ ๊ฒ€์ฆ ๋ฐ ์บ์‹ฑ ํ›„ ์žฌ์‚ฌ์šฉ

๋ฒŒํฌ ์—ฐ์‚ฐ

  • ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ ๋ณ€๊ฒฝ (UPDATE, DELETE)

    • executeUpdate()๋กœ ์˜ํ–ฅ์„ ๋ฐ›์€ ์—”ํ‹ฐํ‹ฐ ์ˆ˜ ํ™•์ธ ๊ฐ€๋Šฅ

  • ๋ฒŒํฌ ์—ฐ์‚ฐ์€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ง์ ‘ ์ฟผ๋ฆฌ๋ฅผ ์ „๋‹ฌํ•˜๋ฏ€๋กœ

    • ๋ฒŒํฌ ์—ฐ์‚ฐ์„ ๋จผ์ € ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜

    • ๋ฒŒํฌ ์—ฐ์‚ฐ ์ˆ˜ํ–‰ ํ›„ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ์ดˆ๊ธฐํ™” (em.clear())

@Entity
@NamedQuery(
        name = "Member.findByUsername",
        query="select m from Member m where m.username = :username")
public class Member {
 ...
}
//
List<Member> resultList =
 em.createNamedQuery("Member.findByUsername", Member.class)
    .setParameter("username", "ํšŒ์›1")
    .getResultList();

ํŽ˜์น˜ ์กฐ์ธ

Fetch Join

  • JPQL ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด ์ œ๊ณต

  • ์ฟผ๋ฆฌ ํ•œ ๋ฒˆ์— ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋‚˜ ์ปฌ๋ ‰์…˜์„ ํ•จ๊ป˜ ์กฐํšŒ (์ฆ‰์‹œ ๋กœ๋”ฉ ์šฐ์„  ์ ์šฉ)

    • ์ผ๋ฐ˜ ์กฐ์ธ์—์„œ๋Š” ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•จ๊ป˜ ์กฐํšŒํ•˜์ง€ ์•Š์Œ

  • N + 1 ์ด์Šˆ์˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

  • [ LEFT [OUTER] / INNER ] JOIN FETCH

    SELECT m FROM Member m JOIN FETCH m.team;

Collection Fetch Join

  • ์ผ๋Œ€๋‹ค ๊ด€๊ณ„์—์„œ์˜ ํŽ˜์น˜ ์กฐ์ธ

    -- JPQL
    SELECT t FROM Team t JOIN FETCH t.members WHERE t.name = 'ํŒ€A'
    
    -- SQL
    SELECT T.*, M.*
    FROM TEAM T
    INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
    WHERE T.NAME = 'ํŒ€A
  • ์ผ๋Œ€๋‹ค ๊ด€๊ณ„์—์„œ์˜ N+1 ๋ฌธ์ œ๋Š” batchSize ์„ค์ •์œผ๋กœ ํ•ด๊ฒฐ ๊ฐ€๋Šฅ

    • LAZY ๋™์ž‘ ์‹œ, IN ์ฟผ๋ฆฌ๋กœ size ๋งŒํผ ํ•œ ๋ฒˆ์— ์กฐํšŒ

      • ๊ฐœ๋ณ„ ์„ฑ์ •

        @BatchSize(size = 100)
        @OneToMany(mappedBy = "team")
        List<Member> members = new ArrayList<Member>();
      • Global ์„ค์ •

        hibernate.default_batch_fetch_size: 100
    • SQL

      select *
      from member
      where member.team_id in (?, ?, ?..)

DISTINCT

  • JPQL์—์„œ DISTINCT๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ

    • ์ฟผ๋ฆฌ์— DISTINCT ์ถ”๊ฐ€

    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ค‘๋ณต ์—”ํ‹ฐํ‹ฐ ์ œ๊ฑฐ

ํ•œ๊ณ„

  • ํŽ˜์น˜ ์กฐ์ธ ๋Œ€์ƒ์—๋Š” ๋ณ„์นญ ๋ถˆ๊ฐ€

    • ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„ ์‚ฌ์ƒ(N์— ํ•ด๋‹นํ•˜๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์กฐํšŒ๋ฅผ ๊ธฐ๋Œ€)๊ณผ ๋งž์ง€ ์•Š์Œ

    • t.members์—์„œ ์กฐ๊ฑด์„ ๊ฑธ๊ณ  ์‹ถ๋‹ค๋ฉด, member๋ฅผ select์ ˆ์—์„œ ์‚ฌ์šฉํ•˜์ž.

  • ์ปฌ๋ ‰์…˜์€ ํ•œ ๊ฐœ๋งŒ ํŽ˜์น˜ ์กฐ์ธ ๊ฐ€๋Šฅ

  • ์ปฌ๋ ‰์…˜ ํŽ˜์น˜ ์กฐ์ธ์„ ํ•˜๋ฉด ํŽ˜์ด์ง• API ์‚ฌ์šฉ ๋ถˆ๊ฐ€

    • ๋‹จ์ผ ๊ฐ’ ์—ฐ๊ด€ ํ•„๋“œ(1:1/N:1)๋Š” ํŽ˜์น˜ ์กฐ์ธ์„ ํ•ด๋„ ํŽ˜์ด์ง• ๊ฐ€๋Šฅ

    • ์ปฌ๋ ‰์…˜ ํŽ˜์น˜ ์กฐ์ธ์œผ๋กœ ์ปฌ๋ ‰์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž˜๋ฆฌ๋Š” ํ˜„์ƒ ๋ฐœ์ƒ

Last updated