<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>computer_study</title>
    <link>https://knowable.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 13 Jul 2026 23:00:36 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>knowable</managingEditor>
    <item>
      <title>[JPA] 14. 컬렉션과 부가 기능</title>
      <link>https://knowable.tistory.com/109</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 컬렉션&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 명세에 자바 컬렉션에 대한 언급이 없기에 JPA 구현체에 따라 제공하는 기능이 다를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(책에선 하이버네이트 구현체를 기준으로)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;JPA와 컬렉션&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하이버네이트는 엔티티를 영속 상태로 만들 때 컬렉션 필드를 하이버네이트에서 준비한 컬렉션으로 감싼다.&lt;/li&gt;
&lt;li&gt;ex) ArrayList타입이 하이버네이트가 제공하는 PersistentBag타입으로 변경된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Collection, List -&amp;gt; PersistentBag으로 변경
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ArrayList로 초기화하면 된다.&lt;/li&gt;
&lt;li&gt;Private Collection&amp;lt;CollectionChild&amp;gt; collection = new ArrayList&amp;lt;CollectionChild&amp;gt;();&lt;/li&gt;
&lt;li&gt;엔티티 추가 시 중복된 엔티티가 있는지 비교하지 않고 단순히 저장만 하면 된다.&lt;/li&gt;
&lt;li&gt;엔티티를 추가해도 지연 로딩된 컬렉션을 초기화하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Set -&amp;gt; PersistentSet으로 변경
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HashSet으로 초기화&lt;/li&gt;
&lt;li&gt;private Set&amp;lt;SetChild&amp;gt; set = new HashSet&amp;lt;SetChild&amp;gt;();&lt;/li&gt;
&lt;li&gt;Set은 엔티티를 추가할 때 중복된 엩티티가 있는지 비교해야 한다.&lt;/li&gt;
&lt;li&gt;엔티티를 추가할 때 로딩된 컬렉션을 초기화 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;List + @OrderColumn -&amp;gt; PersistentList로 변경
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@OrderColumn을 추가하면 순서가 있는 특수한 컬렉션으로 인식 (데이터베이스에 순서 값을 저장해서 조회할 떄 사용)&lt;/li&gt;
&lt;li&gt;실무에서 사용하기엔 단점이 많다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Comment를 INSERT할 경우 UPDATE하는 SQL이 추가로 발생한다.&lt;/li&gt;
&lt;li&gt;List변경 시 연관된 많은 위치 값들을 변경해야 한다.&lt;/li&gt;
&lt;li&gt;컬렉션을 순회할 때 NullPointerException이 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;@OrderBy&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@OrderColumn이 DB에 순서용 컬럼을 매핑해서 관리한다면 @OrderBy는 ORDER BY절을 사용해서 컬렉션을 정렬&lt;/li&gt;
&lt;li&gt;순서용 컬럼을 매핑하지 않아도 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. @Converter&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티의 데이터를 변환해서 DB에 저장할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1664901612894&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE MEMBER (
    ID VARCHAR(255) NOT NULL,
    USERNAME VARCHAR(255),
    VIP VARCHAR(1) NOT NULL,
    PRIMARY KEY (ID)
)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1664901689030&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Member {
    
    @Id
    private String id;
    private String username;
    
    // DB에 저장되기 직전에 BooleanToYNConverter(Bollean을 YN으로 변환해주는 컨버터)가 동작하도록
    @Convert(converter=BooleanToYNConverter.class)
    private bollean vip;
    
    // Getter, Setter
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 Boolean 타입에 컨버터를 적용할 수도 있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Converter(AutoApply = true) 사용&lt;/li&gt;
&lt;li&gt;@Converter를 지정하지 않아도 모든 Boolean 타입에 대해 자동으로 컨버터가 적용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 리스너&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티의 생명주기에 따른 이벤트를 처리하기 위함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;이벤트 종류&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;507&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KzzA6/btrNL9JpUas/M2eD0CkOfF0QG41rMzeCk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KzzA6/btrNL9JpUas/M2eD0CkOfF0QG41rMzeCk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KzzA6/btrNL9JpUas/M2eD0CkOfF0QG41rMzeCk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKzzA6%2FbtrNL9JpUas%2FM2eD0CkOfF0QG41rMzeCk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;275&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;507&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;이벤트 적용 위치&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티에 직접 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@PrePersist, @PostPersist등의 어노테이션으로 지정한 메소드가 이벤트 발생 시 마다 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;별도의 리스너 등록
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@EntityListeners(DuckListener.class) 와 같이 리스너를 사용할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기본 리스너 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;META-INF/orm.xml에 default리스너로 등록하여 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;여러 리스너 등록 시 이벤트 호출 순서
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 리스너&lt;/li&gt;
&lt;li&gt;부모 클래스 리스너&lt;/li&gt;
&lt;li&gt;리스너&lt;/li&gt;
&lt;li&gt;엔티티&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;세밀한 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트를 잘 활용하면 대부분의 엔티티에 공통으로 적용하는 등록 일자, 수정 일자 처리 등에 대한 기록을 리스너 하나로 처리할 수 있다.&lt;/li&gt;
&lt;li&gt;javax.persistence.ExcludeDefaultListeners -&amp;gt; 기본 리스너 무시&lt;/li&gt;
&lt;li&gt;javax.persistence.ExcludeSuperclassListeners -&amp;gt; 상위 클래스 이벤트 리스너 무시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 엔티티 그래프&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 그래트의 기능은 엔티티 조회시점에 연관된 엔티티들을 함께 조회하는 기능&lt;/li&gt;
&lt;li&gt;Named 엔티티 그래프
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 그래프를 정적으로 정의&lt;/li&gt;
&lt;li&gt;@NamedEntityGraph로 정의&lt;/li&gt;
&lt;li&gt;@NamedEntityGraph(name =&quot;Order.withMember&quot;, attributeNodes = (@NamedAttributeNode(&quot;Member&quot;))
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name -&amp;gt; 엔티티 그래프의 이름을 정의&lt;/li&gt;
&lt;li&gt;attributeNodes -&amp;gt; 함께 조회할 속성을 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Named 엔티티 그래프를 사용하기 위해선
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정의한 엔티티 그래프를 em.getEntityGraph(&quot;Order.withMember&quot;) 이런 식으로 찾아올 수 있다.&lt;/li&gt;
&lt;li&gt;조회 시에는 Hints 값으로 그래프를 주어 엔티티 그래프를 사용해 조회할 수도 있다.&lt;/li&gt;
&lt;li&gt;Order-&amp;gt; OrderItem -&amp;gt; Item 이런식으로 조회하고 싶다면 subgraph를 사용하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1664903701098&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EntityGraph graph = em.getEntityGraph(&quot;Order.withMember&quot;);

Map hints = new HashMap();
hints.put(&quot;javax.persistence.fetchgraph&quot;, graph);

Order order = em.find(Order.class, orderId, hints);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동적으로 정의하는 그래프
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;createEntityGraph()메소드를 사용해서 엔티티 그래프를 동적으로 구성할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1664904075298&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EntityGraph&amp;lt;Order&amp;gt; graph = em.createEntityGraph(Order.class) // 그래프를 통적으로 구성 가능
graph.addAttributeNodes(&quot;member&quot;); // Order.member 속성을 엔티티 그래프에 포함
Subgraph&amp;lt;OrderItem&amp;gt; orderItems = graph.addSubgraph(&quot;orderItems&quot;); // 서브그래프
orderItems.addAttributeNodes(&quot;item&quot;); // 서브 그래프가 item 속성을 포함하도록

Map hints = new HashMap();
hints.put(&quot;javax.persistence.fetchgraph&quot;, graph);

Order order = em.find(Order.class, orderId, hints);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스터디/자바 ORM표준 JPA 프로그래밍</category>
      <author>knowable</author>
      <guid isPermaLink="true">https://knowable.tistory.com/109</guid>
      <comments>https://knowable.tistory.com/109#entry109comment</comments>
      <pubDate>Wed, 5 Oct 2022 02:22:30 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 13. 웹 애플리케이션과 영속성 관리</title>
      <link>https://knowable.tistory.com/108</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;스프링이나 J2EE 컨테이너 환경에서 JPA가 동작하는 동작 방식을 이해한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 트랜잭션 범위의 영속성 컨텍스트&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링이나 J2EE컨테이너 환경에서 JPA사용 시 컨테이너가 제공하는 전략을 따라야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;스프링 컨테이너의 기본 전략&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 범위의 영속성 컨텍스트 전략을 사용. (트랜잭션 시작 시 영속성 컨텍스트 생성, 트랜잭션 끝나면 영속성 컨텍스트 종료)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSDH53/btrNEwKIx82/pzidVbXkfjBjWON18KyHdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSDH53/btrNEwKIx82/pzidVbXkfjBjWON18KyHdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSDH53/btrNEwKIx82/pzidVbXkfjBjWON18KyHdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSDH53%2FbtrNEwKIx82%2FpzidVbXkfjBjWON18KyHdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;511&quot; height=&quot;266&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JeEmC/btrNAxjatnL/nPo6OKj9v8KuPSFZ9rhLW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JeEmC/btrNAxjatnL/nPo6OKj9v8KuPSFZ9rhLW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JeEmC/btrNAxjatnL/nPo6OKj9v8KuPSFZ9rhLW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJeEmC%2FbtrNAxjatnL%2FnPo6OKj9v8KuPSFZ9rhLW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;477&quot; height=&quot;250&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링에서 서비스 계층에 @Transactional을 선언해서 트랜잭션을 시작&lt;/li&gt;
&lt;li&gt;이 때 AOP가 먼저 동작하게 된다.&lt;/li&gt;
&lt;li&gt;메소드 종료 시 트랜잭션 커밋하며 종료된다. (이 때 JPA는 영속성 컨텍스트를 플러시하여 변경 내용을 DB에 반영)&lt;/li&gt;
&lt;li&gt;예외 발생 시 플러시를 호출하지 않는다.&lt;/li&gt;
&lt;li&gt;트랜잭션이 같으면 같은 영속성 컨텍스트 사용 (다르면 다른 영속성 컨텍스트 사용 (같은 엔티티 매이저를 사용하더라도))&lt;/li&gt;
&lt;li&gt;이런 트랜잭션과 복잡한 멀티 스레드 상황을 컨테이너가 처리해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 준영속 상태와 지연 로딩&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스, 레포지토리 계층이 아닌 컨트롤러나 뷰 같은 프리젠테이션 계층에서는 준영속 상태.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경 감지 기능이 동작하지 않는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프리젠테이션 계층에선 변경이 끝난 이후 보여주는 것에만 집중하면 되므로 동작하지 않아도 별 상관 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;지연로딩이 동작하지 않는다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰 렌더링 시 연관된 엔티티를 사용하고, 엔티티를 지연 로딩으로 설정해서 조회하는 상황&lt;br /&gt;영속성 컨텍스트가 없는 준영속 상태에선 지연로딩 시도 시 예외 발생&lt;/li&gt;
&lt;li&gt;해결 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰가 필요한 엔티티를 미리 로딩&lt;/li&gt;
&lt;li&gt;OSIV를 사용해서 엔티티를 항상 영속 상태로 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;뷰가 필요한 엔티티를 미리 로딩하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;글로벌 페치 전략 수정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;글로벌 페치 전략을 지연 로딩에서 즉시 로딩으로 변경&lt;/li&gt;
&lt;li&gt;@ManyToOne(fetch = FetchType.EAGER) 로 설정 가능&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용하지 않는 엔티티를 로딩&lt;/li&gt;
&lt;li&gt;N+1문제 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연관 관계가 설정된 엔티티를 조회할 경우 조회한 데이터 수 만큼 추가로 SQL을 사용해서 조회하는 문제&lt;/li&gt;
&lt;li&gt;order와 member가 연관되어있을 때, 조회한 order엔티티가 10개라면 member를 조회하는 SQL도 10번 실행된다&lt;/li&gt;
&lt;li&gt;JPQL페치 조인으로 해결 가능(15.4.1절)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JPQL 페치 조인
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPQL을 호출하는 시점에 함께 로딩할 엔티티를 선택하는 방법&lt;/li&gt;
&lt;li&gt;조인 명령어 마지막에 fetch를 넣어주면 된다.&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 화면에 맞춘 레포지토리 메소드가 증가할 수 있다 (프리젠테이션 계층이 접근 계층을 침범)&lt;/li&gt;
&lt;li&gt;적절한 타협점을 찾아야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1664814415227&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;JPQL:
    select o
    from Order o
    join fetch o.member

SQL:
    select o.*, m.*
    from Order o
    join Member m on o.MEMBER_ID=m.MEMBER_ID&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;강제로 초기화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영속성 컨텍스트가 살아있을 때 프리젠테이션 계층이 필요한 엔티티를 강제로 초기화해서 반환하는 방법&lt;/li&gt;
&lt;li&gt;프록시 객체는 실제 사용하는 시점에 초기화되므로 실제 값을 호출해버린다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) order.getMember() 까지 하면 프록시 객체만 반환하지만&lt;br /&gt;order.gerMember().getName()까지 해서 실제 값을 사용하는 시점에 초기화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하이버네이트를 사용하면 initialize()메소드를 사용해서 초기화도 가능&lt;/li&gt;
&lt;li&gt;뷰가 필요한 엔티티에 따라 서비스 로직을 변경해야되는 상황(프리젠테이션 계층이 서비스 계층을 침범한 것)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 계층에서 프리젠테이션 계층을 위한 프록시 초기화 역할을 분리해야 한다 (FACADE계층이 담당)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;FACADE 계층 추가
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Facade에서 트랜잭션을 시작한다.&lt;/li&gt;
&lt;li&gt;역할과 특징
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프리젠테이션 계층과 도메인 모델 계층 간의 논리적 의존성 분리&lt;/li&gt;
&lt;li&gt;프리젠테이션 계층에서 필요한 프록시 객체 초기화&lt;/li&gt;
&lt;li&gt;서비스 계층을 호출해서 비즈니스 로직 실행&lt;/li&gt;
&lt;li&gt;리포지토리를 직접 호출해서 뷰가 요구하는 엔티티 찾음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;중간 계층이 하나 더 있는 것이기에 더 많은 코드를 작성해야 하는 단점이 있다 (단순히 서비스 계층을 호출하는 코드가 많을 것이다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;준영속 상태와 지연 로딩의 문제점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방법을 사용한다고 해도, 준영속 상태라는 것 때문에 번거로운 것은 사실&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영속성 컨텍스트를 뷰까지 열어두는 것이 가장 좋은데 이게 OSIV&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. OSIV&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영속성 컨텍스트를 뷰까지 열어두는 방법 (뷰에서도 지연 로딩 사용이 가능하다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;839&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yid1Q/btrNCgBpLB2/yMFEahkTMHsxQhrNqKP1B0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yid1Q/btrNCgBpLB2/yMFEahkTMHsxQhrNqKP1B0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yid1Q/btrNCgBpLB2/yMFEahkTMHsxQhrNqKP1B0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyid1Q%2FbtrNCgBpLB2%2FyMFEahkTMHsxQhrNqKP1B0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;263&quot; data-origin-width=&quot;839&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 OSIV
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청이 들어올 때 영속성 컨텍스트를 생성해 유지하고 요청이 끝나면 트랜잭션도 끝낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기존 OSIV 문제점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨트롤러나 뷰 같은 프리젠테이션 계층이 엔티티를 변경할 수 있다&lt;/li&gt;
&lt;li&gt;변경하지 못하게 하는 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티를 읽기 전용 인터페이스로 제공&lt;/li&gt;
&lt;li&gt;엔티티 레핑&lt;/li&gt;
&lt;li&gt;DTO만 반환 (단순히 데이터만 전달하는 객체 DTO를 생성해서 반환)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위 방법 모두 코드량이 상당히 증가하게 된다.&lt;/li&gt;
&lt;li&gt;이런 요청당 트랜잭션 방식의 OSIV는 최근 거의 사용되지 않는다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;최근에는 비즈니스 계층에서만 트랜잭션을 유지하는 방식의 OSIV를 사용 (스프링 프레임워크가 제공)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WyJMV/btrNLAyy0Tb/J2SnBl8GjwLVEHMOOIQO00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WyJMV/btrNLAyy0Tb/J2SnBl8GjwLVEHMOOIQO00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WyJMV/btrNLAyy0Tb/J2SnBl8GjwLVEHMOOIQO00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWyJMV%2FbtrNLAyy0Tb%2FJ2SnBl8GjwLVEHMOOIQO00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;332&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특징
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청이 들어올 때 부터 끝날 때 까지 같은 영속성 컨텍스트를 유지 (한 번 조회한 엔티티는 요청이 끝날 때 까지 영속상태 유지)&lt;/li&gt;
&lt;li&gt;엔티티 수정은 트랜잭션이 있는 계층에서만 동작.(프리젠테이션 계층은 조회만 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 영속성 컨텍스트를 여러 트랜잭션이 공유&lt;/li&gt;
&lt;li&gt;프리젠테이션 계층에서 엔티티 수정 후 비즈니스 로직을 수정하면 엔티티가 수정될 수 있다&lt;/li&gt;
&lt;li&gt;프리젠테이션 계층에서 지연 로딩에 의한 SQL이 실행. 성능 튜닝 시 여러 계층을 확인해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OSIV사용하면 화면 출력 시 엔티티를 유지하면서 객체 그래프를 자유롭게 탐색할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 화면을 구성할 때는 이 방법이 효과적이진 않다.&lt;/li&gt;
&lt;li&gt;여러 테이블을 조인하거나, 통계 화면을 보여줄 때 엔티티로 조회하기 보단 데이터들만 조회해서 DTO로 반환하는 것이 더 좋을 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;같은 JVM을 벗어난 원격 상황에서 사용할 수 없다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 필요한 데이터를 모두 JSON으로 생성해서 반환해야 한다.&lt;/li&gt;
&lt;li&gt;JSON으로 생성한 API는 외부API와 내부API로 자누어진다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부API
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부에 노출&lt;/li&gt;
&lt;li&gt;한 번 정의하면 변경이 어렵다&lt;/li&gt;
&lt;li&gt;엔티티는 생각보다 자주 변경되는데, 변경 시 노출하는 JSON API도 함께 변경되므로 엔티티를 직접 노출하기보단 DTO로 변환해서 노출하는 것이 안전(?)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;내부 API
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부에 노출하지 않는다&lt;/li&gt;
&lt;li&gt;언제든지 변경 가능&lt;/li&gt;
&lt;li&gt;엔티티를 변경해도 클라이언트와 서버를 동시에 수정할 수 있기에 엔티티를 직접 노출해도 괜찮다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스터디/자바 ORM표준 JPA 프로그래밍</category>
      <author>knowable</author>
      <guid isPermaLink="true">https://knowable.tistory.com/108</guid>
      <comments>https://knowable.tistory.com/108#entry108comment</comments>
      <pubDate>Tue, 4 Oct 2022 02:20:13 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 12. 스프링 데이터 JPA</title>
      <link>https://knowable.tistory.com/107</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 스프링 데이터 JPA 소개&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트&lt;/li&gt;
&lt;li&gt;CRUD처리를 위한 공통 인터페이스를 제공&lt;/li&gt;
&lt;li&gt;인터페이스 작성만 하면 실행 시점에 &quot;스프링 데이터 JPA&quot;가 구현 객체를 동적으로 생성해서 주입해준다.&lt;br /&gt;(데이터 접근 계층 구현 없이 인터페이스만 작성해도 개발을 완료할 수 있다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1664121564439&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// JPA의 반복적인 CURD
public class MemberRepositroy {
    @PersistenceContext
    EntityManager em;
    
    public void save(Member member) {...}
    public Member findOnd(Long id) {...}
    public List&amp;lt;Member&amp;gt; findAll() {...}
    
    public Member findByUsername(String username) {...}
}
public class ItemRepositroy {
    @PersistenceContext
    EntityManager em;
    
    public void save(Item item) {...}
    public Member findOnd(Long id) {...}
    public List&amp;lt;Member&amp;gt; findAll() {...}
}
// 하는 일이 비슷하게 코드를 작성한다
// GenericDAO를 만들어 상속을 사용할 수도 있지만, 부모에 종속되는 단점이 있다.

////////
// 스프링 데이터 JPA 적용
public interface MemberRepositroy extends JpaRepository&amp;lt;Member, Long&amp;gt;{
    Member findByUsername(String username;)
}
public interface ItemRepositroy extends JpaRepository&amp;lt;Item, Long&amp;gt;{
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 스프링 데이터 JPA 설정&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spring-data-jpa 라이브러리 사용&lt;/li&gt;
&lt;li&gt;환경설정 완려 시 스프링 데이터 JPA는 app실행 시 basePackage에 있는 레포지토리 인터페이스들을 찾아서 해당 인터페이스를 구현한 클래스를 동적으로 생성한다음 빈으로 등록한다.&lt;/li&gt;
&lt;li&gt;개발자가 직접 구현 클래스를 만들 필요가 없어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 공통 인터페이스 기능&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단한 CRUD기능을 처리하는 JpaRepositroy 인터페이스 제공&lt;/li&gt;
&lt;li&gt;스프링 데이터가 사용하는 인터페이스에 추가적으로 JPA에 특화된 기능을 제공하는 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b17zgH/btrMXjeqOQT/JWmIILfCwM9H6CQERC69gK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b17zgH/btrMXjeqOQT/JWmIILfCwM9H6CQERC69gK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b17zgH/btrMXjeqOQT/JWmIILfCwM9H6CQERC69gK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb17zgH%2FbtrMXjeqOQT%2FJWmIILfCwM9H6CQERC69gK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;490&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 쿼리 메소드 기능&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.메소드 이름으로 쿼리 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interface에 findByEmailAndNmae(String email, String name) 정의 후 실행 시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 데이터 JPA는 메소드 이름을 분석해서 아래 JPQL을 생성하고 실행한다&lt;/p&gt;
&lt;pre id=&quot;code_1664123102095&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select m from Member m where m.email = ?1 and m.name = ?2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티 필드명이 변경된다면 인터페이스에 정의한 메소드 이름을 함께 변경해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;정해진 규칙&lt;/a&gt;에 따라서 이름을 지어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.JPA NamedQuery&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메소드 이름으로 JPA Named 쿼리를 호출하는 기능을 제공한다. (쿼리에 이름을 부여해서 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1664123924352&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 쿼리 정의 (애노테이션) , XML에 정의도 가능
@Entity
@NamedQuery(
    name = &quot;Member.findByUsername&quot;,
    query = &quot;select m from Member m where m.username = :username&quot;)
public class Member {
...
}

// 사용 방법. 메소드 이름만으로 호출 가능
public interface MemberRepository extends JpaRepository&amp;lt;Member, Long&amp;gt;{
    List&amp;lt;Member&amp;gt; findByUsername(@Param(&quot;username&quot;) String username);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3.@Query, 레포지토리 메소드에 쿼리 정의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레포지토리 메소드에 직접 쿼리를 정의&lt;/p&gt;
&lt;pre id=&quot;code_1664124159889&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface MemberRepository extends JpaRepository&amp;lt;Member, Long&amp;gt;{
    @Query(&quot;select m from Member m where m.username = ?1&quot;)
    Member findByUsername(String username);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라 본다&lt;/li&gt;
&lt;li&gt;애플리케이션 실행 시점에 문법 오류를 발견할 수 있는 장점이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 파라미터 바인딩&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 데이터 JPA는 위치 기반 파라미터 바인딩, 이름 기반 파라미터 바인딩 모두 지원&lt;/li&gt;
&lt;li&gt;코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩 사용을 추천&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 벌크성 수정 쿼리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Modifying 사용&lt;/li&gt;
&lt;li&gt;벌크성 수정 쿼리 -&amp;gt; 대량의 데이터들을 쿼리를 통해 수정&lt;/li&gt;
&lt;li&gt;실행 이후 영속성 컨텍스트를 초기화 하고싶다면 clearAutomatically = true로 옵션을 주면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 반환 타입&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과가 한 건 이상이면 컬렉션 인터페이스를 사용&lt;/li&gt;
&lt;li&gt;단건이면 반환 타입을 지정 -&amp;gt; 2건 이상 조회 시 NonUniqueResultException 발생&lt;/li&gt;
&lt;li&gt;조회 결과가 없으면 빈 컬렉션을 반환하고 단건은 null을 반환(원래 NoResultException을 반환하지만 다루기가 어려우므로, 스프링 데이터 JPA는 null을 반환한다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 페이징과 정렬&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Sort로 정렬, Pageable로 페이징 사용 가능.&lt;/li&gt;
&lt;li&gt;Page 인터페이스는 다양한 메소드를 지원한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1664125790805&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Page&amp;lt;Member&amp;gt; findByName(String name, Pageable pageable);
List&amp;lt;Member&amp;gt; findByName(String name, Pageable pageble);
List&amp;lt;Member&amp;gt; findByName(String name, Sort sort);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 힌트&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@QueryHints 사용&lt;/li&gt;
&lt;li&gt;JPA구현체에서 제공하는 힌트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- Lock&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Lock을 사용하는데, 이는 16장에서 다룸&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 명세(specification)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색하기 위한 제약 조건들을 정의한 클래스&lt;/li&gt;
&lt;li&gt;JPA는 JPA Criteria로 이 개념을 사용할 수 있도록 지원한다. (10.3절)&lt;/li&gt;
&lt;li&gt;여러 Specification을 조합할 수 있다.&lt;/li&gt;
&lt;li&gt;Specification 인터페이스를 구현해서 명세를 정의할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 사용자 정의 리포지토리 구현&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 데이터 JPA는 공통기능을 모두 구현할 필요 없이 메소드를 직접 구현할 수 있도록 해준다.&lt;/li&gt;
&lt;li&gt;방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직접 구현 할 메소드를 위한 사용자 정의 인터페이스 작성&lt;/li&gt;
&lt;li&gt;사용자 정의 인터페이스를 구현한 클래스 작성 (인터페이스 이름 + Impl로 이름을 짓는다.)&lt;/li&gt;
&lt;li&gt;레포지토리 인터페이스에서 사용자 정의 인터페이스를 상속&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1664126717099&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
public interface MemberRepositoryCustom {
    public List&amp;lt;Member&amp;gt; findMemberCustom();
}
// 2
public class MemberRepositoryImpl implements MemberRepositoryCustom {
    @Override
    public List&amp;lt;Member&amp;gt; findMemberCustom(){
        // 사용자 구현
    }
}
// 3
public interface MemberRepository extends JpaRepository&amp;lt;Member, Long&amp;gt;, MemberRepositoryCustom{
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. web확장&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@EnableSpringDataWebSupport로 Web확장 기능 활성화 가능 (스프링 MVC에서 사용할 수 있는 기능 제공)&lt;/li&gt;
&lt;li&gt;기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 클래스 컨버터 기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 파라미터로 넘어온 엔티티의 아이디로 객체를 찾아서 바인딩 해준다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;페이징과 정렬 기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PageableHandlerMethodArgumentResolver -&amp;gt; 페이징 기능&lt;/li&gt;
&lt;li&gt;SortHandlerMethodArgumentResolver -&amp;gt; 정렬 기능&lt;/li&gt;
&lt;li&gt;접두사
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이징 정보가 둘 이상일 때 @Qualifier 사용해서 구분할 수 있다 ( &quot;{접두사명}_&quot; )&lt;/li&gt;
&lt;li&gt;@Qualifier(&quot;member&quot;) pageable memberPageable&lt;/li&gt;
&lt;li&gt;@Qualifier(&quot;order&quot;) pageable orderPageable&lt;/li&gt;
&lt;li&gt;/members?member_page=0&amp;amp;order_page=1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기본값
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;page = 0, size= 20이 기본 값&lt;/li&gt;
&lt;li&gt;@PageableDefault 사용해서 변경 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. 스프링 데이터 JPA가 사용하는 구현체&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SimpleJpaRepository class로 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;9. JPA 샵에 적용&lt;/b&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;10. 스프링 데이터 JPA와 QueryDSL 통합&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;QueryDSL&lt;/b&gt; - SQL, JPQL을 코드로 작성할 수 있도록 도와주는 빌더 API&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;QueryDslPredicateExecutor 상속 사용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 데이터 JPA에서 편리하게 QueryDSL을 사용할 수 있게 해주지만 기능적 한계가 있다.&lt;/li&gt;
&lt;li&gt;다양한 기능을 사용하기 위해선 JPAQuery를 직접 사용하거나 스프링 데이터 JPA가 제공하는 QueryDslRepositorySupport를 사용해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;QueryDslRepositorySupport 상속 사용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색 조건에 따라 &lt;b&gt;동적으로 쿼리를 생성&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스터디/자바 ORM표준 JPA 프로그래밍</category>
      <author>knowable</author>
      <guid isPermaLink="true">https://knowable.tistory.com/107</guid>
      <comments>https://knowable.tistory.com/107#entry107comment</comments>
      <pubDate>Mon, 26 Sep 2022 02:53:54 +0900</pubDate>
    </item>
    <item>
      <title>[JPA]10. 객체지향 쿼리 언어</title>
      <link>https://knowable.tistory.com/106</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 객체지향 쿼리 소개&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블이 아닌 객체를 대상으로 실행&lt;/li&gt;
&lt;li&gt;JPA가 지원하는 기능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPQL
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 객체를 조회하는 객체지향 쿼리&lt;/li&gt;
&lt;li&gt;문법은 SQL과 비슷하나 SQL보다 간결하다&lt;/li&gt;
&lt;li&gt;SQL을 추상화 해서 특정 데이터베이스 SQL에 의존하지 않는다.&lt;/li&gt;
&lt;li&gt;JPQL은 결국 SQL로 변환된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Criteria 쿼리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Criteria는 JPQL을 생성하는 빌더 클래스&lt;/li&gt;
&lt;li&gt;프로그래밍 코드로 JPQL을 작성할 수 있다.&lt;/li&gt;
&lt;li&gt;컴파일 시점에 오류를 발견할 수 있다.&lt;/li&gt;
&lt;li&gt;IDE를 사용하면 코드 자동완성을 지원한다.&lt;/li&gt;
&lt;li&gt;동적 쿼리를 작성하기 편하다.&lt;/li&gt;
&lt;li&gt;복잡하고 장황하여 불편하고, 코드가 한 눈에 들어오지 않는다는 단점이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;네이티브 SQL
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL을 직접 사용할 수 있는 기능&lt;/li&gt;
&lt;li&gt;특정 데이터베이스만 사용하는 기능이나 힌트들을 사용할 때 사용.&lt;/li&gt;
&lt;li&gt;데이터베이스를 변경하면 네이티브 SQL도 수정해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;QueryDSL
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPQL빌더 역할을 한다.&lt;/li&gt;
&lt;li&gt;코드 기반이면서 단순하고 사용하기 쉽다.&lt;/li&gt;
&lt;li&gt;어노테이션 프로세서를 사용해서 쿼리 전용 클래스를 만들어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JDBC 직접 사용 (MyBatis같은 SQL매퍼 프레임워크 사용)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA는 JDBC커넥션을 획득하는 API를 제공하지 않기에 JPA 구현체가 제공하는 방법을 사용해야 한다.&lt;/li&gt;
&lt;li&gt;JDBC나 마이바티스를 JPA와 함께 사용하면 영속성 컨텍스트를 적절한 시점에 강제로 플러시 해야한다. (JDBC나 마이바티스 사용 시 DB에 우회해서 접근하게 되는데, 이는 JPA가 인식할 수 없으므로 플러시를 통해 데이터베이스와 영속성 컨텍스트를 동기화)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. JPQL&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.1 기본 문법과 쿼리 API&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT, UPDATE, DELETE문을 사용할 수 있고, 엔티티 저장 시 EntityManager.persist()메소드를 사용하므로 INSERT문은 없다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티와 속성은 대소문자를 구분(SELECT, FROM같은 JPQL키워드는 구분 x)&lt;/li&gt;
&lt;li&gt;클래스 명은 엔티티 명으로 사용하는 것을 추천&lt;/li&gt;
&lt;li&gt;별칭을 필수로 사용해야 한다 (Member AS m 아니면 Member m)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TypeQuery, Query
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작성한 JPQL을 실행하기 위한 쿼리 객체&lt;/li&gt;
&lt;li&gt;반환 타입이 명확하다면 TypeQuery객체&lt;/li&gt;
&lt;li&gt;명확하지 않다면 Query 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결과 조회
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메소드 호출로 쿼리를 실행해서 결과를 조회할 수 있다.&lt;/li&gt;
&lt;li&gt;query.getResultList() : 결과를 예제로 반환. 없으면 빈 컬렉션 반환&lt;/li&gt;
&lt;li&gt;query.getSingleResult() : 결과가 정확히 하나일 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.2 파라미터 바인딩&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름 기준 파라미터
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파라미터를 이름으로 구분&lt;/li&gt;
&lt;li&gt;em.createQuery(&quot;SELECT m FROM Member m where m.username = :username&quot;, Member.class) 파라미터 정의&lt;/li&gt;
&lt;li&gt;query.setParameter(&quot;username&quot;, usernameParam) 으로 파라미터 바인딩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위치 기준 파라미터
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;em.createQuery(&quot;SELECT m FROM Member m where m.username = ?1&quot;, Member.class).setParameter(1, usernameParam)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위치 기준보단 이름 기준을 사용하는 것이 더 명확&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.3 프로젝션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT절에 조회할 대상을 지정하는 것 (SELECT [프로젝션 대상] FROM)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 프로젝션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT m FROM Member m 같이 원하는 객체를 바로 조회&lt;/li&gt;
&lt;li&gt;컬럼을 나열해서 조회하지 않아도 된다.&lt;/li&gt;
&lt;li&gt;조회한 엔티티는 영속성 컨텍스트에서 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;임베디드 타입 프로젝션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티와 거의 비슷하게 사용된다.&lt;/li&gt;
&lt;li&gt;조회의 시작이 될 수 없기에 엔티티를 우선 조회 후 임베디드 타입을 조회할 수 있다.&lt;/li&gt;
&lt;li&gt;Order가 엔티티 Address(city, street, zipcode)가 임베디드 타입이라면 order.city 이런식으로 조회해야&lt;/li&gt;
&lt;li&gt;직접 조회한 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스칼라 타입 프로젝션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;숫자, 문자, 날짜와 같은 기본 데이터 타입들&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;여러 값 조회
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필요한 데이터들만 선택해서 조회할 때 Query를 사용해서 가능하다.&lt;/li&gt;
&lt;li&gt;em.createQuery(&quot;SELECT m.username, m.age FROM Member m&quot;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NEW 명령어
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반환받을 클래스를 지정해서 생성자에 JPQL 조회 결과를 넘겨줄 수 있다. (객체 변환 작업을 줄일 수 있다.)&lt;/li&gt;
&lt;li&gt;em.createQuery(&quot;SELECT new jpabook.jpql.UserDTO(m.username, m.age) &lt;br /&gt;FROM Member m&quot;, UserDTO.class)&lt;/li&gt;
&lt;li&gt;주의사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패키지 명을 포함한 전체 클래스 명을 입력해야 한다.&lt;/li&gt;
&lt;li&gt;순서와 타입이 일치하는 생성자가 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.4 페이징 API&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이징을 두 API로 추상화해서, 데이터베이스마다 다른 페이징 처리를 같은 API들로 처리할 수 있도록 하였다(Dialect 사용)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setFirstResult(int startPosition): 조회 시작 위치(0부터 시작)&lt;/li&gt;
&lt;li&gt;setMaxResults(int maxResult): 조회할 데이터 수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.5 집합과 정렬&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통계 정보를 구할 때 사용. SQL과 같음&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;집합함수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;COUNT&lt;/li&gt;
&lt;li&gt;MAX, MIN&lt;/li&gt;
&lt;li&gt;AVG&lt;/li&gt;
&lt;li&gt;SUM&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GROUP BY, HAVING&lt;/li&gt;
&lt;li&gt;ORDER BY&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.6 JPQL 조인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL조인과 같은 기능. 문법만 살짝 다르다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인은 스킵&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.8 경로 표현식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;.&quot; 을 찍어서 객체 그래프를 탐색할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 필드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순히 값을 저장하기 위한 필드&lt;/li&gt;
&lt;li&gt;경로 탐색의 끝으로 더는 탐색할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;연관 필드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연관관계를 위한 필드&lt;/li&gt;
&lt;li&gt;단일 값 연관 필드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;묵시적으로 내부 조인이 일어나고 단일 값 연관 경로는 계속 탐색할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컬렉션 값 연관 필드&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;묵시적으로 내부 조인이 일어나고 더는 탐색할 수 없다.&lt;/li&gt;
&lt;li&gt;FROM절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;주의사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;항상 내부 조인이다.&lt;/li&gt;
&lt;li&gt;묵시적 조인이 사용되므로 sql from절에도 영향을 준다. (명시적 조인을 사용하는 것이 좋다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.9 서브쿼리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE, HAVING절에서만 사용할 수 있고 SELECT, FROM절에서는 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.10 조건식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.11 다형성 쿼리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPQL로 부모 엔티티를 조회하면 그 자식 엔티티도 함께 조회된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.12 사용자 정의 함수 호출(JPA 2.1)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 2.1 부턴 사용자 정의 함수를 호출할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방언클래스를 상속해서 구현 후 방언을 등록해두어 사용이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.14 엔티티 직접 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPQL에서 엔티티를 직접 사용하면 SQL로 변환될 때 엔티티의 기본 키를 사용하게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1662344372802&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select count (m.id) from Member m
select count (m) from Member m
// 실행되는 SQL은 두 개가 같다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.15 Named쿼리: 정적 쿼리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 정의한 쿼리에 이름을 부여해서 필요할 때 사용할 수 있게 하는 것이 정적 쿼리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cf) 동적쿼리: em.createQuery(&quot;select ..&quot;)처럼 JPQL을 문자로 완성해서 직접 넘기는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 로딩 시점에 JPQL 문법을 체크하고 미리 파싱한다&lt;/li&gt;
&lt;li&gt;오류를 빨리 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;사용하는 시점에는 파싱된 결과를 재사용하므로 성능상 이점이 있다&lt;/li&gt;
&lt;li&gt;변하지 않는 정적 SQL이 생성되므로 DB조회 성능에 도움이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@NamedQuery 어노테이션을 사용하거나 XML문서에 작성할 수 있고 우선권은 XML이 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Criteria&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.1 Criteria 기초&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 API&lt;/li&gt;
&lt;li&gt;코드로 JPQL을 작성한다&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문법 오류를 컴파일 단계에서 잡을 수 있다&lt;/li&gt;
&lt;li&gt;문자 기반의 JPQL보다 동적 쿼리를 안전하게 생성할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드가 복잡하고 장황해서 직관적으로 이해가 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1662345942820&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CriteriaBuilder cb = em.CriteriaBuilder(); // 빌더 생성

CriteriaQuery&amp;lt;Member&amp;gt; cg = cb.createQuery(Member.class); // 생성, 반환 타입 지정

Root&amp;lt;Member&amp;gt; m = cq.from(Member.class); // from절 생성, 조회의 시작으로 Root
cq.select(m); // select절 생성

TypedQuery&amp;lt;Member&amp;gt; query = em.createQuery(cq);
List&amp;lt;Member&amp;gt; members = query.getResultList();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.2 Criteria 쿼리 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CriteriaBuilder.createQuery() 메서드를 통해 쿼리 생성 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.3 조회&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1662347816028&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 한 건 조회
cq.select(m);
// 여러 건 조회
cq.multiselect(m.get(&quot;username&quot;), m.get(&quot;age&quot;));
cq.select( cb.array(m.get(&quot;username&quot;), m.get(&quot;age&quot;)) );
// distinct
cq.multiselect(m.get(&quot;username&quot;), m.get(&quot;age&quot;)).distinct(true);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.4 집합&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1662347957591&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cq.multiselect(m.get(&quot;team&quot;).get(&quot;name&quot;), maxAge, minAge)
.groupBy(m.get(&quot;team&quot;).get(&quot;name&quot;)) // groupby
.having(cb.gt(minAge, 10); // having&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.5 정렬&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1662348015504&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cq.select(m).where(ageGt).orderBy(cb.desc(m.get(&quot;age&quot;)));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.6 조인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.7 서브쿼리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 쿼리와 서브 쿼리가 관련이 있다면 메인 쿼리에서 사용한 별칭을 얻어서 쿼리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Root&amp;lt;Member&amp;gt; subM = subQuery.correlate(m) 이런 식으로 메인 쿼리의 별칭을 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.8 IN&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;in 사용 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.9 CASE&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;selectCase(), whne(), otherwise() 사용 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.10 파라미터 정의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터 정의 후 바인딩 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cb.parameter(타입, 파라미터 이름) -&amp;gt; setParameter(파라미터 이름, 파라미터)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.11 네이티브 함수 호출&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cb.function()메소드 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.12 동적 쿼리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다양한 검색 조건에 따라 실행 시점에 쿼리를 생성하는 것&lt;/li&gt;
&lt;li&gt;문자 기반인 JPQL보다 코드 기반인 Criteria로 작성하는 것이 더 편리(공백이나 where, and의 위치로 인해 에러발생이 없어서)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.13 함수 정리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p.424&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.14 Criteria 메타 모델 API&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;m.get(&quot;age&quot;)대신 m.get(Member_.age)처럼 문자 기반에서 정적 코드 기반으로 변경할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이버네이트의 코드 생성기를 통해 메타 모델 클래스를 자동으로 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. QueryDSL&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리를 문자가 아닌 코드로 작성해도 쉽고 간결하며 그 모양도 쿼리와 비슷하게 개발 할 수 있도록&lt;/li&gt;
&lt;li&gt;JPA, JDO, JDBC, Lucene, Hibernate Search, 몽고DB, 자바 컬렉션 등 다양하게 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1662351518260&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;JPAQuery query = new JPAQuery(em);
QItem item = QItem.item;
List&amp;lt;item&amp;gt; list = query.from(item).where(item.name.eq(&quot;좋은상품&quot;).and ..)
    .list(item); // 조회 할 프로젝션 지정

// 결과 호출 시
uniqueResult()
singleResult()
list()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;페이징, 정렬, 그룹, 조인, 서브쿼리 모두 사용 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QueryDSL은 코드로 쿼리를 작성할 수 있으면서 동적쿼리가 복잡하지 않기에 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 네이티브 SQL&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPQL을 사용할 수 없을 때 SQL을 직접 사용하도록 하는 기능&lt;/li&gt;
&lt;li&gt;엔티티를 조회할 수 있고 JPA가 지원하는 영속성 컨텍스트의 기능을 그대로 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;네이티브 SQL로 SQL만 직접 사용할 뿐 나머지는 JPQL을 사용할 때와 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1662351999701&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String sql = &quot;SELECT ID, AGE&quot; + 
    &quot;FROM MEMBER WHERE AGE &amp;gt; ?&quot;;

Query nativeQuery = em.createNativeQuery(sql, Member.class).setParameter(1,20);

List&amp;lt;Member&amp;gt; resultList = nativeQuery.getResultList();

// 결과 타입정의 없이 단순한 값으로 조회 시
createNativeQuery(String sqlString)
// 엔티티와 스칼라를 함께 조회하는 등, 매핑이 복잡해졌을 때
createNativeQuery(String sqlString, String resultSetMapping)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과 매핑 애노테이션 정리 - p.449&lt;/li&gt;
&lt;li&gt;JPQL처럼 Named 네이티브 SQL을 사용해서 정적 SQL을 작성할 수 있다.&lt;/li&gt;
&lt;li&gt;Query, TypeQuery를 반환한다.&lt;/li&gt;
&lt;li&gt;관리하기가 쉽지 않고, 특정 데이터베이스에 종속적인 쿼리가 증가해 이식성이 떨어지는 단점이 있지만 사용을 안할 수 없어, JPQL -&amp;gt; JPA 구현체 기능 -&amp;gt; 네이티브 SQL -&amp;gt; MyBatis, JdbcTemplate등을 사용하자&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 객체지향 쿼리 심화&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;벌크연산
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 건을 한 번에 수정하거나 삭제할 수 있다&lt;/li&gt;
&lt;li&gt;executeUpdate()메소드를 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;영속성 컨텍스트를 무시하고 DB에 직접 쿼리하므로 영속성 컨텍스트와 DB간 데이터 차이에 주의해야 한다.&lt;/li&gt;
&lt;li&gt;가능하면 벌크 연산을 가장 먼저 수행하고 영속성 컨텍스트를 초기화 하는 것도 필요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;영속성 컨텍스트와 JPQL
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영속성 컨텍스트에 엔티티가 있으면 DB에서 조회한 결과를 버리고 영속성 컨텍스트에 있던 엔티티를 반환한다.&lt;/li&gt;
&lt;li&gt;영속성 컨텍스트는 영속 상태인 엔티티의 동일성을 보장하기에 기존 엔티티를 그대로 두고, 새로 검색한 엔티티를 버린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JPQL과 플러시 모드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPQL은 영속성 컨텍스트에 있는 데이터를 고려하지 않고 DB에서 데이터를 조회하므로 JPQL실행 전에 영속성 컨텍스트의 내용을 데이터베이스에 반영해야 한다.&lt;/li&gt;
&lt;li&gt;일반적으로 플러시 모드의 기본값이 AUTO라 신경 쓸 것이 없지만 COMMIT모드 사용 시에는 em.flush()로 수동으로 처리하거나 setFlushMode()로 특정 쿼리만 플러시 모드를 AUTO로 해주면 된다.&lt;/li&gt;
&lt;li&gt;COMMIT모드(트랜젝션 커밋 시에만 플러시. 쿼리 실행때는 플러시 하지 않는다) 를 사용하는 이유는 플러시 횟수를 줄여 성능을 최적화 하기 위함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스터디/자바 ORM표준 JPA 프로그래밍</category>
      <author>knowable</author>
      <guid isPermaLink="true">https://knowable.tistory.com/106</guid>
      <comments>https://knowable.tistory.com/106#entry106comment</comments>
      <pubDate>Mon, 5 Sep 2022 11:45:53 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 08. 프록시와 연관관계 관리</title>
      <link>https://knowable.tistory.com/105</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 프록시&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연로딩 : 엔티티를 조회할 때 엔티티를 항상 사용하진 않기에, 엔티티가 실제 사용될 때 까지 데이터베이스 조회를 지연하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 지연 로딩을 위해 조회를 지연할 수 있는 가짜 객체가 필요. 이것이 프록시.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.1 프록시 기초&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시의 특징
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시 클래스는 실제 클래스를 상속받아 만들어진다.&lt;/li&gt;
&lt;li&gt;사용자 입장은 프록시 객체와 진짜 객체를 구분하지않고 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;처음 사용할 떄 한 번만 초기화 된다. (실제 엔티티로 바뀌는 것이 아니라 프록시를 통해 접근할 수 있게 되는 것)&lt;/li&gt;
&lt;li&gt;원본 엔티티를 상속받기에 타입 체크 시에 주의해서 사용해야 한다.&lt;/li&gt;
&lt;li&gt;영속성 컨텍스트에 찾는 엔티티가 이미 있으면 DB접근이 필요 없기에 실제 엔티티를 반환한다.&lt;/li&gt;
&lt;li&gt;준영속 상테의 프록시를 초기화하면 문제가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프록시 객체의 초기화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시객체가 실제 사용되어 실제 엔티티 객체를 생성하는 것이 프록시 객체의 초기화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.2 프록시와 식별자&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티를 프록시로 조회 시 PK값을 파라미터로 전달. 프록시 객체는 이 식별자 값을 보관한다.&lt;/li&gt;
&lt;li&gt;결국 식별자를 조회 시에는 DB접근을 안하기에 프록시를 초기화 하지 않는다.(엔티티 접근 방식을 프로퍼티로 설정한 경우)&lt;/li&gt;
&lt;li&gt;엔티티 접근 방식이 필드면 프록시 객체를 초기화한다.&lt;/li&gt;
&lt;li&gt;연관관계 설정 시 프록시를 사용하면 DB 접근 횟수를 줄일 수 있기에 이익을 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.3 프록시 확인&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PersistenceUnitUtil.isLoaded(Object entity)메소드를 통해 프록시 인스턴스의 초기화 여부를 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;초기화가 되어있거나 프록시 인스턴스가 아니면 true를 반환&lt;/li&gt;
&lt;li&gt;조회한 엔티티가 진짜인지, 프록시로 조회인지 확인을 위해선 클래스 명을 출력해보면 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 즉시 로딩과 지연 로딩&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.1 즉시 로딩&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.&lt;/li&gt;
&lt;li&gt;@ManyToOne(fetch = FetchType.EAGER)로 설정&lt;/li&gt;
&lt;li&gt;ex) member를 호출했다면 그에 연관된 team도 함께 조회&lt;/li&gt;
&lt;li&gt;조회 시 쿼리를 두 번 수행하지 않고, 가능하면 조인 쿼리를 사용(즉시 로딩을 최적화하기 위해)&lt;/li&gt;
&lt;li&gt;비즈니스 로직에서 항상 같이 조회하는 경우에 즉시로딩을 쓰면 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2 지연 로딩&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연관된 엔티티를 실제 사용할 때 조회한다.&lt;/li&gt;
&lt;li&gt;@ManyToOne(fetch=FetchType.LAZY)로 설정&lt;/li&gt;
&lt;li&gt;ex) member호출 시 연관된 team은 조회하지 않고 team멤버변수에 프록시 객체를 넣어둔다.&lt;/li&gt;
&lt;li&gt;실제 사용이 될 떄 프록시 객체가 데이터 로딩을 수행(초기화)&lt;/li&gt;
&lt;li&gt;필요할 떄 마다 실행할 때가 효율적이 때도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 지연 로딩 활용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예사 상황&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원은 팀 하나에만 소속 (N:1) / 회원, 팀이 자주 함께 사용 (즉시로딩)&lt;/li&gt;
&lt;li&gt;회원은 여러 주문내역을 가진다 (1:N) / 회원, 주문 내역은 가끔 함께 사용 (지연로딩)&lt;/li&gt;
&lt;li&gt;주문내역은 상품정보를 가진다 (N:1) / 자주 함께 사용 (즉시로딩)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1661099442921&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Member{
    @Id
    private String id;
    private String username;
    private Integer age;
    
    @ManyToOne(fetch=FetchType.EAGER)
    private Team team;
    
    @OneToMany(mappedBy=&quot;member&quot;, fetch=FetchType.LAZY)
    private List&amp;lt;Order&amp;gt; orders;
    
    //getter, setter...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1 프록시와 컬렉션 래퍼&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하이버네이트가 엔티티를 영속 상태로 만들 때&lt;/li&gt;
&lt;li&gt;엔티티에 컬렉션이 있다면 원본 컬렉션을 하이버네이트가 제공하는 내장 컬렉션으로 변경하는 것이 컬렉션 래퍼&lt;/li&gt;
&lt;li&gt;(컬렉션을 추적하고 관리 할 목적)&lt;/li&gt;
&lt;li&gt;컬렉션 래퍼도 컬렉션에 대한 프록시 역할을 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.2 JPA 기본 fetch 전략&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연관된 엔티티가 하나라면 즉시 로딩&lt;/li&gt;
&lt;li&gt;컬렉션이면 지연 로딩(컬렉션을 로딩하는 것에는 비용이 많이 들고, 많은 데이터가 로딩될 수 있기에)&lt;/li&gt;
&lt;li&gt;항상 지연 로딩을 기본으로 하고 필요할 때만 즉시 로딩을 한다고 생각하면 된다.&lt;/li&gt;
&lt;li&gt;컬렉션을 즉시로딩 한다면 항상 OUTER JOIN이 사용된다. (ex. 회원이 한 명도 없는 팀을 내부조인하면 팀까지 조회가 안되는 문제가 발생하기에)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 영속성 전이: CASCADE&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 영속 상태로 만들고 싶다면 영속성 전이 기능을 사용&lt;/li&gt;
&lt;li&gt;이때 CASCADE를 사용&lt;/li&gt;
&lt;li&gt;+) JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 한다.&lt;/li&gt;
&lt;li&gt;연관관계 매핑과는 관련이 없다. (연관된 엔티티도 같이 영속화하는 편리함을 제공할 뿐)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.1 저장&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1661101145776&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Parent{
    @OneToMany(mappedBy=&quot;parent&quot;, cascade=CascadeType.PERSIST) // 옵션 설정
    private List&amp;lt;Child&amp;gt; children = new ArrayList&amp;lt;Child&amp;gt;();
    ...
}
/////////////////
private static void example(EntityManager em){
    Child child1 = new Child();
    Child child2 = new Child();
    
    Parent parent = new Parent();
    child1.setParent(parent); // 연관관계 추가
    child2.setParent(parent);
    
    parent.getChildren().add(child1);
    parent.getChildren().add(child2);
    
    // 부모, 연관된 자식들 저장
    em.persist(parent)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.2 삭제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존이라면 각각의 엔티티를 하나씩 제거애햐 하지만 CascadeType.REMOVE로 설정이 되어있다면 연관된 엔티티도 함께 삭제가 된다&lt;/p&gt;
&lt;pre id=&quot;code_1661101331406&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기존
Parent findParent = em.find(Parent.class, 1L);
Child findChild1 = em.find(Child.class, 1L);
Child findChild2 = em.find(Child.class, 2L);

em.remove(findChild1);
em.remove(findChild2);
em.remove(findParent);

// CascadeType.REMOVE사용 시
Parent findParent = em.find(Parent.class, 1L);
em.remove(findParent);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.3 CASCADE의 종류&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1661101386852&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum CascadeType{
    ALL,
    PERSIST,
    MERGE,
    REMOVE,
    REFRESH,
    DETACH
}
// PERSIST, REMOVE는 바로 전이가 발생되지 않고 플러시를 호출할 때 전이가 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 고아 객체&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제할 수 있게 한다. (고아객체(ORPHAN) 제거)&lt;/li&gt;
&lt;li&gt;자식엔티티의 참조만 제거하면 자동으로 삭제되도록 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1661101670238&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Parent {
    @Id @GeneratedValue
    private Long id;
    
    @OneToMany(mappedBy = &quot;parent&quot;, orphanRemoval = true)
    private List&amp;lt;Child&amp;gt; children = new ArrayList&amp;lt;Child&amp;gt;();
    ...
}
/////////////
Parent parent1 = em.find(Parent.class. id);
parent1.getChildren().remove(0); // 자식 엔티티를 컬렉션에서 제거
////////// 모든 자식 엔티티를 제거하는 방법
parent1.getChildren().clear();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참조하는 곳이 하나일 때만 사용해야 한다.(특정 엔티티가 개인 소유하는 엔티티에만 이 기능을 적용해야 한다.)&lt;/li&gt;
&lt;li&gt;@OneToOne, @OneToMany에서만 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;부모를 제거하면 자식은 고아가 되고, 자식 또한 제거되기에 CascadeType.REMOVE를 설정한 것과 같이 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 영속성 전이 + 고아 객체, 생명 주기&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CascadeType.ALL + orpahnRemoval = ture 동시 사용 시&lt;/li&gt;
&lt;li&gt;부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있다.&lt;/li&gt;
&lt;li&gt;자식을 저장하려면 부모에 등록만 하면 되고, 자식을 삭제하려면 부모에서 제거만 하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스터디/자바 ORM표준 JPA 프로그래밍</category>
      <author>knowable</author>
      <guid isPermaLink="true">https://knowable.tistory.com/105</guid>
      <comments>https://knowable.tistory.com/105#entry105comment</comments>
      <pubDate>Mon, 22 Aug 2022 02:12:57 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 07. 고급 매핑</title>
      <link>https://knowable.tistory.com/104</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 상속 관계 매핑&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관계형 DB에선 상속이란 개념이 없고 '슈퍼타입 서브타입 관계' 라는 모델링 기법이 상속 개념과 가장 유사하다.&lt;/li&gt;
&lt;li&gt;슈퍼타입 서브타입 논리 모델을 실제 물리 모델인 테이블로 구현하는 방법은 3가지
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;각각의 테이블로 변환(조인전략)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각각을 모두 테이블로 만들고 조회할 때 조인을 사용&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블이 정규화 된다&lt;/li&gt;
&lt;li&gt;외래 키 참조 무결성 제약조건을 활용할 수 있다.&lt;/li&gt;
&lt;li&gt;저장공간을 효율적으로 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있다.&lt;/li&gt;
&lt;li&gt;조회 쿼리가 복잡하다.&lt;/li&gt;
&lt;li&gt;데이터를 등록할 INSERT SQL을 두 번 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특징
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA 표준 명세는 구분 컬럼을 사용하도록 하지만 하이버네이트를 포함한 몇몇 구현체는 구분 컬럼(@DiscriminatorColumn)없이도 동작한다.&lt;/li&gt;
&lt;li&gt;* 구분컬럼 : 이 컬럼으로 저장된 자식 테이블을 구분할 수 있다.(DTYPE)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;통합 테이블로 변환(단일 테이블 전략)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블을 하나만 사용해서 통합&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조인이 필요 없으므로 일반적으로 조회 성능이 빠르다&lt;/li&gt;
&lt;li&gt;조회 쿼리가 단순하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.&lt;/li&gt;
&lt;li&gt;단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. (상황에 따라 조회 성능이 오히려 느려질 수 있다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특징
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구분 컬럼을 꼭 사용해야 한다 (@DiscriminatorColumn을 꼭 설정해야 한다. 지정하지 않으면 기본으로 엔티티 이름을 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서브타입 테이블로 변환&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브 타입마다 하나의 테이블을 만든다.&lt;/li&gt;
&lt;li&gt;자식 테이블 각각에 필요한 컬럼이 모두 있다.&lt;/li&gt;
&lt;li&gt;일반적으로 추천하진 않는다.&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브 타입을 구분해서 처리할 때 효과적이다&lt;/li&gt;
&lt;li&gt;not null 제약조건을 사용할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 자식 테이블을 함께 조회할 때 성능이 느리다&lt;/li&gt;
&lt;li&gt;자식 테이블을 통합해서 쿼리하기 어렵다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특징
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구분 컬럼을 사용하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x411t/btrI2YeKyZk/q5hGkV1cuNFyhoaiDRRCg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x411t/btrI2YeKyZk/q5hGkV1cuNFyhoaiDRRCg0/img.png&quot; data-alt=&quot;조인전략&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x411t/btrI2YeKyZk/q5hGkV1cuNFyhoaiDRRCg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx411t%2FbtrI2YeKyZk%2Fq5hGkV1cuNFyhoaiDRRCg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;297&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;297&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;조인전략&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxRBPV/btrJck78tQh/L9MQ6J9BX8fDfGYQwULFMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxRBPV/btrJck78tQh/L9MQ6J9BX8fDfGYQwULFMK/img.png&quot; data-alt=&quot;단일 테이블 전략&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxRBPV/btrJck78tQh/L9MQ6J9BX8fDfGYQwULFMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxRBPV%2FbtrJck78tQh%2FL9MQ6J9BX8fDfGYQwULFMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;726&quot; height=&quot;288&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;단일 테이블 전략&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1wQ86/btrIZ6D57vV/ry6cLektkNZQ9sDEgk1wKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1wQ86/btrIZ6D57vV/ry6cLektkNZQ9sDEgk1wKK/img.png&quot; data-alt=&quot;서브타입 테이블&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1wQ86/btrIZ6D57vV/ry6cLektkNZQ9sDEgk1wKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1wQ86%2FbtrIZ6D57vV%2Fry6cLektkNZQ9sDEgk1wKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;736&quot; height=&quot;237&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서브타입 테이블&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. @MappedSuperclass&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 과정들은 부모 클래스와 자식 클래스를 모두 DB 테이블과 매핑함&lt;/li&gt;
&lt;li&gt;매핑하지 않고 자식클래스에게 매핑 정보만 제공하고싶을 때 @MappedSuperclass 사용하여 실제 테이블과 매핑되지 않고 매핑 정보만 상속할 수 있도록&lt;/li&gt;
&lt;li&gt;이 클래스를 직접 생성해서 사용할 일은 거의 없다 (추상 클래스로 만드는 것을 권장)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X3MRi/btrJcF5zqfu/MtT5YFH3Kj52rM9ST3NyjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X3MRi/btrJcF5zqfu/MtT5YFH3Kj52rM9ST3NyjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X3MRi/btrJcF5zqfu/MtT5YFH3Kj52rM9ST3NyjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX3MRi%2FbtrJcF5zqfu%2FMtT5YFH3Kj52rM9ST3NyjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;611&quot; height=&quot;235&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1659886087444&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@MappedSuperclass
public abstract class BaseEntity{ // 테이블과 매핑할 필요X, 공통사용 정보만 제공
    @Id @GeneratedValue
    private Long id;
    private String name;
    ...
}

@Entity
public class Member extends BaseEntity {
    // ID 상속
    // NAME 상속 
    private String email;
    ...
}

@Entity
public class Seller extends BaseEntity {
    // ID 상속
    // NAME 상속
    private String shopName;
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 복합 키와 식별 관계 매핑&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.1 식별 관계 v.s 비식별 관계&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;식별 관계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 테이블의 기본 키를 내려받아서 자식 태이블의 기본 키 + 외래 키로 사용하는 관계 (자식의 기본+외래키로 사용되는 것이 부모의 기본키)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비식별 관계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키로만 사용하는 관계&lt;/li&gt;
&lt;li&gt;필수적 비식별 관계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외래 키에 NULL을 허용하지 않는다(연관관계를 필수적으로 맺어야 한다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;선택적 비식별 관계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외래 키에 NULL을 허용한다. (연관관계를 맺을지 말지 선택할 수 있다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비식별 관계를 주로 사용하고, 필요한 곳에서만 식별 관계를 사용하는 추세.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.2 복합 키: 비식별 관계 매핑&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YBIZy/btrJcGi7Hu3/NShPMJrG1KHYk5oFRP1kS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YBIZy/btrJcGi7Hu3/NShPMJrG1KHYk5oFRP1kS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YBIZy/btrJcGi7Hu3/NShPMJrG1KHYk5oFRP1kS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYBIZy%2FbtrJcGi7Hu3%2FNShPMJrG1KHYk5oFRP1kS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;775&quot; height=&quot;199&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;199&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@IdClass (관계형 데이터베이스에 가까운 방법)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1659887922027&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@IdClass(ParentId.class)
public class Parent {
    @Id
    @Column ( name = &quot;PARENT_ID1&quot;)
    private String id1;
    
    @Id
    @Column(name = &quot;PARENT_ID2&quot;)
    private String id2;
    
    private String name;
}

///////////////
public class ParentId implements Serializable {
    private String id1;
    private String id2;

    public ParentId(){
    }
    
    public Parentid(String id1, String id2){
        ....
    }
    
    // equals(), hashCode()
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야한다.&lt;/li&gt;
&lt;li&gt;Seriablizable 인터페이스를 구현해야 한다&lt;/li&gt;
&lt;li&gt;equals, hashCode를 구현해야 한다.&lt;/li&gt;
&lt;li&gt;기본 생성자가 있어야 한다&lt;/li&gt;
&lt;li&gt;식별자 클래스는 public이어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@EmbeddedId(객체 지향에 가까운 방법)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1659888375394&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Parent {
    @EmbeddedId
    private ParentId id;
    
    private String name;
}

// 식별자 클래스

@Embeddable
public class ParrentId implements Serializable{
    @Column ( name = &quot;PARENT_ID1&quot;)
    private String id1;
    
    @Column(name = &quot;PARENT_ID2&quot;)
    private String id2;
    
    // equals(), hashCode()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Embeddable 어노테이션을 붙여야 한다.&lt;/li&gt;
&lt;li&gt;Serializable 인터페이스를 구현해야 한다&lt;/li&gt;
&lt;li&gt;equals, hashCode를 구현해야 한다.&lt;/li&gt;
&lt;li&gt;기본 생성자가 있어야 한다.&lt;/li&gt;
&lt;li&gt;식별자 클래스는 public이어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EmbeddedId가 @IdClass와 비교해서 더 객체지향적이고 중복도 없어서 좋아보이지만 특정 상황에 JPQL이 조금 더 길어질 수도 있다. (취향에 맞는 것을 일관성 있게 사용하면 된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.3 복합 키: 식별 관계 매핑&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nKMFL/btrI2qaKd9b/hb9GMo6lX7It8nkEL1VwQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nKMFL/btrI2qaKd9b/hb9GMo6lX7It8nkEL1VwQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nKMFL/btrI2qaKd9b/hb9GMo6lX7It8nkEL1VwQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnKMFL%2FbtrI2qaKd9b%2Fhb9GMo6lX7It8nkEL1VwQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;716&quot; height=&quot;185&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;205&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 테이블의 기본 키를 포함해서 복합 키를 구성해야해서 @IdClass나 @EmbeddedId를 사용해서 식별자를 매핑해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@IdClass&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1659889074149&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 부모
@Entity
public class Parent {
    @Id @Column (name = &quot;PARENT_ID&quot;)
    private String id
    private String name;
    ...
}
// 자식
@Entity
@IdClas(ChildId.class)
public class Child {
    @Id 
    @ManyToOne
    @JoinColumn (name = &quot;PARENT_ID&quot;)
    public Parent parent;
    
    @Id @Column(name = &quot;CHILD_ID&quot;)
    private String childId;
    
    private String name;
    ...
}
// 자식 ID
public class ChildId implements Serializable {
    
    private String Parent;
    private String childId;
    
    // equlas(), hashCode()
    ...
}
//손자
@Entity
@IdClas(GrandChildId.class)
public class GrandChild {
    @Id 
    @ManyToOne
    @JoinColumns({
        @JoinColumn (name = &quot;PARENT_ID&quot;),
        @JoinColumn (name = &quot;CHILD_ID&quot;)
    })
    private Child child;
    
    @Id @Column(name = &quot;GRANDCHILD_ID&quot;)
    private String id;
    
    private String name;
    ...
}
// 손자 ID
public class GrandChildId implements Serializable {
    
    private ChildId childId;
    private String id;
    
    // equlas(), hashCode()
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@EmbeddedId&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1659889390861&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 부모
@Entity
public class Parent {
    @Id @Column (name = &quot;PARENT_ID&quot;)
    private String id
    private String name;
    ...
}
// 자식
@Entity
public class Child {

    @EmbeddidId
    private ChildId id;
    
    @MapsId(&quot;parentId&quot;)
    @ManyToOne
    @JoinColumn (name = &quot;PARENT_ID&quot;)
    public Parent parent;
    
    private String name;
    ...
}
// 자식 ID
@Embeddable
public class ChildId implements Serializable {
    
    private String ParentId; // @MapsId(&quot;parentId&quot;)로 매핑
    
    @Column(name = &quot;CHILD_ID&quot;)
    private String id;
    
    // equlas(), hashCode()
    ...
}
//손자
@Entity
public class GrandChild {

    @EmbeddedId
    private GrandChildId id;
    
    @MapsId(&quot;childId&quot;) 
    @ManyToOne
    @JoinColumns({
        @JoinColumn (name = &quot;PARENT_ID&quot;),
        @JoinColumn (name = &quot;CHILD_ID&quot;)
    })
    private Child child;
    
    private String name;
    ...
}
// 손자 ID
@Embeddable
public class GrandChildId implements Serializable {
    
    private ChildId childId;
    
    @Column(name = &quot;GRANDCHILD_ID&quot;)
    private String id;
    
    // equlas(), hashCode()
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.4 비식별 관계로 구현&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pNnBS/btrI1PWr0DP/cDtx7zPmkV8Akq82rkIYe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pNnBS/btrI1PWr0DP/cDtx7zPmkV8Akq82rkIYe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pNnBS/btrI1PWr0DP/cDtx7zPmkV8Akq82rkIYe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpNnBS%2FbtrI1PWr0DP%2FcDtx7zPmkV8Akq82rkIYe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;628&quot; height=&quot;174&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매핑도 쉽고 코드도 간단해진다. (복합 키 클래스 구현 필요 X)&lt;/p&gt;
&lt;pre id=&quot;code_1659890120420&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//부모
@Entity
public class Parent {
    @Id @GeneratedValue
    @Column(name = &quot;PARENT_ID&quot;)
    private Long id;
    private String name;
    ...
}

//자식
@Entity
public class Child {
    @Id @GeneratedValue
    @Column(name = &quot;CHILD_ID&quot;)
    private Long id;
    private String name;
    
    @ManyToOne
    @JoinColumn(name = &quot;PARENT_ID&quot;)
    private Parent parent;
    ...
}
// 손자
@Entity
public class GrandChild{
    @Id @GeneratedValue
    @Column(name = &quot;GRANDCHILD_ID&quot;)
    private Long id;
    private Stirng name;
    
    @ManyToOne
    @JoinColumn (name = &quot;CHILD_ID&quot;)
    private Child child;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.5 일대일 식별 관계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 테이블의 기본 키 값으로 부모 테이블의 기본 키 값을 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mui7k/btrI2ZY4bOm/FgnxziupcRA7cjR0pC4t80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mui7k/btrI2ZY4bOm/FgnxziupcRA7cjR0pC4t80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mui7k/btrI2ZY4bOm/FgnxziupcRA7cjR0pC4t80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmui7k%2FbtrI2ZY4bOm%2FFgnxziupcRA7cjR0pC4t80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;651&quot; height=&quot;149&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1659891323373&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//부모
@Entity
public class Board{
    @Id @GeneratedValue
    @Column(name = &quot;BOARD_ID&quot;)
    private Long id;
    
    private String title;
    
    @OneToOne(mappedBy = &quot;board&quot;)
    private BoartdDetail boardDetail;
    ...
}
// 자식
@Entity
public class BoardDetail {
    @Id
    private Long boardId;
    
    @MapsId
    @OneToOne
    @JoinColumn(name=&quot;BOARD_ID&quot;)
    private Board board;
    
    private String content;
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.6 식별, 비식별 관계의 장단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 설계 관점에선 식별 관계보단 비식별 관계를 선호&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;식별관계는 자식 테이블의 기본 키 컬럼이 점점 늘어나면서, 조인 시 SQL이 복잡해지고 기본 키 인덱스가 불필요하게 커질 수 있다&lt;/li&gt;
&lt;li&gt;식별 관계는 2개 이상의 컬럼을 합해서 복합 기본키를 사용하는 경우가 많다&lt;/li&gt;
&lt;li&gt;요구사항의 변경이 있을 때 식별 관계의 자연키 컬럼들이 손자까지 전파되어있다면 변경하기 쉽지 않다.&lt;/li&gt;
&lt;li&gt;테이블 구조가 유연하지 못하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 관계 매핑 관점에선 비식별 관계를 선호&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA에서 복합 키는 별도의 복합 키 클래스를 만들어 사용하기에 컬럼이 하나인 기본 키를 매핑하는 것보다 많은 노력이 필요하다.&lt;/li&gt;
&lt;li&gt;비식별 관계의 기본 키는 대리키를 주로 사용하는데 JPA는 @GenerateValue처럼 대리키를 생성하기에 편리한 방법을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORM신규 프로젝트를 진행한다면 비식별 관계를 사용하고 기본 키를 Long 타입의 대리 키를 사용하는 것을 추천&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(대리키는 비즈니스와 아무 관련이 없어, 비즈니스가 변경되어도 유연하게 대처가 가능하기 때문에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택적 비식별 관계보단 필수적 비식별 관계를 추천 (NOT NULL을 보장하므로 내부 조인만 사용할 수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 조인 테이블&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스의 연관관계 설계방법.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조인 컬럼 사용(외래 키)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;421&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwZWEB/btrI2wQmtvT/l8ok1pPLBZZ5ogoKVwvdn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwZWEB/btrI2wQmtvT/l8ok1pPLBZZ5ogoKVwvdn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwZWEB/btrI2wQmtvT/l8ok1pPLBZZ5ogoKVwvdn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwZWEB%2FbtrI2wQmtvT%2Fl8ok1pPLBZZ5ogoKVwvdn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;421&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;421&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조인 테이블 사용(테이블)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도의 테이블을 사용해서 연관관계를 관리한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;일대일 조인 테이블&lt;/li&gt;
&lt;li&gt;일대다 조인 테이블&lt;/li&gt;
&lt;li&gt;다대일 조인 테이블&lt;/li&gt;
&lt;li&gt;다대다 조인 테이블&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;일대일 조인 테이블&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;293&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oXeRH/btrI2vYdKKX/VA4kd9if0Cvf7ThYasUhgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oXeRH/btrI2vYdKKX/VA4kd9if0Cvf7ThYasUhgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oXeRH/btrI2vYdKKX/VA4kd9if0Cvf7ThYasUhgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoXeRH%2FbtrI2vYdKKX%2FVA4kd9if0Cvf7ThYasUhgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;636&quot; height=&quot;293&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;293&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일대일 관계를 위해선, 외래 키 컬럼 각각 총 2개의 유니크 제약 조건을 걸어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;일대다 조인 테이블&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/shjmX/btrI2xIsunj/t4sZYqUFn9GduRNvheWte1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/shjmX/btrI2xIsunj/t4sZYqUFn9GduRNvheWte1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/shjmX/btrI2xIsunj/t4sZYqUFn9GduRNvheWte1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FshjmX%2FbtrI2xIsunj%2Ft4sZYqUFn9GduRNvheWte1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;635&quot; height=&quot;277&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다(N)쪽에 유니크 제약조건을 걸어야 한다. 위 예시에선 CHILD가 다 쪽 이기에 CHILD_ID에 유니크.(하지만 PK이기에 이미 유니크)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일대 다 단방향 관계로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;다대일 조인 테이블&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 일대다 조인 테이블과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일대 다 양방향 관계로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;다대다 조인 테이블&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0mX7R/btrJaXyH3d4/aF7hKpKfZGG2inTXYspO4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0mX7R/btrJaXyH3d4/aF7hKpKfZGG2inTXYspO4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0mX7R/btrJaXyH3d4/aF7hKpKfZGG2inTXYspO4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0mX7R%2FbtrJaXyH3d4%2FaF7hKpKfZGG2inTXYspO4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;165&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;165&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다대다 관계를 위해선 두 컬럼을 합해서 하나의 복합 유니크 제약 조건을 걸어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(PARENT_ID, CHILD_ID는 복합 기본키로 유니크 제약조건이 걸려있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 엔티티 하나에 여러 테이블 매핑&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@SecondaryTable을 사용해서 여러 테이블을 매핑할 수 있다.&lt;/li&gt;
&lt;li&gt;잘 사용하진 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3g2sS/btrI7MqUk3O/e9uIDdlkRA3tqiW8lPBAm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3g2sS/btrI7MqUk3O/e9uIDdlkRA3tqiW8lPBAm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3g2sS/btrI7MqUk3O/e9uIDdlkRA3tqiW8lPBAm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3g2sS%2FbtrI7MqUk3O%2Fe9uIDdlkRA3tqiW8lPBAm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;628&quot; height=&quot;281&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1659893442147&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Table(name=&quot;BOARD&quot;) // BOARD테이블과 매핑
// BOARD_DETAIL테이블을 추가로 매핑, 더 많이 매핑하려면 @SecondaryTables 사용
@SecondaryTable(name=&quot;BOARD_DETAIL&quot;,
    // 매핑할 다른 테이블의 기본 키 컬럼 속성
    pkJoinColumns = @PrimaryKeyJoinColumn(name = &quot;BOARD_DETAIL_ID&quot;))
public class BOARD {
    @Id @GeneratedValue
    @Column(name = &quot;BOARD_ID&quot;)
    private Long id;
    
    private String title;
    
    @Column(table = &quot;BOARD_DETAIL&quot;)
    private String content;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@SecondaryTable을 사용해서 두 테이블을 하나의 엔티티에 매핑하는 것 보다 테이블당 엔티티를 각각 만들어서 일대일 매핑 하는 것을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 방법 사용 시 항상 두 테이블을 조회해서 최적화에 어려움이 있다. (일대일 매핑 후 필요 시 둘을 함께 조회하는 방향이 더 좋다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스터디/자바 ORM표준 JPA 프로그래밍</category>
      <author>knowable</author>
      <guid isPermaLink="true">https://knowable.tistory.com/104</guid>
      <comments>https://knowable.tistory.com/104#entry104comment</comments>
      <pubDate>Mon, 8 Aug 2022 02:33:07 +0900</pubDate>
    </item>
    <item>
      <title>[kotest] Core Matchers 기록</title>
      <link>https://knowable.tistory.com/103</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotest.io/docs/assertions/core-matchers.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;kotest core matchers&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;범용&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;obj.shouldBe(other)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 주어진 obj와 other가 모두 같다는 범용 assertion&lt;/li&gt;
&lt;li&gt;expr.shouldBeTrue()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 표현식이&lt;span&gt;&amp;nbsp;&lt;/span&gt;true인지 확인&lt;/li&gt;
&lt;li&gt;expr.shouldBeFalse()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 표현식이&lt;span&gt;&amp;nbsp;&lt;/span&gt;false인지 확인&lt;/li&gt;
&lt;li&gt;shouldThrow &amp;lt;T&amp;gt; { block }&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 블록이&lt;span&gt;&amp;nbsp;&lt;/span&gt;T&lt;span&gt;&amp;nbsp;&lt;/span&gt;Throwable&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는 하위 유형을 throw 하는지 확인&lt;/li&gt;
&lt;li&gt;shouldThrowExactly &amp;lt;T&amp;gt; { block }&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 블록이 정확히 예외를 throw 하는지 확인&lt;/li&gt;
&lt;li&gt;shouldThrowAny { block }&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 블록이 모든 유형의 throwable을 throw하는지 확인&lt;/li&gt;
&lt;li&gt;shouldThrowMessage(message) { block }&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 코드 블록이 주어진 메시지와 함께 모든 throwable을 throw하는지 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;타입&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;obj.shouldBeSameInstanceAs(other)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 개체를 ID로 비교하여 정확히 동일한 참조인지 확인&lt;/li&gt;
&lt;li&gt;obj.shouldBeTypeOf &amp;lt;T&amp;gt;()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 주어진 Type이 정확히&lt;span&gt;&amp;nbsp;&lt;/span&gt;T&lt;span&gt;&amp;nbsp;&lt;/span&gt;유형이라고 확인, 하위 클래스는 실패한다&lt;/li&gt;
&lt;li&gt;obj.shouldBeInstanceOf &amp;lt;T&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 주어진 참조가&lt;span&gt;&amp;nbsp;&lt;/span&gt;T&lt;span&gt;&amp;nbsp;&lt;/span&gt;유형 또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;T의 하위 클래스임을 확인&lt;/li&gt;
&lt;li&gt;obj.shouldHaveAnnotation(annotationClass)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 주어진 인스턴스에 에 지정된 유형의 Annotation이 있는지 확인&lt;/li&gt;
&lt;li&gt;obj.shouldBeNull()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 지정된 참조가 null임을 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비교&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;comp.shouldBeLessThan(other)&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;compareTo를 사용하여 comp가 other보다 미만인지 확인&lt;/li&gt;
&lt;li&gt;comp.shouldBeLessThanOrEqualTo(other)&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;compareTo를 사용하여 comp가 other보다 작거나 같은지 확인&lt;/li&gt;
&lt;li&gt;comp.shouldBeEqualComparingTo(other)&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;compareTo를 사용하여 comp가 other보다 같은지 확인&lt;/li&gt;
&lt;li&gt;comp.shouldBeEqualComparingTo(other, comparator)&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;comparator.compare를 사용하여 comp와 other이 같은지 확인&lt;/li&gt;
&lt;li&gt;comp.shouldBeGreaterThan(other)&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;compareTo를 사용하여 comp가 other보다 큰지 확인&lt;/li&gt;
&lt;li&gt;comp.shouldBeGreaterThanOrEqualTo(other)&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;compareTo를 사용하여 comp가 other보다 크거나 같은지 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Iterator&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iterator.shouldBeEmpty()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: iterator에 다음 값이 없는지 검증&lt;/li&gt;
&lt;li&gt;iterator.shouldHaveNext()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: iterator에 다음값이 있는지 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Map&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;map.shouldContain(&quot;key&quot;, &quot;value&quot;)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: map에 &quot;key&quot;에 대해 매핑되는 &quot;value&quot;가 있는지 확인&lt;/li&gt;
&lt;li&gt;map.shouldContainAll(other)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: map과 other이 동일한 쌍을 포함하는지 확인&lt;/li&gt;
&lt;li&gt;map.shouldContainExactly(other)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: map과 other이 정확하게 동일한 쌍을 포함하는지(다른 쌍은 없어야 함) 확인&lt;/li&gt;
&lt;li&gt;map.shouldContainKey(key)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: map에 &quot;key&quot;의 값이 포함되어있는지 확인&lt;/li&gt;
&lt;li&gt;map.shouldContainKeys(keys)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: map에 주어진 &quot;keys&quot;에 모든 값이 매핑되어있는지 확인&lt;/li&gt;
&lt;li&gt;map.shouldContainValue(value)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: map에 주어진 &quot;value&quot;가 하나 이상 매핑되어있는지 확인&lt;/li&gt;
&lt;li&gt;map.shouldContainValues(values)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: map에 주어진 &quot;values&quot;가 모두 포함하고 있는지 확인&lt;/li&gt;
&lt;li&gt;map.shouldBeEmpty()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: map이 비어있는지 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;String&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;str.shouldBeBlank()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열이 nullOrEmpty인지 확인&lt;/li&gt;
&lt;li&gt;str.shouldBeEmpty()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열의 길이가 0 임을 확인&lt;/li&gt;
&lt;li&gt;str.shouldBeLowerCase()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열이 모두 소문자인지 확인&lt;/li&gt;
&lt;li&gt;str.shouldBeUpperCase()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열이 모두 대문자임을 확인&lt;/li&gt;
&lt;li&gt;str.shouldContain(&quot;substr&quot;)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열에 지정된 하위 문자열이 포함되어 있는지 확인. 하위 문자열은 문자열과 같을 수 있고, 대 소문자를 구분한다&lt;/li&gt;
&lt;li&gt;str.shouldContain(regex)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열에 주어진 정규 표현식이 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;str.shouldContainADigit()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열에 최소한 하나의 숫자가 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;str.shouldContainIgnoringCase(substring)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열에 대소문자를 무시하고 하위 문자열이 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;str.shouldContainOnlyDigits()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열에 숫자만 포함되어 있거나 비어 있는지 확인&lt;/li&gt;
&lt;li&gt;str.shouldBeInteger([radix])&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열에 정수가 포함되어 있는지 확인하고 반환&lt;/li&gt;
&lt;li&gt;str.shouldContainOnlyOnce(substring)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열에 부분 문자열이 정확히 한 번 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;str.shouldEndWith(&quot;suffix&quot;)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열이 지정된 접미사로 끝나는 것을 확인. 접미사는 문자열과 같을 수 있고, 대소문자를 구분한다&lt;/li&gt;
&lt;li&gt;str.shouldHaveLength(length)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열이 주어진 길이를 가지고 있는지 확인&lt;br /&gt;str.shouldHaveLineCount(count)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열에 주어진 수의 행이 포함되어 있는지 확인.&lt;span&gt;&amp;nbsp;&lt;/span&gt;tr.split(&quot;\n&quot;). length.shouldBe(n)&lt;/li&gt;
&lt;li&gt;str.shouldHaveMaxLength(max)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열이 주어진 최대 길이보다 길지 않은지 확인&lt;/li&gt;
&lt;li&gt;str.shouldHaveMinLength(min)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열이 주어진 최소 길이보다 짧지 않은지 확인&lt;/li&gt;
&lt;li&gt;str.shouldHaveSameLengthAs(length)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열의 길이가 다른 문자열과 동일한지 확인&lt;/li&gt;
&lt;li&gt;str.shouldMatch(regex)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열이 주어진 정규식과 완전히 일치하는지 확인&lt;/li&gt;
&lt;li&gt;str.shouldStartWith(&quot;prefix&quot;)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열이 주어진 접두사로 시작하는지 확인. 접두사는 문자열과 같을 수 있고, 대소문자를 구분한다&lt;/li&gt;
&lt;li&gt;str.shouldBeEqualIgnoringCase(other)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 문자열이 대소문자를 무시하고 다른 문자열과 같은지 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Integer, Long&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;num.shouldBeBetween(x, y)&amp;nbsp;: x와 y 사이에 있고 x와 y를 모두 포함하는지 확인&lt;/li&gt;
&lt;li&gt;num.shouldBeLessThan(n) : num이 주어진 값 n보다 작다고 주장&lt;/li&gt;
&lt;li&gt;num.shouldBeLessThanOrEqual(n): num이 주어진 값 n보다 작거나 같은지 확인&lt;/li&gt;
&lt;li&gt;num.shouldBeGreaterThan(n) : num이 주어진 값 n보다 큰지 확인&lt;/li&gt;
&lt;li&gt;num.shouldBeGreaterThanOrEqual(n) : num이 주어진 값 n보다 크거나 같은지 확인&lt;/li&gt;
&lt;li&gt;num.shouldBeEven() : num이 짝수 임인지 확인&lt;/li&gt;
&lt;li&gt;num.shouldBeOdd() : num이 홀수임을 확인&lt;/li&gt;
&lt;li&gt;num.shouldBeInRange(range) : num이 지정된 범위에 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;num.shouldBeZero()&amp;nbsp;: num이 0 임을 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Collection&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;collection.shouldBeEmpty()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션에 element가 없음을 확인&lt;/li&gt;
&lt;li&gt;collection.shouldBeUnique()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션의 모든 element가 중복이 없는지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldContain(element)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션에 지정된 element가 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldContainAll(e1, e2,..., en)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션에 순서가 중요하지 않은 나열된 모든 element가 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldContainDuplicates()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션에 하나이상의 중복 element가 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldContainExactly(e1, e2, ..., en)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션이 정확히 주어진 값을 포함하고 다른 것은 순서대로 포함하지 않음을 확인&lt;/li&gt;
&lt;li&gt;collection.shouldContainExactlyInAnyOrder(e1, e2,..., en)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션이 순서에 상관없이 정확히 주어진 값을 포함하고 다른 것은 포함하지 않음을 확인&lt;/li&gt;
&lt;li&gt;collection.shouldContainNull()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션에 하나 이상의 null element가 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldContainOnlyNulls()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션이 null elements만 포함하거나 비어 있는지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldHaveSingleElement(element)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션이 하나의 element만 포함하는지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldHaveSingleElement { block }&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션이 주어진 술어에 의해 하나의 element를 포함하는지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldHaveSize(length)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션이 정확히 주어진 길이인지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldBeSingleton()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션에 element가 하나만 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldBeSingleton { block }&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션이 단 하나의 element인지 확인한 후 이 element를 사용하여 block실행&lt;/li&gt;
&lt;li&gt;collection.shouldHaveLowerBound(element)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 주어진 element가 컬렉션의 모든 element보다 작거나 같은지 확인. (Comparable을 구현하는 element에 대해서만 작동)&lt;/li&gt;
&lt;li&gt;collection.shouldHaveUpperBound(element)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 주어진 element가 컬렉션의 모든 element보다 크거나 같은지 확인. (Comparable을 구현하는 요소에 대해서만 작동)&lt;/li&gt;
&lt;li&gt;collection.shouldBeSmallerThan(col)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션이 다른 컬렉션보다 작은지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldBeLargerThan(col)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션이 다른 컬렉션보다 큰지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldBeSameSizeAs(col)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션의 크기가 다른 컬렉션과 동일한지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldHaveAtLeastSize(n)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션의 크기가 n 이상인지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldHaveAtMostSize(n)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션의 크기가 최대 n인지 확인&lt;/li&gt;
&lt;li&gt;list.shouldBeSorted()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 목록이 정렬되었는지 확인&lt;/li&gt;
&lt;li&gt;list.shouldContainInOrder(other)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 이 목록에 주어진 목록이 순서대로 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;list.shouldExistInOrder({ element },...)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 이 목록에 술어와 순서대로 일치하는 element가 포함되어 있는지 확인. 다른 element는 술어와 일치하는 element 주변이나, element 사이에 나타날 수 있음&lt;/li&gt;
&lt;li&gt;list.shouldHaveElementAt(index, element)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 이 목록이 주어진 index에 주어진 element를 포함하는지 확인&lt;/li&gt;
&lt;li&gt;list.shouldStartWith(lst)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 이 목록이 주어진 목록의 element로 순서대로 시작하는지 확인&lt;/li&gt;
&lt;li&gt;list.shouldEndWith(lst)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 이 목록이 주어진 목록의 element로 순서대로 끝나는 것을 확인&lt;/li&gt;
&lt;li&gt;value.shouldBeOneOf(collection)&lt;span&gt;&amp;nbsp;&lt;/span&gt;:특정 인스턴스가 컬렉션에 포함되어 있는지 확인&lt;/li&gt;
&lt;li&gt;collection.shouldContainAnyOf(collection)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 컬렉션에 있는 element 중 적어도 하나가 있음을 확인&lt;/li&gt;
&lt;li&gt;value.shouldBeIn(collection)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: object가 컬렉션에 포함되어 있는지 확인. (참조가 아닌 값으로 확인함)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Result&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;result.shouldBeSuccess()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Result가 Success인지 확인&lt;/li&gt;
&lt;li&gt;result.shouldBeSuccess(value)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Result가 Success이고 value와 동일한지 확인&lt;/li&gt;
&lt;li&gt;result.shouldBeSuccess(block)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Result가 Success이면 블록을 실행하여 확인&lt;/li&gt;
&lt;li&gt;result.shouldBeFailure()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Result가 Failure인지 확인&lt;/li&gt;
&lt;li&gt;result.shouldBeFailureOfType &amp;lt;Type : Throwable&amp;gt;()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Result의 Failure의 타입이 동일한지 확인&lt;/li&gt;
&lt;li&gt;result.shouldBeFailure(block)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Result가 Failure이면 블록을 실행하여 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Coroutine Channel&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;channel.shouldReceiveWithin(duration)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 채널이 기간 내에 수신 수신하는지 확인&lt;/li&gt;
&lt;li&gt;channel.shouldReceiveNoElementsWithin(duration)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 채널이 기간 내에 어떤 요소도 수신하지 않는지 확인&lt;/li&gt;
&lt;li&gt;channel.shouldHaveSize(n)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 닫기 전에 채널이 정확히 n개의 요소를 수신하는지 확인&lt;/li&gt;
&lt;li&gt;channel.shouldReceiveAtLeast(n)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 채널이 &amp;gt;= n개의 요소를 수신하는지 확인&lt;/li&gt;
&lt;li&gt;channel.shouldReceiveAtMost(n)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 채널이 닫기 전에 &amp;lt;=n 요소를 수신하는지 확인&lt;/li&gt;
&lt;li&gt;channel.shouldBeClosed()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 채널이 닫혀 있는지 확인&lt;/li&gt;
&lt;li&gt;channel.shouldBeOpen()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 채널이 열려 있는지 확인&lt;/li&gt;
&lt;li&gt;channel.shouldBeEmpty() :채널이 비어 있음을 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참고자료 &lt;a href=&quot;https://beomseok95.tistory.com/368#fun-spec&quot;&gt;https://beomseok95.tistory.com/368#fun-spec&lt;/a&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1659428302374&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Kotest 알아보기&quot; data-og-description=&quot;Kotest Index Kotest Index KoTest란 테스트 스타일 Fun Spec String Spec Should Spec Describe Spec Behavior Spec Word Spec Free Spec Feature Spec Expect Spec Annotation Spec 조건부로 테스트 실행하기 Co..&quot; data-og-host=&quot;beomseok95.tistory.com&quot; data-og-source-url=&quot;https://beomseok95.tistory.com/368#fun-spec&quot; data-og-url=&quot;https://beomseok95.tistory.com/368&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/OO1sf/hyPjd8O0XD/kjE9gf7RtJTzQ8VAtQ3Es1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/JYGQM/hyPhtZJAnU/9PaB5FE3x55ZfcDQvaLMXK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://beomseok95.tistory.com/368#fun-spec&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://beomseok95.tistory.com/368#fun-spec&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/OO1sf/hyPjd8O0XD/kjE9gf7RtJTzQ8VAtQ3Es1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/JYGQM/hyPhtZJAnU/9PaB5FE3x55ZfcDQvaLMXK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Kotest 알아보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Kotest Index Kotest Index KoTest란 테스트 스타일 Fun Spec String Spec Should Spec Describe Spec Behavior Spec Word Spec Free Spec Feature Spec Expect Spec Annotation Spec 조건부로 테스트 실행하기 Co..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;beomseok95.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 참고자료/kotlin</category>
      <author>knowable</author>
      <guid isPermaLink="true">https://knowable.tistory.com/103</guid>
      <comments>https://knowable.tistory.com/103#entry103comment</comments>
      <pubDate>Tue, 2 Aug 2022 17:18:41 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 06. 다양한 연관관계 매핑</title>
      <link>https://knowable.tistory.com/102</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;엔티티의 연관관계를 매핑할 때 고려해야 할 3가지&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다중성(다대일, 일대다, 일대일, 다대다) - 실무에선 보통 다대일과 일대다 관계를 많이 사용한다.&lt;/li&gt;
&lt;li&gt;단방향, 양방향 - 객체에서 한 쪽만 참조하는 것이 단방향, 양쪽 서로 참조는 양방향&lt;/li&gt;
&lt;li&gt;연관관계의 주인 - 연관관계의 주인만이 외래 키를 변경할 수 있다. 주인이 아닌 곳에 mappedBy([주인 이름]) 을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://knowable.tistory.com/101&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 다대일&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt; 다대일 관계의 반대 방향은 항상 일대다 관계&lt;/li&gt;
&lt;li&gt;데이터베이스 테이블의 '일', '다' 관계에서 외래 키는 항상 '다' 쪽에&lt;/li&gt;
&lt;li&gt;객체 양방향 관계에서 연관관계의 주인은 항상 다쪽 (회원(N)과 팀(1)이 있으면 회원 쪽이 연관관계의 주인)&lt;/li&gt;
&lt;li&gt;5장에서 살펴본 내용들이 다대일의 단방향, 양방향&lt;/li&gt;
&lt;li&gt;양방향 연관관계는 항상 서로 참조해야 한다.&lt;/li&gt;
&lt;li&gt;서로 참조를 위해 편의 메소드를 작성해주는데, 양 쪽에 메소드를 작성한다면 무한루프에 빠지지 않도록 주의해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 일대다&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다대일 관계의 반대방향&lt;/li&gt;
&lt;li&gt;엔티티를 하나 이상 참조할 수 있으므로 Collection, List, Set, Map중에 하나를 사용해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단방향&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 팀이 여러 회원을 참조할 수 있으면 일대다 관계&lt;/li&gt;
&lt;li&gt;팀이 회원들을 참조하지만, 회원이 팀을 참조하지 않는다면 단방향 관계&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDDc9p/btrIA19JTDp/INQ8y6QGWuPfxHNjEhLZu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDDc9p/btrIA19JTDp/INQ8y6QGWuPfxHNjEhLZu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDDc9p/btrIA19JTDp/INQ8y6QGWuPfxHNjEhLZu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDDc9p%2FbtrIA19JTDp%2FINQ8y6QGWuPfxHNjEhLZu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;267&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;331&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일대 다 관계에서 외래키는 항상 '다'쪽에&lt;/li&gt;
&lt;li&gt;Member가 다 쪽이지만, Member에 외래키를 매핑할 수 있는 필드가 없기에 참조 필드(members)가 있는 Team에서 MEMBER의 FK를 관리하게 된다.&lt;/li&gt;
&lt;li&gt;일대다 단방향 관계를 매핑할 때 @JoinColumn을 명시해야 한다.(아니면 JPA는 joinTable전략을 기본으로 하여 매핑. 7.4)&lt;/li&gt;
&lt;li&gt;일대 다 매핑의 단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매핑한 객체가 관리하는 외래키가 다른 테이블에 있다는 점&lt;/li&gt;
&lt;li&gt;엔티티 저장과 연관관계 처리 시, INSERT 뿐만 아니라 UPDATE 까지 추가로 실행해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;일대 다 보단 다대일 양방향 매핑 사용이 좋다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성능과 관리의 편함을 위하여.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;양방향&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따로 존재하지 않고, 다대일 양방향 매핑을 사용해야 한다.(일대다 양방향이나 다대일 양방향은 사실 같은 말)&lt;/li&gt;
&lt;li&gt;'다'쪽이 항상 주인이 되기에 일대다에서 @OneToMany쪽에 주인이 될 수 없다.&lt;/li&gt;
&lt;li&gt;하지만 일대다 단방향 매핑 반대편에 같은 외래 키를 사용하는 다대일 단방향 매핑을 읽기전용으로 추가하여 양방향 매핑을 사용할 수는 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTCqC9/btrICPVb8mL/yEdljfqfEKowjNIOKV363K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTCqC9/btrICPVb8mL/yEdljfqfEKowjNIOKV363K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTCqC9/btrICPVb8mL/yEdljfqfEKowjNIOKV363K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTCqC9%2FbtrICPVb8mL%2FyEdljfqfEKowjNIOKV363K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;546&quot; height=&quot;247&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단방향과 마찬가지의 단점을 그대로 가지기에, 될 수 있으면 다대일 양방향을 사용하도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Member&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1659198485473&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Member{
    // 다대일 단방향, 양방향 / 일대다 단방향
    @Id @GeneratedValue
    @Column(name = &quot;MEMBER_ID&quot;) 
    private String id;
    
    private String username;
    
    // 다대일 단방향, 양방향//
    @ManyToOne
    @JoinColumn(name=&quot;TEAM_ID&quot;)
    private Team team;
    
    // 일대다 양방향
    // 읽기만 가능하게 해서 문제 발생을 막는다
    // 일대다 단방향 반대편에 다대일 단방향 매핑을 읽기 전용으로 두어 일대다 양방향처럼 보이도록
    @ManyToOne
    @JoinColumn(name= &quot;TEAM_ID&quot;, insertable = false, updatable=false)
    private Team team;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Team&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1659198499710&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Team{
    @Id @GeneratedValue
    @Column(name=&quot;TEAM_ID&quot;)
    private String id;
    
    private String name;
    
    // 다대일 양방향
    @OneToMany(mappedBy=&quot;team&quot;)
    private List&amp;lt;Member&amp;gt; members = new ArrayList&amp;lt;Member&amp;gt;();
    
    // 일대다 단뱡향, 양방향
    @OneToMany
    @JoinColumn(name = &quot;TEAM_ID&quot;) // MEMBER테이블의 TEAM_ID(FK)
    private List&amp;lt;Member&amp;gt; members = new ArrayList&amp;lt;Member&amp;gt;();
    

    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 일대일&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;양쪽이 서로 하나의 관계만 가진다. (ex. 회원은 하나의 사물함만 사용)&lt;/li&gt;
&lt;li&gt;일대일 관계는 그 반대도 일대일&lt;/li&gt;
&lt;li&gt;일대일 관계는 주 테이블이나 대상 테이블 중 어느 곳이나 외래키를 가질 수 있다.(둘 중 선택해야 한다.)&lt;/li&gt;
&lt;li&gt;주 테이블에 외래 키
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주 테이블에 외래 키를 두고 대상 테이블을 참조&lt;/li&gt;
&lt;li&gt;주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있다.&lt;/li&gt;
&lt;li&gt;객체 지향적임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대상 테이블에 외래 키
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블 관계를 일대일에서 일대다로 변경할 때, 테이블 구조를 그대로 유지할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1 주 테이블에 외래 키&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단방향&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdNy4i/btrIzDH7eeB/NXCqU4hVRQDOdKIxm2lfC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdNy4i/btrIzDH7eeB/NXCqU4hVRQDOdKIxm2lfC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdNy4i/btrIzDH7eeB/NXCqU4hVRQDOdKIxm2lfC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdNy4i%2FbtrIzDH7eeB%2FNXCqU4hVRQDOdKIxm2lfC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;276&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LOCKER_ID 외래키에 유니크 제약조건 추가 (UNI)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;양방향&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b30L6V/btrIyxauoYJ/8TFKRdbcDJyoDo4lu7E0hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b30L6V/btrIyxauoYJ/8TFKRdbcDJyoDo4lu7E0hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b30L6V/btrIyxauoYJ/8TFKRdbcDJyoDo4lu7E0hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb30L6V%2FbtrIyxauoYJ%2F8TFKRdbcDJyoDo4lu7E0hk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;501&quot; height=&quot;271&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.2 대상 테이블에 외래 키&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단방향&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diV8HV/btrIBUWRLwH/ElsA5whJ57K5GwJOLItaV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diV8HV/btrIBUWRLwH/ElsA5whJ57K5GwJOLItaV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diV8HV/btrIBUWRLwH/ElsA5whJ57K5GwJOLItaV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiV8HV%2FbtrIBUWRLwH%2FElsA5whJ57K5GwJOLItaV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;487&quot; height=&quot;256&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지원하지 않는다.&lt;/li&gt;
&lt;li&gt;단방향 관계를 Locker에서 Member방향으로 수정하여 대상 테이블을 바꾸거나, 양방향 관계로 만든 후 Locker를 주인으로 설정해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;양방향&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blsw7V/btrIxCclLZL/BDdB6227fjfszUp0IbVja0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blsw7V/btrIxCclLZL/BDdB6227fjfszUp0IbVja0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blsw7V/btrIxCclLZL/BDdB6227fjfszUp0IbVja0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblsw7V%2FbtrIxCclLZL%2FBDdB6227fjfszUp0IbVja0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;286&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1659199895222&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Member{
    // 단방향, 양방향
    @Id @GeneratedValue
    @Column(name = &quot;MEMBER_ID&quot;) 
    private Long id;
    
    private String username;
    
    @OneToOne // 일대일 관계
    @JoinColumn(name = &quot;LOCKER_ID&quot;)
    private Locker locker;
    
    // 대상 테이블 외래키 양방향
    @OneToOne(mappedBy = &quot;member&quot;)
    private Locker locker;
}

@Entity
public class Locker{
    // 단방향
    @Id @GeneratedValue
    @Column(name = &quot;LOCKER_ID&quot;) 
    private Long id;
    
    private String name;
    
    // 양방향
    // MEMBER테이블이 외래 키를 가지고있으므로 Member.lock이 주인
    // Locker.member는 주인이 아니라고 설정해야 하기에 mappedBy
    @OneToOne(mappedBy = &quot;locker&quot;)
    private Member member;
    
    // 대상 테이블 외래키 양방향
    // 대상 엔티티인 Locker를 연관관계의 주인으로 만들어서 LOCKER테이브르이 외래 키를 관리하도록 함
    @OneToOne
    @JoinColumn(name = &quot;MEMBER_ID&quot;)
    private Member member;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 다대다&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관계형 DB는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.&lt;/li&gt;
&lt;li&gt;중간에 연결 테이블을 추가해서 다대다 관계를 일대다/다대일 관계로 풀어낼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ASlPW/btrIxwpkPza/gZ4kGSEqcr4XCcZqd5LMp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ASlPW/btrIxwpkPza/gZ4kGSEqcr4XCcZqd5LMp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ASlPW/btrIxwpkPza/gZ4kGSEqcr4XCcZqd5LMp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FASlPW%2FbtrIxwpkPza%2FgZ4kGSEqcr4XCcZqd5LMp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;241&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(회원들이 상품을 주문하는 경우가 다대다 관계)&lt;/li&gt;
&lt;li&gt;객체는 테이블과 다르게 2개로 다대다 관계를 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.1 단방향&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1659286362016&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Member{
    @Id @Column(name = &quot;MEMBER_ID&quot;)
    private String id;
    
    private String username;
    
    // ManyToMany로 매핑하면서 JoinTable을 사용해 연결 테이블을 바로 매핑
    @ManyToMany
    @JoinTable(name=&quot;MEMBER_PRODUCT&quot;, // 다대다를 일대다/다대일 로 풀어내기 위한 연결 테이블 지정
              joinColumns = @JoinColumn(name = &quot;MEMBER_ID&quot;), // 현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정
              inverseJoinColumns = @JoinColumn(name = &quot;PRODUCT_ID&quot;)) // 반대 방향인 상품과 매핑할 조인 컬럼 정보
    private List&amp;lt;Product&amp;gt; products = new ArrayList&amp;lt;Product&amp;gt;();
    ...
}

@Entity
public class Product{
    @Id @Column(name=&quot;PRODUCT_ID&quot;)
    private String id;
    
    private String name;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.2 양방향&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단뱡향 product에 역방향만 추가하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1659286528533&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Product{
    @Id @Column(name=&quot;PRODUCT_ID&quot;)
    private String id;
    
    private String name; // 책 예시에는 없음
    
    @ManyToMany(mappedBy = &quot;products&quot;) // 역방향 추가
    private List&amp;lt;Member&amp;gt; members;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.3 매핑의 한계와 극복, 연결 엔티티 사용(식별관계)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다대다 매핑을 실무에서 하기엔 한계가 있다.&lt;/li&gt;
&lt;li&gt;MemberProduct에 주문 수량, 주문 날짜와 같은 컬럼이 추가된다면, 이들을 매핑 할 방법이 없다.&lt;/li&gt;
&lt;li&gt;떄문에 연결 테이블을 매핑하는 연결 엔티티를 만들고 이곳에 추가한 컬럼을 매핑한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbf9dO/btrIACPDP8m/KZFofKqQYaXY9KCemU2FVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbf9dO/btrIACPDP8m/KZFofKqQYaXY9KCemU2FVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbf9dO/btrIACPDP8m/KZFofKqQYaXY9KCemU2FVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcbf9dO%2FbtrIACPDP8m%2FKZFofKqQYaXY9KCemU2FVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;601&quot; height=&quot;142&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1659287463665&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Member{
    @Id @Column(name = &quot;MEMBER_ID&quot;)
    private String id;
    
    private String username;
    
    @OneToMany(mappedBy = &quot;member&quot;)
    private List&amp;lt;MemberProduct&amp;gt; memberProducts;
    ...
}

@Entity
public class Product{
    @Id @Column(name=&quot;PRODUCT_ID&quot;)
    private String id;
    
    private String name;
}

@Entity
@IdClass(MemberProductId.class) // 복합 기본 키를 매핑, 아래 식별자 클래스를 지정하면 된다
public class MemberProduct{
    @Id // 기본키
    @ManyToOne
    @JoinColumn(name = &quot;MEMBEWR_ID&quot;) // 외래키 한번에 매핑
    private Member member;
    
    @Id
    @ManyToOne
    @JoinColumn(name = &quot;PRODUCT_ID&quot;)
    private Product product;
    
    // 기본 키가 MEMBER_ID, PRODUCT_ID인 복합 기본 키
    
    private int orderAmount;
}

// 복합키를 사용하기 위한 별도의 식별자 클래스
public class MemberProductId implements Serializable{
    private String member;
    private String product;
    
    @Override // equals, hashCode 메소드를 구현해야 한다.
    public boolean equals(Object o){...}
    
    @Override
    public int hashCode(){...}
    ...
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.4 새로운 기본 키 사용(비식별관계)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단하고 추천하는 기본 키 생성 전략은 데이터베이스에서 자동으로 생성해주는 대리 키를 Long값으로 사용하는 것&lt;/li&gt;
&lt;li&gt;복합 키를 사용하는 것 보다 매핑이 단순하고 이해하기 쉬워진다. (편리하게 ORM 매핑을 할 수 있어 비식별 관계를 추천)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;353&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHS23r/btrIGylLowj/gW9F3Vg6hhv45gVHK3Fjl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHS23r/btrIGylLowj/gW9F3Vg6hhv45gVHK3Fjl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHS23r/btrIGylLowj/gW9F3Vg6hhv45gVHK3Fjl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHS23r%2FbtrIGylLowj%2FgW9F3Vg6hhv45gVHK3Fjl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;152&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;353&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ORDER_ID라는 새로운 기본 키 하나를 만들고, MEMBER_ID, PRODUCT_ID 컬럼을 외래키로만 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1659288186251&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Member{
    @Id @Column(name = &quot;MEMBER_ID&quot;)
    private String id;
    
    private String username;
    
    @OneToMany(mappedBy = &quot;member&quot;)
    private List&amp;lt;Order&amp;gt; orders = new ArrayList&amp;lt;Order&amp;gt;();
    ...
}

@Entity
public class Product{
    @Id @Column(name=&quot;PRODUCT_ID&quot;)
    private String id;
    
    private String name;
}

@Entity
public class Order{
    @Id @GeneratedValue
    @Column(name = &quot;ORDER_ID&quot;)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = &quot;MEMBER_ID&quot;)
    private Member member;
    
    @ManyToOne
    @JoinColumn(name = &quot;PRODUCT_ID&quot;)
    private Product product;
        
    private int orderAmount;
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스터디/자바 ORM표준 JPA 프로그래밍</category>
      <author>knowable</author>
      <guid isPermaLink="true">https://knowable.tistory.com/102</guid>
      <comments>https://knowable.tistory.com/102#entry102comment</comments>
      <pubDate>Sun, 31 Jul 2022 01:52:43 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 05. 연관관계 매핑 기초</title>
      <link>https://knowable.tistory.com/101</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 단방향 연관관계&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객체 연관관계 v.s 테이블 연관관계&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zbfog/btrH1bsUIyi/aCrC3VztdSvk1y8JCZgssK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zbfog/btrH1bsUIyi/aCrC3VztdSvk1y8JCZgssK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zbfog/btrH1bsUIyi/aCrC3VztdSvk1y8JCZgssK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzbfog%2FbtrH1bsUIyi%2FaCrC3VztdSvk1y8JCZgssK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;510&quot; height=&quot;278&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체는 참조로 연관관계를 맺는다 (단방향이다)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;member.getTeam() 조회가 가능하다고 tean.getMember()가 가능하진 않다.&lt;/li&gt;
&lt;li&gt;가능하게 하기 위해선 필드를 추가해야 한다 (양방향 보단 2개의 단방향이 만들어진다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블은 외래 키로 연관관계를 맺는다 (양방향이다)&lt;/li&gt;
&lt;li&gt;MEMBER JOIN TEAM이 가능하면 TEAM JOIN MEMBER도 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객체 관계 매핑 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1658675350793&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//**** 객체 ****//
public class Member{
    private String id;
    private String username;
    
    private Team team
    
    //getter, setter
}
public class Team{
    private String id;
    private Stirng name;
    // getter, setter
}

//**** 테이블 ****//
CREATE TABLE MEMBER(
    MEMBER_ID
    TEAM_ID
    USERNAME
    PRIMARY KEY (MEMBER_ID)
)
CREATE TABLE TEAM(
    TEAM_ID
    NAME
    PRIMARY KEY (TEAM_ID)
)

//**** 매핑 ****//
@Entity
public class Member{
    @Id
    @Column(name = &quot;MEMBER_ID&quot;)
    private String id;
    
    private String username;
    
    // 연관관계 매핑
    @ManyToOne // 다중성 나타내는 애노테이션 사용이 필수
    @JoinColumn(name=&quot;TEAM_ID&quot;) // 외래 키를 매핑할 때 사용(name속성에 매핑할 외래 키 이름 지정)
    private Team team;
}

// Team에는 특별한거 없이&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 속성은 책 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 연관관계 사용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저장&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1658676899339&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void save(){
	// 팀1 저장
    Team team1 = new Team(&quot;team1&quot;, &quot;팀1&quot;);
    em.persist(team1);
    
    // 회원1 저장
    Member member1 = new Member(&quot;member1&quot;, &quot;회원1&quot;);
    member1.setTeam(team1) // 회원 -&amp;gt; 팀 참조, JPA가 Team의 식별자를 외래 키로 하여 적절한 쿼리를 생성
    eme.persist(member1) // 저장
    
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;조회&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1658677311685&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 객체 그래프 탐색
Member member = em.find(Member.class, &quot;member1&quot;);
Team team = member.getTeam(); // get사용해서 객체 그래프 탐색 후 조회 가능
// 자세한 내용은 8장

// 객체지향 쿼리 사용
String jpql = &quot;select m from Member m join m.team t where &quot; +
    &quot;t.name=:teamName&quot;;
    
List&amp;lt;Member&amp;gt; resultList = em.createQuery(jpql, Member.class)
    .setParameter(&quot;teamName&quot;,&quot;팀1&quot;)
    .getResultList();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1658677494784&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 팀1 소속이던 회원을 팀2에 소속되도록 수정한다면
private static void updateRelation(EntityManager em){
    // 새로운 팀 생성
    Team team2 = new(&quot;team2&quot;, &quot;팀2&quot;);
    em.persist(team2);
    // 회원에 새로운 팀 설정
    Member member = em.find(Member.class, &quot;member1&quot;);
    memberse3tTeam(team2);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;연관관계 제거&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1658677828139&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 회원1을 팀에 소속하지 않도록 변경
private static void deleteRelation(EntityManager em){
    Member member1 = em.find(Member.class, &quot;member1&quot;);
    member.setTeam(null); // 연관관계 제거
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;연관된 엔티티 삭제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1658677943041&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 엔티티 삭제를 위해선 연관관계 제거 후 삭제 해야한다.
// team에 member1,2가 소속되어있다면
private static void deleteRelation(EntityManager em){
    member1.setTeam(null);
    member2.setTeam(null);
    em.remove(team);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 양방향 연관관계&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서는 회원에서 팀으로만 접근했다면, 티멩서 회원으로 접근할 수 있도록&lt;/p&gt;
&lt;pre id=&quot;code_1658678618887&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 팀 엔티티에 추가
@Entity
public clas Team{
    @Id
    @Column(name=&quot;TEAM_ID&quot;)
    private String id;
    
    private String name;
    
    @OneToMany(mappedBy=&quot;team&quot;) // 일대 다 관계 (팀 하나에 여러 멤버), 반대쪽 매핑의 필드를 값으로, 연관관계의 주인에서 설명
    private List&amp;lt;Member&amp;gt; members = new ArrayList&amp;lt;Member&amp;gt;();
    // 팀과 회원이 일대다 관계이기 때문에 List를 추가
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;조회&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1658678776193&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void biDirection(){
    Team team = em.find(Team.class, &quot;team1&quot;);
    List&amp;lt;Member&amp;gt; members = team.getMembers(); // 객체 그래프 탐색
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 연관관계의 주인&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티를 양방향으로 매핑하면 단방향 2개로 관리하기에 누가 외래키를 관리할지 모르게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 관리하기 위해 연관관계주인을 두고 외래키를 관리하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주인만이 DB연관관계와 매핑되고 외래 키를 관리한다.(주인이 아닌 쪽은 읽기만)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연관관계의 주인은 외래키가 있는 곳이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;u&gt;회원테이블이 FK를 가지고있으므로 주인&lt;/u&gt;&lt;/i&gt;이 되고, 주인이 아닌 team에 mappedBy=&quot;team&quot;설정해서 주인이 아님을 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(비즈니스 중요도에 따라 주인을 생각해서는 안된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 양방향 연관관계 저장&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1658679708842&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 팀 생성, 회원 저장은 단방향과 동일

team1.getMembers().add(member1) // team은 주인이 아니기에 외래키에 영향을 주지 않는다.

member1.setTeam(team1) // 주인만이 외래키를 관리할 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 양방향 연관관계의 주의점&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 5번에서 언급한 것 처럼 주인이 아닌 곳에만 값을 입력하는 것을 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 객체 관점에서 주인 뿐만 아니라 &lt;i&gt;&lt;u&gt;양쪽 방향에 모두 값을 입력해주는 것이 가장 안전&lt;/u&gt;&lt;/i&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(모두 입력하지 않고, JPA를 사용하지 않으면 문제가 발생할 수 있기 때문)&lt;/p&gt;
&lt;pre id=&quot;code_1658680390389&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Team team1 = new Team(&quot;tean1&quot;, &quot;팀1&quot;);
Member member1 = new Member(&quot;member1&quot;, &quot;회원1&quot;);
Member member2 = new Member(&quot;member2&quot;, &quot;회원2&quot;);

member1.setTeam(team1);
member2.setTeam(team1);

List&amp;lt;Member&amp;gt; members = team1.getMembers();
System.out.println(members.size()) // 0이 나온다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 이유로 양방향을 한번에 설정하도록 하는 것이 안전(양방향 관계를 한 번에 설정, 연관관계 편의 메소드)&lt;/p&gt;
&lt;pre id=&quot;code_1658680603845&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Member {
  private Team team;
  
  public void setTeam(Team team){
      
      // 기존 관계를 없애지 않으면 다른 팀으로 삭제를 해도 기존 관계가 유지되기에
      // set 전에 기존관계 확인 후 관계가 있다면 삭제를 우선 해주어야 한다.
      if(this.team != null){
          this.team.getMembers().remove(this);
      }
  
      this.team = team;
      team.getMembers().add(this);
  }
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체에서 양방향 연관 관계를 사용하려면 로직을 견고하게 작성 할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡하기에, 단방향 매핑을 우선 사용하다 필요 시 양방향을 추가하는 편이 좋다.&lt;/p&gt;</description>
      <category>스터디/자바 ORM표준 JPA 프로그래밍</category>
      <author>knowable</author>
      <guid isPermaLink="true">https://knowable.tistory.com/101</guid>
      <comments>https://knowable.tistory.com/101#entry101comment</comments>
      <pubDate>Mon, 25 Jul 2022 01:50:23 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 04. 엔티티 매핑</title>
      <link>https://knowable.tistory.com/100</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. @Entity&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블과 매핑할 클래스에 필수로 붙여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용 시 주의사항&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 생성자가 필수(파라미터가 없는 public 또는 protected 생성자)&lt;/li&gt;
&lt;li&gt;final 클래스, enum, interface, inner 클래스에는 사용할 수 없다.&lt;/li&gt;
&lt;li&gt;저장할 필드에 final을 사용해서는 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. @Table&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티와 매핑할 테이블을 지정한다.(생략시 매핑한 엔티티 이름을 테이블 이름으로 사용)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;속성&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;기능&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;기본값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;name&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;매핑할 테이블 이름&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;엔티티 이름 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;catalog&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;catalog 기능이 있는 DB에서 catalog 매핑&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;schema&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;schema 기능이 있는 DB에서 schema 매핑&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;uniqueConstraints&lt;br /&gt;(DDL)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;- DDL 생성 시에 유니크 제약조건을 만든다.&lt;br /&gt;- 2개 이상의 복합 유니크 제약조건도 만들 수 있다.&lt;br /&gt;- 스키마 자동 생성 기능을 사용해서 DDL만들 때만 사용된다&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 다양한 매핑 사용&lt;/b&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 데이터베이스 스키마 자동 생성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA는 DB 스키마를 자동으로 생성하는 기능을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(매핑정보와 DB 방언을 사용해서 스키마를 생성한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1658076297591&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;property name=&quot;hibernate.hbm2ddl.auto&quot; value=&quot;create&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 persistence.xml에 등록하면, 애플리케이션 실행 시 자동으로 DB테이블을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 자동 생성 기능이 만든 DDL은 운영 환경에서 사용할 만큼 완벽하진 않기에 개발 환경에서 사용하거나 매핑을 어떻게 할지 참고하는 용으로 사용하여야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. DDL 생성 기능&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동으로 생성되는 DDL에 제약조건을 추가할 수 있다&lt;/p&gt;
&lt;pre id=&quot;code_1658076581356&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 회원 이름을 필수로 입력해야 하고, 10자를 초과하면 안된다
@Column(name=&quot;NAME&quot;, nullable=false, length = 10)
private String username;

// 유니크 제약조건 만들기
// @Table의 uniqueConstraints속성 사용
@Entity(name=&quot;Member&quot;)
@Table(name=&quot;MEMBER&quot;, uniqueConstraints = {@UniqueConstraint(
  name = &quot;NAME_AGE_UNIQUE&quot;,
  columnNames = {&quot;NAME&quot;, &quot;AGE&quot;} )})
public class Member{
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들은 DDL자동 생성때만 사용되고 JPA실행 로직에는 영향을 주지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 엔티티만 보고 쉽게 다양한 제약조건을 파악할 수 있다는 장점이 있기에 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 기본 키 매핑&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;직접 할당&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 키를 애플리케이션에 직접 할당한다&lt;/p&gt;
&lt;pre id=&quot;code_1658076981357&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Id
@Column(name = &quot;id&quot;)
private String id;

// em.persist()로 엔티티를 저장하기 전에 기본 키를 직접 할당
Board board = new Board();
board.setId(&quot;id1&quot;);
em.persist(board);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;자동 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB가 생성해주는 값을 사용하여 기본키를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Id 애노테이션 뿐만 아니라 @GeneratedValue 애노테이션도 사용해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;IDENTITY 전략&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 키 생성을 DB에 위임하는 전략&lt;/li&gt;
&lt;li&gt;ex) MySQL의 AUTO_INCREMENT 기능 (DB가 자동으로 기본키를 순서대로 생성)&lt;/li&gt;
&lt;li&gt;DB에 저장하고 나서 기본키를 구할 수 있을 때 사용&lt;/li&gt;
&lt;li&gt;@GeneratedValue(strategy = GenerationType.IDENTITY)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SEQUENCE 전략&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유일한 값을 순서대로 생성하여 기본 키를 생성&lt;/li&gt;
&lt;li&gt;CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1 과 같이 우선 시퀀스를 생성해야 한다.&lt;/li&gt;
&lt;li&gt;@GeneratedValue(strategy = GenerationType.SEQUENCE,&amp;nbsp;generator = &quot;BOARD_SEQ_GENERATOR&quot;)&lt;/li&gt;
&lt;li&gt;다음과 같이 사용 할 시퀀스 매핑 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TABLE 전략&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키 생성 전용 테이블을 하나 만들고, 이름과 값으로 사용할 컬럼을 만들어 DB 시퀀스를 흉내내는 전략&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1658077731090&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TABLE 우선 생성
create table MY_SEQUENCES(
    sequence_name varchar(255) not null, // 시퀀스 이름
    next_val bigint, // 시퀀스 값
    primary key ( sequence_name )
)

// TABLE 전략 사용 시
@Entity
@TableGenerator( //테이블 키 생성기를 등록
    name = &quot;BOARD_SEQ_GENERATOR&quot;, // 이런 이름으로 테이블 키 생성기를 등록
    table = &quot;MY_SEQUENCES&quot;, // 거기에 매핑 된 테이블
    pkColumnValue = &quot;BOARD_SEQ&quot;, allocationSize = 1)
public class Board{

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = &quot;BOARD_SEQ_GENERATOR&quot;)
    private Long id;
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;AUTO 전략&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선택한 DB나 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다.&lt;/li&gt;
&lt;li&gt;(오라클을 선택하면 SEQUENCE, MySQL을 선택하면 IDENTITY를 사용)&lt;/li&gt;
&lt;li&gt;@GeneratedValue(strategy = GenerationType.AUTO) 혹은 @GeneratedValue (기본값이 AUTO)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. 필드와 컬럼 매핑: 레퍼런스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요할 때 마다 찾아서 읽는 것을 권장.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@Column&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 필드를 테이블 컬럼에 매핑&amp;nbsp;&lt;/li&gt;
&lt;li&gt;name
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필드와 매핑할 테이블의 컬럼 이름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;insertable
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;거의 사용하지 않음&lt;/li&gt;
&lt;li&gt;엔티티 저장 시 이 필드도 같이 저장하도록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;updatable
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;거의 사용하지 않음&lt;/li&gt;
&lt;li&gt;엔티티 수정 시 이 필드도 같이 수정하도록.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;table
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;거의 사용하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;nullable(DDL)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null값 허용 여부 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;unique(DDL)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 컬럼에 간단히 유니크 제약조건을 걸 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;columnDefinition(DDL)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스 컬럼 정보를 직접 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;length(DDL)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자 길이 제약조건, String 타입에만 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;precision, scale(DDL)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BigDecimal타입에 사용&lt;/li&gt;
&lt;li&gt;precision -&amp;gt; 소수점 포함한 전체 자릿수&lt;/li&gt;
&lt;li&gt;scale -&amp;gt; 소스의 자릿수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Enumerated&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바의 enum타입을 매핑할 때 사용&amp;nbsp;&lt;/li&gt;
&lt;li&gt;value
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EnumType.ORDINAL
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순서를 DB에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;EnumType.STRING
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름을 DB에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Temporal&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;날짜 타입을 매핑할 때 사용.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;value
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TemporalType.DATE
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;날짜, DB date 타입과 매핑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TemporalType.TIME
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간, DB time 타입과 매핑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TemporalType.TIMESTAMP
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;날짜와 시간, DB timestamp 타입과 매핑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Lob&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB의 BLOB, CLOB 타입과 매핑&lt;/li&gt;
&lt;li&gt;BLOB
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #34495e;&quot;&gt;Binary Large Object, 이미지, 사운드, 비디오와 같은 멀티미디어 데이터를 다룰 때 사용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CLOB
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Character Large Obejct, 문자형 대용량 객체를 다룰 때 사용(가변도 가능, 최대 4gb)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Transient&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체에 임시로 어떤 값을 보관하고 싶을 때 사용&lt;/li&gt;
&lt;li&gt;해당 필드는 매핑하지않고, 저장 조회도 하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Access&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA가 엔티티 데이터에 접근하는 방식을 지정&lt;/li&gt;
&lt;li&gt;필드 접근
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AccessType.FIELD로 지정. 필드에 직접 접근&lt;/li&gt;
&lt;li&gt;private에도 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프로퍼티 접근
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AccesType.PROPERTY로 지정. 접근자(Getter)사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@Access설정이 없다면 @Id를 기준으로 접근 방식이 설정된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스터디/자바 ORM표준 JPA 프로그래밍</category>
      <author>knowable</author>
      <guid isPermaLink="true">https://knowable.tistory.com/100</guid>
      <comments>https://knowable.tistory.com/100#entry100comment</comments>
      <pubDate>Mon, 11 Jul 2022 08:51:18 +0900</pubDate>
    </item>
  </channel>
</rss>