jpa.persist(member);
Member member = jpa.find(memmberId);
member.setName("변경힐 이름");
jpa.remove(member);
src > main > resources > META-INF 디렉토리 > persistence.xml 생성
<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;
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();
INSERT INTO Member (...)
<property name="hibernate.hbm2ddl.auto" value="create" />
data varchar(255) not null
alter table Tablename add constraint UK_Xxx unique (username)
EMPTY
”)data varchar(400)
cal numeric(10,2)
// H2, PostgresSQLcal number(10,2)
// 오라클cal decimal(10,2)
// MySQL// 유니크 제약조건 이름정의
@Entity(name = "Member")
@Table(name = "MEMBER", uniqueConstraints = {@UniqueConstraint(
name = "NAME_AGE_UNIQUE",
columnNames = {"NAME", "AGE"},
)}
)
public class Member{
}
@SequenceGenerator(name="MEMEBER_SEQ_GENERATOR", sequenceName="MEMBER_SEQ",initialValue=1, allocationSize=1)
- call next value for MEMBER_SEQ로 seq 값을 받아와서 INSERT할때 MEMBER.ID에 넣어줌
-
name : 컬럼에서
@GeneratedValue(…, generator=”MEMBER_SEQ_GENERATOR”)
-
sequenceName : 매핑할 데이터베이스 시퀀스 이름
-
allocationSize=50 : DB에 50->100->.. 단위로 생성 하고, 메모리에서는 1씩
- **필드에 추가** : @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="MEMBER_SEQ_GENERATOR")
- 매핑할 DDL:
create sequence [sequenceName] start with [initialValue] increment by [allocationSize]`@TableGenerator(name = "...", table = "MY_SEQUENCES", pkColumnValue = “MEMBER_SEQ", allocationSize = 1)
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);
연관관계의 주인(Owner)
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
}
Team 클래스에 private List<Member> members;
Member
가 외래키 들고있음)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();
}
}
@OneToOne @JoinColumn(name = "LOCKER_ID") private Locker locker;
데이터 중요도 낮고 적은 데이터면 SINGLE_TABLE
@MappedSuperclass
@AttributeOveride
(name="", column=@Column(name="MEMBER_ID"))
@AssociationOverride
기본키 + 외래키
로 사용
외래키
로 사용@Id
를 2개이상 만들려면 별도의 식별자 클래스 만들어야 함.@IdClass
: 관계형 데이터베이스에 맞춘 방법@EmbeddedId
: 객체 지향적 방법
@IdClass
: 관계형 데이터베이스에 맞춘 방법@Entity
@IdClass(ParentId.class)
public class Parent{
@Id
@Column(name = "PARENT_ID1")
private String id1;
@Id
@Column(name = "PARENT_ID2")
private String 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;
}
@EmbeddedId
: 객체 지향적 방법
@Embeddable
어노테이션 붙여야 함@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;
}
@Embeddable
: 생성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();
}
}
@Embeddable
: 조회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();
}
}
equals()
, hashCode()
equals()
, hashCode()
사용@IdClass
vs. @Embeddable
@IdClass
는 JPQL이 길어질 수 있음@IdClass
와 식별 관계// 부모
@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
}
@EmbeddedId
와 식별 관계// 부모
@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);
}
엔티티 하나에 여러테이블 매핑
em.find()
조회한 엔티티를 사용하는 여부와 상관 없이, DB통해서 실제 엔티티 객체 조회em.getReference()
엔티티를 실제 사용 하는 시점까지 DB 조회를 미룸
- 데이터 접근을 위임한 프록시 (가짜) 엔티티객체 반환 (DB 조회 X, 엔티티 객체 생성 X)
Proxy { Entity target=null, getId(), getName()}
member.getName()
처럼 ‘실제 사용’될 때, DB 조회해서 실제 엔티티 객체 생성member.getName()
호출해서 실제 데이터 조회
em.getReference(Member.class, "id1");
로 반환한 MemberProxy로 getName()
호출@Access(AccessType.FIELD)
로 설정 시, 초기화 함 (getId()가 id만 조회하는 메소드인지 알 수 없으므로)
- 연관관계 설정 할 때는, 식별자 값만 사용하므로, 프록시를 사용하면, 데이터베이스 접근 횟수를 줄일 수 있음.
- 연관관계 설정 할 때는, 엔티티 접근방식을 FIELD로 설정해도, 엔티티 초기화 하지 않음.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();
}
}
member.getClass().getName()
jpabook.domain.Member_$$_javasist_0
em.find(Member.class, "member1");
호출시@ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name="TEAM_ID") Team team;
member.getTeam().getName()
처럼 조회한 팀 엔티티를 실제 사용하는 시점에 JPA가 SQL을 호출해서 팀 엔티티 조회@ManyToOne(fetch = FetchType.LAZY)
em.find(Member.class, "member1")
호출 시 DB에서 멤버만 조회Team team = member.getTeam()
팀은 DB에서 조회 하지 않고, team 멤버변수에 프록시 객체를 넣어둔다team.getName()
팀 객체 실제 사용 할 때, DB 조회하여 프록시 객체 초기화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;
}
(optional = true)
외부(optional = false)
내부 조인(optional = false)
외부 조인(optional = true)
외부 조인Parent{ @OneToMany(mappeBy = "parent") List<Child> children= new(); }
Child{ @ManyToOne Parent parent;}
c.setParent(p);
p.getChildren().add(c);
em.persist(parent);
em.pesist(c1);
em.persist(c2);
Parent{ @OneToMany(mappedBy = "parent", cascade= CascadeType.PERSIST) List<Child> children= new(); }
Child{ @ManyToOne Parent parent;}
c.setParent(p);
p.getChildren().add(c);
em.persist(parent);
em.remove(c1);
- em.remove(c2);
- em.remove(p);
CascadeType.REMOVE
em.remove(p);
cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
public enum CascadeType {
ALL,
PERSIST,
MERGE,
REMOVE,
REFRESH,
DETACH
}
@OneToOne
, @OneToMany
일때만 적용 가능.parent.getChildren().clear()
parent.addChild(child1);
parent.getChildren().remove(child2);
@Entity
public class Parent {
@OneToMany(mappedBy="parent", orphanRemoval= true)
private List<Child> children = new ArrayList<Child>();
}
@Embedded Period workPeriod
-> @Embeddable public class Period {}
@Embedded Address homeAddress
-> @Embeddable public class Address {}
Member {id:Long, name:String, workPeriod:Period, homeAddress:Address}
@Embedded Address homeAddress @AttributeOverrides({...})
임베디드 타입과 null : Address 필드가 null이면 그 하위 필드 들도 null (e.g. Address.street == null)
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();
}
}
getResultList()
(컬렉션반환, 없으면 빈컬렉션)getSingleResult()
(결과가 0이거나 1보다 많으면 에러 발생)
[select {프로젝션대상} from]
select m from Member m
select m.team from Member m
select m.name, AVG(m.age), COUNT(m) from Member m GROUP BY m.name HAVING AVG(m,age) >= 10
select m.name, m.age from Member m order by m.age ASC, m.username ASC
select m from Member m INNER JOIN m.team t where t.name = :teamname
select m from Member m LEFT OUTER JOIN m.team t
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();
}
}
em.createQuery("select ...");
Named 쿼리를 어노테이션에 정의
Criteria