2. JPA 시작

<dependencies>
    <!-- JPA 하이버네이트 -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>5.3.10.Final</version>
    </dependency>
    <!-- H2 데이터베이스 -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.199</version>
    </dependency>
</dependencies>
CREATE TABLE MEMBER (
  id bigint not null,
  name varchar(255),
  primary key (id)
);
SELECT * FROM MEMBER;

3. 영속성 관리

package hello.jpa;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Member {
	@Id
	private Long id;
	private String name;
	// getter, setter
package hello.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

public class JpaMain {
	public static void main(String[] args) {
		// persistence.xml에 정의된 hello 설정정보 가져옴
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
		EntityManager em = emf.createEntityManager();

		EntityTransaction tx = em.getTransaction();
		tx.begin();
		try {
			// 1. Create 등록

			// 비영속
//			Member member = new Member();
//			member.setId(1L);
//			member.setName("HelloA");
			//	영속 DB쿼리는 아직-> commit(); 시점에 INSERT 됨
			// 1차캐시에 저장됨
//			em.persist(member);
			// 비슷한 방식으로 select 를 할때도 DB 조회후에 1차캐시에 저장!
			//  -> 2번째 조회시에는 1차캐시에서 가져옴

			// 2. Read 조회
			// 1차캐시에서 조회함 : 쿼리 select 쿼리 안나감(1차캐시)
//			Member findMember = em.find(Member.class, 1L);
//			System.out.println("findMember.Id=" + findMember.getId());
//			System.out.println("findMember.Name=" + findMember.getName());
			List<Member> result = em.createQuery("select m from Member as m", Member.class)
					.setFirstResult(5) // 5번 부터
					.setMaxResults(8)  // 8개 데이터
					.getResultList();
			for (Member m : result) {
				System.out.println("member.name = " + m.getName());
			}

			// 3. Update 수정
			Member findMember = em.find(Member.class, 1L);
			findMember.setName("HelloJPA");
			// em.persist(member); 필요없음! 마치 컬렉션에 저장하는것 같음
			// -> UPDATE쿼리 tx.commit(); 시점에 발생!

			// 4. Delete 삭제
//			em.remove(findMmeber);

			// 커밋!
			tx.commit();

		} catch(Exception e) {
			tx.rollback();
		} finally {
			em.close();
		}

		emf.close();
	}
}
  // 3. Update 수정
  Member findMember = em.find(Member.class, 1L);
  findMember.setName("HelloJPA");
  // em.persist(member); 필요없음! 마치 컬렉션에 저장하는것 같음
  // -> UPDATE쿼리 tx.commit(); 시점에 발생!
Member m = em.find(Member.class, 150L);
m.setName("AAA");
em.detach(m);

// 아무일도 일어나지 않음(detach되어서 JPA가 관리하지 않기 때문)
// select쿼리만 나오고 insert는 실행안됨
tx.commit();

4. 앤티티 매핑

// 유니크 제약조건 이름정의
@Entity(name = "Member")
@Table(name = "MEMBER", uniqueConstraints = {@UniqueConstraint(
	name = "NAME_AGE_UNIQUE",
	columnNames = {"NAME", "AGE"},
	)}
)
public class Member{
}
private static void logic(EntityManager em) {
	Member m = new Member();
	em.persist(m);
	// 출력 m.id = 1
	System.out.println("m.id = " + m.getId());
}
@Entity
public class Member {
	@Id
	private Long id;

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

	private Integer age;

	@Enumerated(EnumType.STRING)
	private RoleType roleType;

	@Temporal(TemporalType.TIMESTAMP)
	private Date createdDate;

	@Temporal(TemporalType.TIMESTAMP)
	private Date lastModifiedDate;

	@Lob
	private String description;

	public Member() {}
}
create table Member (
  id bigint not null,
  age integer,
  createdDate timestamp,
  description clob,
  lastModifiedDate timestamp,
  roleType varchar(255),
  name varchar(255),
  primary key (id)
)
  Order order = em.find(Order.class, 1L);
  Long memberId = order.getMemberId();
  Member member = em.find(Member.class, memberId);

5. 연관관계 매핑 기초

package hello.jpa;

import javax.persistence.*;

@Entity
public class Member {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "MEMBER_ID")
	private Long id;

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

	// 객체 team과 DB 조인컬럼 TEAM_ID 명시
	@ManyToOne
	@JoinColumn(name = "TEAM_ID")
	private Team team;

	//getter setter
}
@Entity
public class Team {

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

	private String name;

	// Member객체의 필드명 "team"을 맵핑
	@OneToMany(mappedBy = "team")
	private List<Member> members = new ArrayList<>();

	// getter setter
}
package hello.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

public class JpaMain {
	public static void main(String[] args) {
		// persistence.xml에 정의된 hello 설정정보 가져옴
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
		EntityManager em = emf.createEntityManager();

		EntityTransaction tx = em.getTransaction();
		tx.begin();
		try {
			Team team = new Team();
			team.setName("TeamA");
			em.persist(team);

			Member member = new Member();
			member.setUsername("member1");
			member.setTeam(team);
			em.persist(member);

			// 편의메소드 정의 Member.changeTeam(m){ .. 여기서 team.getMembers().add(this);... }
//			team.getMembers().add(member);
//			em.flush();
//			em.clear();

			// flush();clear(); 없을경우 밑에서 출력 안됨
			//1차 캐시 (team객체에 컬렉션 세팅안되어있음)
			Team findTeam = em.find(Team.class, team.getId());
			List<Member> members = findTeam.getMembers();
			for (Member m : members) {
				System.out.println("m=" + m.getUsername());
			}

			tx.commit();
		} catch(Exception e) {
			tx.rollback();
		} finally {
			em.close();
		}

		emf.close();
	}
}

jpashop

7. 고급 매핑

복합 키: 비식별 관계 매핑

@Entity
@IdClass(ParentId.class)
public class Parent{
	@Id
	@Column(name = "PARENT_ID1")
	private String id1;
	
	@Id
	@Column(name = "PARENT_ID2")
	private String id2;
}
public class ParentId implements Serializable {
	private String id1;
	private String id2;
	
	public ParentId(){}
	public ParentId(String id1, String id2){
		this.id1 = id1;
		this.id2 = id2;
	}
	@Override
	public boolean equals(Object o) {}
	@Override
	public int hashCode(){}
}
@Entity
public class Child {
	
	@Id
	private String id;
	
	@ManyToOne
	@JoinColumns({
			@JoinColumn(name="PARENT_ID1", referencedColumnName="PARENT_ID1"),
			@JoinColumn(name="PARENT_ID2", referencedColumnName="PARENT_ID2")
	})
	private Parent parent;
}
@Entity
public class Parent {
	
	@EmbeddedId
	private ParentId id;
	
	private String name;
}
@Embeddable
public class ParentId implements Serializable {
	@Column(name="PARENT_ID1")
	private String id1;
	@Column(name="PARENT_ID2")
	private String id2;
	// equals, hashCode 구현
}
@Entity
public class Child {
	@Id
	private String id;
	
	@ManyToOne
	@JoinColumns({
			@JoinColumn(name="PARENT_ID1", referencedColumnName="PARENT_ID1"),
			@JoinColumn(name="PARENT_ID2", referencedColumnName="PARENT_ID2")
	})
	private Parent parent;
}
public class JpaMain {
	public static void main(String[] args) {
		// emf, em, tx
		tx.begin();
		try {
			Parent parent = new Parent();
			
			ParentId parentId = new ParentId("myId1", "myId2");
			parent.setId(parentId);
			parent.setName("parentName");

			em.persist(parent);
			tx.commit();
		} catch(Exception e) {
			tx.rollback();
		} finally {
			em.close();
		}
		emf.close();
	}
}
public class JpaMain {
	public static void main(String[] args) {
		// emf, em, tx 생성
		try {
			ParentId parentId = new ParentId("myId1", "myId2");
			Parent parent = em.find(Parent.class, parentId);
			
		} catch(Exception e) {
		} finally {
			em.close();
		}
		emf.close();
	}
}

복합 키: 식별 관계 매핑

// 부모
@Entity
public class Parent {
	@Id
	@Column(name = "PARENT_ID")
	private String id;
	private String name;
}

// 자식
@Entity
@IdClass(ChildId.clas)
public class Child {
	@Id
	@ManyToOne
	@JoinColumn(name = "PARENT_ID")
	private Parent parent;

	@Id
	@Column(name = "CHILD_ID")
	private String childId;
	
	private String name;
}

// 자식 ID
public class ChildId implements Serializable {
	private String parent; // Child.parent 매핑
	private String childId; // Child.childId 매핑
	// equals, hashCode
}

// 손자
@Entity
@IdClass(GrandChildId.class)
public class GrandChild {
	@Id
	@ManyToOne
	@JoinColumns({
			@JoinColumn(name = "PARENT_ID"),
			@JoinColumn(name = "CHILD_ID")
	})
	private Child child;
	
	@Id
	@Column(name="GRANDCHILD_ID")
	private String id;
	
	private String name;
}

// 손자 ID
public class GrandChildId implements Serializable {
	private ChildId child; // GrandChild.child 매핑
	private String id; // GrandChild.id 매핑
	
	// equals, hashCode
}
// 부모
@Entity
public class Parent {
	@Id
	@Column(name = "PARENT_ID")
	private String id;

	private String name;
}

// 자식
@Entity
public class Child {
	@EmbeddedId
	private ChildId id;

	@MapsId("parentId") // ChildId.parentId 매핑
	@ManyToOne
	@JoinColumn(name = "PARENT_ID")
	private Parent parent;

	private String name;
}

// 자식 ID
@Embeddable
public class ChildId implements Serializable {
	private String parentId; // @MapsId("parentId")로 매핑

	@Column(name = "CHILD_ID")
	private String id;

	// equals, hashCode
}

// 손자
@Entity
public class GrandChild {

	@EmbeddedId
	private GrandChild id;

	@MapsId("childId") // GrandChildId.childId 매핑
	@ManyToOne
	@JoinColumns({
			@JoinColumn(name = "PARENT_ID"),
			@JoinColumn(name = "CHILD_ID")
	})
	private Child child;

	private String name;
}

// 손자 ID
@Embeddable
public class GrandChildId implements Serializable {
	private ChildId childId; // @MapsId("childId")로 매핑
	
	@Column(name = "GRANDCHILD_ID")
	private String id;
	
	// equals, hashCode
}
@Entity
public class Board {
	@Id @GneratedValue
	@Column(name = "BOARD_ID")
	private Long id;
	
	private String title;
	
	@OneToOne(mappedBy = "board")
	private BoardDetail boardDetail;
}
@Entity
public class BoardDetail {
	
	@Id
	private Long boardId;
	
	@MapsId // BoardDetail.boardId 매핑
	@OneToOne
	@JoinColumn(name = "BOARD_ID")
	private Board board;
	
	private String content;
}
public void save(){
	Board board = new Board();
	board.setTitle("제목1");
	em.persist(board);
	
	BoardDetail boardDetail = new BoardDetail();
	boardDetail.setDetail("내용1");
	boardDetail.setBoard(board);
	em.persist(boardDetail);
}

8. 프록시와 연관관계 관리

  1. 프록시 객체에 member.getName() 호출해서 실제 데이터 조회
    • em.getReference(Member.class, "id1"); 로 반환한 MemberProxy로 getName() 호출
    • ‘식별자’ getId() 호출시에는 초기화 되지 않음! 프록시객체는 이미 식별자를 가지고 있기 때문 - @Access(AccessType.FIELD)로 설정 시, 초기화 함 (getId()가 id만 조회하는 메소드인지 알 수 없으므로) - 연관관계 설정 할 때는, 식별자 값만 사용하므로, 프록시를 사용하면, 데이터베이스 접근 횟수를 줄일 수 있음. - 연관관계 설정 할 때는, 엔티티 접근방식을 FIELD로 설정해도, 엔티티 초기화 하지 않음.
  2. 실제 엔티티가 생성되어 있지 않으면, 영속성 컨텍스트에 실제 엔티티 생성 요청 (‘초기화’)
    • 이미 영속성 컨텍스트에 엔티티 있으면 데이터베이스 조회할 필요 없으므로, 프록시가 아닌 실제 엔티티 반환
  3. 영속성컨텍스트는 DB 조회하여 실제 엔티티 객체 생성
  4. 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버변수에 보관함
  5. 프록시 객체는 실제 엔티티 객체의 getName()을 호출하여 결과 반환.
public class JpaMain {
	public static void main(String[] args) {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
		EntityManager em = emf.createEntityManager();
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		try {
			Member member = new Member();
			member.setUsername("hello");
			
			em.persist(member);

			em.flush();
			em.clear();

//			Member findMember = em.find(Member.class, member.getId());
			Member findMember = em.getReference(Member.class, member.getId());
			System.out.println("findMember = " + findMember.getClass());
			System.out.println("findMember.id = " + findMember.getId());
			System.out.println("findMember.username = " + findMember.getUsername());

			tx.commit();
		} catch(Exception e) {
			tx.rollback();
		} finally {
			em.close();
		}
		emf.close();
	}
}
public class Member extends BaseEntity {
	// ...
	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "TEAM_ID")
	private Team team;
}
public class JpaMain {
	public static void main(String[] args) {
		// ...
		try {
			// 회원, 팀 조인 SQL 호출
			// @ManyToOne(fetch= fetchType.EAGER)
			// @JoinColumn(name="TEAM_ID", nullable=false) : 'INNER JOIN'
			// @JoinColumn(name="TEAM_ID") : 'OUTER JOIN'
			Member member = em.find(Member.class, "member1");
			Team team = member.getTeam(); // 객체 그래프 탐색 (로딩된 실제 team1 엔티티)

			tx.commit();
		} catch(Exception e) {
			tx.rollback();
		} finally {
			em.close();
		}
		emf.close();
	}
}
public class Member extends BaseEntity {
	// ...
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "TEAM_ID")
	private Team team;
}
public class JpaMain {
	public static void main(String[] args) {
		// ...
		try {
			Member member = em.find(Member.class, "member1"); // 회원 조회 SQL 호출
			Team team = member.getTeam(); // 객체 그래프 탐색 (프록시 객체)
			
			// 팀객체 실제 사용 전까지 DB 조회 안하다가 사용시 조회
			System.out.println("team.name = " + team.getName());
			tx.commit();
		} catch(Exception e) {
			tx.rollback();
		} finally {
			em.close();
		}
		emf.close();
	}
}
import javax.persistence.JoinColumn;

@Entity
public class Member {
	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name="TEAM_ID")
	private Team team;

	@OneToMany(mappedBy = "member", fetch = FetchType.LAZY)
	private List<Order> orders;
}
public enum CascadeType {
	ALL,
	PERSIST,
	MERGE,
	REMOVE,
	REFRESH,
	DETACH
}

@Entity
public class Parent {

	@OneToMany(mappedBy="parent", orphanRemoval= true)
	private List<Child> children = new ArrayList<Child>();
}

9. 값 타입

10. 객체지향 쿼리언어

public class JpaMain {
	public static void main(String[]args){
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
		EntityManager em = emf.createEntityManager();
		EntityTransaction tx = em.getTransaction();
		
		tx.begin();
		try{
			// JPQL만 사용 시 (별도 검색 방법 X)
			String jpql="select m from Member as m where m.username = 'kim'";
			List<Member> list = em.createQuery(jpql, Member.class).getResultList();
			
			// QueryDSL 사용
			JPAQuery query = new JPAQuery(em);
			QMember member = QMember.member;
			
			List<Member> members = query.from(member)
					.where(member.username.eq("kim"))
					.list(member);
			
			// Native SQL 사용
			String sql = "SELECT ID, AGE, TEAM_ID NAME FROM MEMBER WHERE NAME = 'kim'";
			List<Member> resultList = em.createNativeQuery(sql, Member.class).getResultList();

			// JDBC 직접사용, 마이바티스 같은 매퍼 프레임워크 사용 
			// JDBC나 마이바티스를 JPA와 같이 사용 하면, 영속성컨텍스트를 적절한 시점에 강제 플러쉬 해야한다
			// JDBC든 마이바티스 같은 SQL 매퍼를 사용하면, JPA를 우회해서 데이터베이스에 접근
			// JPA 우회하는 SQL을 JPA가 인식하지 못하기 때문에,
			// 영속성컨텍스트와, 데이터베이스 불일치 -> 데이터 무결성 훼손
			// e.g. JPA로 100->900 업뎃 후 flush하지 않은 상태에서, JPA 우회 조회 시, 900이 아닌 1000으로 조회
			// => JPA 우회해서 SQL 실행 직전에, 영속성컨텍스트를 수동으로 flush하여
			// 		데이터베이스와 영속성컨텍스트를 동기화
			// => AOP 활용하여 JPA 우회 SQL 실행하기 직전에 영속성컨텍스트를 수동으로 flush 해주는 방법이 있음!
			Session session = em.unwrap(Session.class);
			session.doWork(new Work() {
				@Override
				public void execute(Connection c) throws SQLException {
					// work...
				}
			});
			
			
		} catch(Exception e) {
			tx.rollback();
		} fianlly {
			em.close();
		}
		emf.close();
	}
}
public class JpaMain {
	public static void main(String[] args) {
		// ... emf, em, tx 생성

		// 타입쿼리
		TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
		List<Member> resultList = query.getResultList();
		for (Member member : resultList) {
			System.out.Println("member = " + member);
		}

		// 쿼리: select 절 조회 대상개수에 따라 리스트로 담김
		Query query = em.createQuery("SELECT m.username from Member m");
		List resultList = query.getResultList();
		for (Object o : resultList) {
			Object[] result = (Object[]) o;
			System.out.Println("username = " + result[0]);
			System.out.Println("age = " + result[1]);
		}

		// 이름 기준 파라미터 바인딩
		String usernameParam = "User1";
		TypedQuery<Member> query = em.createQuery("select m from Member m where m.username= :username", Member.class);
		query.setParameter("username", usernameParam);
		List<Member> resultList = query.getResultList();

		// 위치 기준 파라미터 바인딩
		List<Member> members =
				em.createQuery("select m from Member m where m.username = ?1", Member.class)
						.setParameter(1, usernameParam)
						.getResultList();

		// 임베디드 타입 프로젝션
		String query = "select o.address from Order o";
		List<Address> addresses = em.createQuery(query, Address.class)
				.getResultList();

		// 스칼라 타입 프로젝션 (숫자, 문자, 날짜 조회 또는 통계용쿼리)
		List<String> usernames = em.createQuery("select username from Member m", String.class)
				.getResultList();

		// 여러 값 조회
		// 프로젝션에 여러값을 선택하면 TypeQuery를 사용 할 수 없고, Query를 사용 해야 함
		Query query = em.createQuery("select m.username, m.age from Member m");
		List resultList = query.getResultList();
		Iterator iterator = resultList.iterator();
		while (iterator.hasNext()) {
			Object[] row = (Object[]) iterator.next();
			String username = (String) row[0];
			Integer age = (Integer) row[1];
		}

		// 여러 값 조회 - 제네릭이 Object[] 사용하여 간결화
		List<Object[]> resultList = em.createQuery("select m.username, m.age from Member m")
				.getResultList();
		for (Object[] row : resultList) {
			String username = (String) row[0];
			Integer age = (Integer) row[1];
		}

		List<Object[]> resultList = em.createQuery("select o.member, o.product, o.orderAmount from Order o")
				.getResultList();
		for (Object[] row : resultList) {
			Member member = (Member) row[0]; // 엔티티
			Product product = (Product) row[1]; // 엔티티
			int orderAmount = row[2]; // 스칼라

//			String username = (String) row[0];
//			Integer age = (Integer) row[1];
		}

		// NEW 명령어 미사용
		List<Object[]> resultList = em.createQuery("select m.username, m.age from Member m");
		List<UserDTO> userDtos = new ArrayList<UserDTO>();
		for (Object[] row : resultList) {
			UserDTO userDto = new UserDTO((String) row[0], (Integer) row[1]);
			userDTOs.add(userDto);
		}

		// NEW 명령어 사용 : Object[] -> DTO
		TypedQuery<User> query = em.createQuery(
				"select new jpabook.jpashop.model.UserDTO(m.username, m.age)" +
						"from Member m", UserDTO.class);
		List<UserDTO> resultList = query.getResultList();
		
		
		// 내부조인
		String teamName = "팀A";
		String jpql = "select m from Member m INNER JOIN m.team t where t.name = :teamName";
		List<Member> members = em.createQuery(jpql, Member.class)
				.setParameter("teamName", teamName)
				.getResultList();
		jpql = "select m, t from Member m INNER JOIN m.team t";
		List<Object[]> result = em.createQuery(query).getResultList();
		for (Object[] row : resultList) {r
			Member member = (Member) row[0];
			Team team = (Team) row[1];
		}
		
		// 외부조인
		jpql = "select m from Member m LEFT OUTER JOIN m.team t";
		List<Member> members = em.createQuery(jpql, Member.class).getResultList();
		
		// 컬렉션조인
		// 일대다, 다대다 관계처럼 컬렉션을 사용하는 곳에 조인
		// 	회원->팀 조인은 다대일 관계 이면서 단일 값 연관필드 m.team 사용
		// 	팀->회원 조인은 일대다 관계 이면서 컬렉션 값 연관필드 m.members 사용
		jpql = "select t, m from Team t LEFT JOIN t.members m";
		
		// 세타조인
		// 전혀 관계 없는 엔티티 조인
		// Member.username 과 Team.name 은 전혀 관계가 없음.
		jpql = "select count(m) from Member m, Team t where m.username = t.name";
		
		// JOIN ON 절 (JPA 2.1)
		// 내부조인의 ON은 where절에 쓰는것과 같으므로, 외부조인에서만 사용함.
		jpql = "select m, t from Member m LEFT JOIN m.team t ON t.name = 'A'";
		jpql = "select m.*, t.* from Member m LEFT JOIN Team t ON m.TEAM_ID = t.id and t.name = 'A'";
		
		// 패치조인
		// SQL 조인종류가 아닌, 연관된 엔티티나 컬렉션을 한번에 같이 조회하는 JPQL 기능
		//   조인하는 테이블은 별칭 사용 X
		// [LEFT [OUTER] | INNER] JOIN FETCH 조인경로
		//  `select m` 또는 `select t`를 하더라도 join fetch 로 연관된 엔티티도 같이 조회 함
		// 	엔티티 패치조인
		//	select M.*, T.* FROM MEMBER M INNER JOIN TEAM T ON M.TEAM_ID = T.ID
		
		// 회원과 팀을 지연로딩 설정 했을때, 회원을 조회할 때 패치조인으로 팀을 함께 조회 했으므로
		// 팀 엔티티는 프록시가 아닌 실제 엔티티다. 연관된 팀을 사용해도 지연 로딩이 일어나지 않는다.
		// 프록시가 아닌 실제 엔티티이므로 회원 엔티티가 영속성 컨텍스트에서 분리되어 준영속 상태가 되어도 연관된 팀을 조회할 수 있다.
		jpql = "select m from Member m JOIN FETCH m.team";
		List<Member> members = em.createQuery(jpql, Member.class)
				.getResultList();
		for (Member member : members) {
			System.out.println("username = " + member.getUsername());
			System.out.println("teamname = " + member.getTeam().getName());
		}
		// 	컬렉션 패치조인
		//	select T.*, M.* from Team T INNER JOIN Member M ON T.ID = M.team_id where T.id = '팀A'
		jpql = "select t from Team t JOIN FETCH t.members where t.name = '팀A'";
		List<Team> teams = em.createQuery(jpql, Team.class).getResultList();
		for (Team team : teams) {
			System.out.println("teamname [" + team.getName() + "] : " + team); 
			for (Member m : team.getMembers()) {
				System.out.println("username [" + m.getUsername() + "] : " + m);
			}
		}
		
		// 패치조인과 DISTINCT : 쿼리에 distinct를 추가하고, 애플리케이션에서 한번더 거름
		// SQL에서는 distinct t 를 하더라도 결과 로우데이터에 팀별 회원 데이터는 전부 다르므로 distinct 효과가 없음
		// 애플리케이션 단에서, 같은 팀이므로 걸러짐 -> 최종 1개팀만 조회됨
		jpql = "select distinct t from Team t join fetch t.members where t.name = '팀A'";
		List<Team> teams = em.createQuery(jpql, Team.class).getResultList();
		for (Team t : teams) {
			System.out.println("team = " + team);
		}
		
		// 패치조인 vs. 일반조인 차이
		// 	일반 INNER 조인의 경우, `select t` 는 팀만 조회함. 연관된 Member는 조회 X
		//		회원 엔티티를 지연로딩 설정하면, 프록시나 아직 초기화 되지 않은 컬렉션 래퍼를 반환!
		//		즉시로딩 설정하면, 회원 컬렉션을 즉시로딩 하기 위해 쿼리를 한번 더 실행.
		// 	패치 조인의 경우, `select t`는 팀과 연관된 멤버도 같이 조회
		// JPQL은 반환할 때 연관관계 까지 고려하지 않음. SELECT에 지정한 엔티티만 조회!
		
		// 패치조인의 한계
		// 글로벌 로딩전략을 LAZY 지연로딩 설정 하고
		// 필요시 JPQL에서 패치조인을 사용하여, 패치조인을 적용해서 함께 조회 하는 방식 권장
		// 패치조인 대상에는 별칭을 줄 수 없음
		// 둘 이상의 컬렉션을 패치 할 수 없음.
		// 컬렉션을 패치조인 하면, 페이징 API 사용 X
		
		// 경로 표현식
		// 	상태 필드 : select m.username, m.age from Member m
		// 	단일값 연관 필드 : 묵시적 내부조인 발생 (단일값 연관 경로는 계속 탐색 할 수 있다)
		/** jpql : select o.member.team from Order o where o.product.name = 'productA' and o.address.city = 'JINJU'
				SQL : select t.*
							from Order o
							inner join Member m on o.member_id = m.id
							inner join Team t on m.team_id = t.id
							inner join Product p on o.product_id = p.id
							where p.name = 'productA' and o.city = 'JINJU'
		*/
		// 	컬렉션 값 연관 필드 m.orders : 묵시적 내부조인 발생
		//		select t.members.username from Team t // 실패!
		//		select m.username from Team t join t.members m // 성공
		//		컬렉션 사이즈
		//		select t.members.size from Team t
		
		// 서브쿼리
		//	WHERE, HAVING 절에서만 사용가능, SELECT, FROM절에서는 사용불가
		//	select m from Member m where m.age > (select avg(m2.age) from Member m2)
		//	select m from Member m where (select count(o) from Order o where m = o.member) > 0
		//	서브쿼리 없이도 가능: select m from Member m where m.orders.size > 0
		
		// 서브쿼리함수
		// [NOT] EXISTS
		// {ALL | ANY | SOME} 서브쿼리 : ALL 조건을 모두 만족하면, ANY/SOME 조건을 하나라도 만족하면 참
		// select o.orderAmount from Order o where o.orderAmount > ALL (select p.stockAmount from Product p)
		// select m from Member m where m.team = ANY (select t from Team t)
		
		// IN
		// select t from Team t where t IN (select t2 from Team t2 JOIN t2.members m2 where m2.age >= 20)
		
		// 조건식
		// 타입 표현 (문자, 숫자, 날짜, Boolean, Enum, 엔티티)
		// 연산자 우선순위
		//		경로탐색
		//		수학
		//		비교
		//		논리
		
		// 논리연산 AND OR NOT
		// 비교식 = != < > >= <>
		
		// BETWEEN, IN, LIKE, NULL 비교
		
		// 컬렉션 식
		//	빈 컬렉션 비교 식
		//	{컬렉션값 연관 경로} IS [NOT] EMPTY
		//	컬렉션 멤버 식
		//	{엔티티나 값} [NOT] MEMBER [OF] {컬렉션 연관경로}
		
		// 스칼라 식
		//	수학 식(+1, -1, +-*/사칙연산) , 문자함수(CONCAT, SUBSTRING,...), 수학함수(ABS, SQRT, ...), 날짜함수(CURENT_DATE, ...)
		
		// CASE 식
		//	기본 CASE
		jpql = "select case when m.age <=10 then '학생요금' when m.age >= 60 then '경로요금' else '일반요금' end from Member m";
		//	심플 CASE
		jpql = "select case t.name when '팀A' then '인센티브110%' when '팀B' then '인센티브120%' else '인센티브105%' from Team t";
		//	COALESCE : 스칼라식 차례로 조회 해서 null이 아니면 반환
		jpql = "select colaesce(m.username, '이름없는회원') from Member m";
		//	NULLIF : 두값이 같으면 null 반환. 다르면 첫번째 값 반환
		jpql = "select nullif(m.username, '관리자') from Member m";
	}
}
public class UserDTO {
	private String username;
	private int age;

	public UserDTO(String username, int age) {
		this.username = username;
		this.age = age;
	}
}
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="DTYPE")
public abstract class Item {
}

@Entity
@DiscriminatorValue("B")
public class Book extends Item {
	private String author;
}
public class JpaMain {
	public static void main(String[]args){
		String jpql = "select i from Item i";
		
		// TYPE
		// 	Item의 자식도 함께 조회 됨
		List resultList = em.createQuery(jpql).getResultList();
		jpql = "select i from Item i where type(i) IN (Book, Movie)";
		// SQL : select i from Item i where i.DTYPE ('B', 'M');
		
		// TREAT (타입캐스팅)
		jpql = "select i from Item i where treat(i as Book).author = 'kim'";
		// SQL : select i.* from Item i where i.DTYPE = 'B' and i.author = 'kim'
		
		// 사용자 정의 함수 호출 (JPA 2.1)
		
		// 기본키값
    //  count(m.id)
    jpql = "select count(m) from Member m";
		//  m.id = ?
		jpql = "select m from Member m where m = :member";
		List resultList = em.createQuery(jpql)
        .setParameter("member", member)
        .getResultList();
		
		// 외래키값
		Team team = em.find(Team.class, 1L);
		//  m.team_id = ?
		jpql = "select m from Member m where m.team = :team";
		List resultList = em.createQuery(jpql)
				.setParameter("team", team)
				.getResultList();
	}
}