강좌 소개
강좌 소개
객체와 테이블 맵핑을 정확히 해야됨.
JPA 소개
JPA 소개
지연 로딩 전략
JPA 시작하기
Hello JPA - 프로젝트 생성
Hello JPA - 애플리케이션 개발
구동방식
객체 테이블 매핑
디비 테이블 생성 쿼리
클래스 생성
@Id = PK
맴버저장
엔티티 매니저 펙토리는 로딩시점에 1개만 만듬.
트랜잭션 단위
고객의 행위(장바구니, 등등) 때마다 엔티티 매니저를 작성.
ids for this class must be manually assigned
트랜잭션단위를 해줘야됨.
쿼리가 나감
persistence.xml
- hibernate.show_sql = 쿼리
- hibernate.format_sql = 포멧
- hibernate.use_sql_comments = 코멘트
@Table(name = "테이블이름")
soutv
System.out.println(이름 + 객체)
저장할 필요 없음
엔티티 매니저는 버려랴. 공유하지마
JPA는 절대 테이블 대상으로 쿼리를 짜지 않는다.
결국 SQL
그러나 JPQL은 엔티티 객체를 대상
정리
영속성 관리 - 내부 동작 방식
영속성 컨텍스트 2
1차 캐시
//비영속
Member member = new Member();
member.setId(101L);
member.setName("HelloJPA");
//영속
System.out.println("=== BEFORE ===");
em.persist(member);
System.out.println("=== AFTER ===");
Member findMember = em.find(Member.class, 101L);
System.out.println("findMember.getId() = " + findMember.getId());
System.out.println("findMember.getName() = " + findMember.getName());
//영속
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
쿼리가 한번만 나감
동일성 보장
System.out.println("result = " + (findMember2 == findMember1));
엔티티 등록
생성자 오류가 나는 이유
JPA가 생성할수 있는 기본 생성자를 만들어줘야됨
//영속
Member member1 = new Member(150L, "A");
Member member2 = new Member(160L, "B");
em.persist(member1);
em.persist(member2);
System.out.println("============================");
버퍼링의 기능
최적화의 여지
JDBC Batch, 사이즈만큼 모아서 보냄
<property name="hibernate.jdbc.batch_size" value="10"/>
옵션을 적용해두면 기본적으로 성능을 먹고 들어감.
내가 그걸 만든다?
변경 감지
//영속
Member member = em.find(Member.class, 150L);
member.setName("ZZZZZ");
너무 신기해
엔티티와 스냅샷 비교
업데이트는 왠만하면 호출하지말자.
if(member.getName().equals("ZZZZZ")){
em.persist(member);
}
혹시라도 잘못 짤수 있으니 손대지 말자.
플러시
준영속 상태
정리
영속성 컨텍스트
동작하는 메커니즘
엔티티 매핑
데이터베이스 스키마 자동 생성
필드와 컬럼 매핑
@Enumerated 디비의 이넘타입
@Temporal 생성일자 수정일자.
@Lob 큰 컨텐츠
정리
nullable
uniqueConstraints
precision, scale 큰숫자, 소수점
@Enumerated STRING 만써라.
Member member = new Member();
member.setId(1L);
member.setUsername("A");
member.setRoleType(RoleType.USER);
em.persist(member);
@Temporal 필요 없음.
localDate, LocalDateTime 을 쓰세요.
private LocalDate testLocalDate;
private LocalDateTime testLocalDateTime;
@Lob
-clob String, char[], java.sql.CLOB
-blob byte[], java.sql BLOB
기본 키 매핑
@Id @GeneratedValue
사실 별로 할수 있는게 없어요.
오라클-시퀀스
그외 - 오토인크리먼트
strategy
AUTO 방언
ITENTITY 데이터베이스의 위임
int 는 안되고 Integer(10억) 도 애매함
Long을 써야됨
거의 영향을 안줌
오류남
<property name="hibernate.hbm2ddl.auto" value="none" />
그래서 이렇게 변경후 확인
Member member = new Member();
member.setUsername("C");
Member member1 = new Member();
member1.setUsername("D");
em.persist(member);
em.persist(member1);
@SequenceGenerator
TABLE 전략 = 키 생성 전용 테이블
Auto-increment, sequence 이런거 통합해서
만들어주는 팩토리
오타 있음
@Entity
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "MEMBER_SEQ", allocationSize = 1)
“MEMBER_SEQ",
운영에서는 디비에 설정 되있는것을 쓰는것이 맞다.
initialValue, allocationSize = 성능 최적화와 연관됨.
권장하는 전략
Long형 + 대체키 + 키 생성전략 사용.
AutoIncrement아니면 SeqenceObject
UUID 아니면 랜덤값
IDENTITY 전략
뭐가 문제냐 순서가 문제다.
DB에 들어가야 키를 확인 가능함.
근데 영속성을 관리하려면 키가 필요함.
그래서 persist를 하자마자 insert 쿼리를 날림
JDBC 드라이버에 select를 안해도 id를 받는 로직이 있다.
@Entity
@SequenceGenerator(
name="MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ", // 매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
public class Member {
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
성능을 위해 allocationSize
미리 Size 만큼 DB에 50 번을 예약해두고 쓰고
50을 다쓰면 100으로 올려두고 쓰고.
Member member1 = new Member();
member1.setUsername("A");
Member member2 = new Member();
member2.setUsername("B");
Member member3 = new Member();
member3.setUsername("C");
System.out.println("============================");
em.persist(member1);
em.persist(member2);
em.persist(member3);
System.out.println("member1.getId() = " + member1.getId());
System.out.println("member2.getId() = " + member2.getId());
System.out.println("member3.getId() = " + member3.getId());
System.out.println("============================");
호출하면 1이되길 기대하는
//DB SEQ = 1 | 1
//DB SEQ = 51 | 2
em.persist(member1); //DB SEQ = 1 | 1
em.persist(member2); //MEM
em.persist(member3); //MEM
서버를 올렷다 내리면 숫자 구멍들이 생기는데 딱히 별 상관 없다.
실전 예제 1 - 요구사항 분석과 기본 매핑
ORDERS 는 sql 쿼리에서 order 예약어를 사용하기 때문에 안된다.
회사마다 네이밍 룰이 다음.
보기 옵션 변경
가급적이면 여기에 적는걸 선호함.
개발자가 제약을 알수 있음.
@Table(uniqueConstraints = "", indexs=@index())
이거 다써야 개발자 들에게 전달 가능.
이름이 자유로우면 냅두고, length 같은거는 써주고
스프링부트를 쓰면 orderDate -> order_date
DBA는 ORDER_DATE, order_date 를 원함.
싸우지말고 맞춰 드려야됨.
핵심
Order order = em.find(Order.class, 1L);
Long memberId = order.getMemberId();
Member member = em.find(Member.class, memberId);
private Member member;
///
Member findMember = order.getMember();
문제점
이런걸 관계형 디비의 맞춤 설계라고함.
연관관계 매핑 기초
단방향 연관관계
객체 지향적인 설계
목표
객체의 참조 테이블의 외래키 매핑
연관관계의 주인 = jpa 계의 포인터
스승님
객체지향의 사실과 오해
오브젝트:코드로 이해하는 객체지향 설계
왜 봐야되냐면
예제 시나리오
select * from member m
join team t on m.team_id = t.team_id;
ANSI 표준 JOIN 문법을 써야 다른 데이터 베이스로 바꿔도 무리가 없음.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setTeamId(team.getId());
em.persist(member);
Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, findTeamId);
관계를 알려줘야됨
어노테이션 = 데이터베이스에게 알려주는 용도
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
매핑 그림
영속성 말고 디비의 쿼리를 보고 싶어.
em.flush();
em.clear();
@ManyToOne(fetch=LAZY)
//수정(왜래키 업데이트)
Team newTeam = em.find(Team.class, 100L);
findMember.setTeam(newTeam);
양방향 연관관계와 연관관계의 주인 1- 기본
이제부터 진짜
잘들어야되
테이블의 입장에서는 방향이 없음.
객체는 팀이 맴버를 소유해야됨
관례 : 널포인트 안뜨게 초기화
private List<Member> members = new ArrayList<>();
@OneToMany(mappedBy ="team")
중간에 플러시 클리어 중요함.
mappedBy 중요해
연결 리스트 구현의 느낌?
디비의 외래키는 1개인데
객체에서 변경이 일어나면 어디를 수정해야되는가.
디비는 맴버의 값만 업데이트 되면 문제가 없다.
두 객체에서는 하나의 객체가 왜래키를 관리해야된다.
주인을 정해야한다.
주인이 아닌쪽은 읽기만 가능.
왜래키가 있는 테이블의 객체가 주인이다.
문제
팀의 멤버를 수정했는데 멤버 (다른)테이블이 수정됨.
고민들이 해결됨
외래키가 있는곳이 N : 1 이다.
그럼 객체는 ManyToOne = OneToMany 이다.
그러면 많은 쪽이 주인이 된다.
큰 의미가 아니라 많은 쪽이라는 뜻이다.
양방향 연관관계와 연관관계의 주인 2 - 주의점, 정리
양방향 매핑시 가장 많이 하는 실수
team.getMembers().add(member); //추가 했다.
결과
Team team = new Team();
team.setName("TeamA");
// team.getMembers().add(member); //추가 했다.
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
em.persist(member);
결과
아...강의때 말한게
(고급) 답이 있습니다.
양쪽에 값을 넣어주는게 맞습니다.
객체 지향적으로는 양쪽에 값을 거는게 맞다.
리스트에 세팅한게 없지만 출력이됨
members를 사용하는 시점에 쿼리를 날려서 가져옴
문제가 되는 두가지 부분
클리어를 하면 문제가 없음.
find는 디비를 조회하는것이기 때문에
팀이 DB에 없기에 select 쿼리가 나갈수가 없음.
Team findTeam = em.find(Team.class, team.getId()); //1차 캐시
List<Member> members = findTeam.getMembers();
System.out.println("==========================");
for (Member m : members) {
System.out.println("m = " + m.getUsername());
}
System.out.println("==========================");
순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자.
= 테스트 케이스에서도 문제가 안됨.
member.setTeam(team); //**
em.persist(member);
team.getMembers().add(member); //**
연관 관계 편의 메소드를 생성.
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
상태 변경시 Set을 사용하지 말자.
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
C++ 처럼 null 체크도 해야되고 나를 빼면 Team 에서도 나를 제외해 줘야되고 하는데 깊이 있게 쓸일은 딱히 없다.
책에 나와 있다.
public void setTeam(Team team) {
this.team = team;
}
public void addMembers(Member member) {
member.setTeam(this);
members.add(member);
}
조심해야 될것은 양쪽 메서드가 같은 일을 하니까 문제가 생김
무한 루프
문자를 찍어주는 라이브러리들이 무한 호출함.
toString() 안의 요소들은 .toString()을 또 호출함.
컨트롤러 ->엔티티->Json->team->json->member->json->team->
toString(), lombok 왠만하면 쓰지말고
JSON은 컨트롤러에는 엔티티를 반환하지마
스프링이 알아서 JSON으로 반환해줌.
엔티티 = api 스펙이 되버림.
dto로 변환후 반환 하는것을 추천함
정리
단방향 매핑으로 끝내라!
실전 예제 2 - 연관관계 매핑 시작
@ManyToOne
@JoinColumn(name="ORDER_ID")
private Order order;
@ManyToOne
@JoinColumn(name="ITEM_ID")
private Item item;
주문 입장에서는 상품이 중요함
상품 입장에서는 그냥 수량만 빼주니 상관이 없음.
회원이 주문을 찾는다 => 주문들에서 회원을 찾는다. 관계성끊기
주문서에서 주문된 물건을 찾는다. 이상적
Order order = new Order();
order.addOrderItem(new OrderItem());
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.setOrder(this);
}
요런식으로
Member.orders,Order.orderItems는 없어도
아무 문제가 없음
이렇게 짜도 된다.
Order order = new Order();
order.addOrderItem(new OrderItem());
em.persist(order);
OrderItem orderItem = new OrderItem();
orderItem.setOrder(order);
em.persist(orderItem);
아~무 문제 없으니 일단 개발을 해라
개발상의 편의, 조회 밖에 없음
실전에서는 jpql 짜요. 그러면 욕구가 생김.
거의 잘못된 코드라 생각한다.
다양한 연관관계 매핑
일대다 [1:N]
이 모델은 권장하지 않음
세터를 잘 안써요. 생성자에서 다 해결되게 빌더를함.
Member member = new Member();
member.setUsername("member1");
em.persist(member);
Team team = new Team();
team.setName("teamA");
team.getMembers().add(member);
em.persist(team);
/* create one-to-many row hellojpa.Team.members */ update
Member
set
TEAM_ID=?
where
MEMBER_ID=?
실무에서 안쓰는이유
쿼리가 눈에 안보임.
@JoinColumn(name="TEAM_ID")
안넣으면 중간테이블이 추가됨.
@ManyToOne
@JoinColumn(insertable = false, updatable = false)
private Team team;
읽기 전용이됨.
한번씩 필요할때가 있음
일대일 [1:1]
일대일
외래 키에 유니크 제약 조건 추가
비즈니스 룰이 있어야됨.
@OneToOne
@JoinColumn(name="LOCKER_ID")
private Locker locker;
@OneToOne
@JoinColumn(name="LOCKER_ID")
private Locker locker;
일대일: 대상 테이블에 외래키 단방향
어떤 딜레마가 있냐면
나중에 1:다로 바뀌면 UNI 만 빼면됨
개발자 딜레마
맴버에 라커가 있는게 성능등 유리함
멤버가 select 문을 넣으면 락커가 조회됨.
저는 이 그림을 일단 가져감.
장점 : 주테이블 조회하면 대상테이블도 확인가능
단점 : 값이 없으면 왜래키에 null(치명적)
대상테이블
장점 : 전통적, null 방지
단점 : 양방향으로 만들어야되. 기본 프록시 기능이 한계가 있어서 지연 로딩이 즉시 로딩 밖에 안됨.
lock의 값이 있는지 없는지 where 문을 넣어야됨.
다대다 [N:M]
실전 예제 3 - 다양한 연관관계 매핑
Delivery 생성
Category 생성
부모자식
@ManyToOne
@JoinColumn(name="PARENT_ID")
private Category parent;
@OneToMany(mappedBy = "parent")
private List<Category> childCategory = new ArrayList<>();
1대1 - 오더(주) : 딜리버리
@OneToOne
@JoinColumn(name="DELIVERY_ID")
private Delivery delivery;
@OneToOne(mappedBy = "delivery")
private Order order;
다대다 - 카테고리 : 아이템
@ManyToMany
@JoinTable(name="CATEGORY_ITEM",
joinColumns = @JoinColumn(name="CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name="ITEM_ID")
)
private List<Item> items = new ArrayList<>();
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<>();
targetEntity = 제네릭으로 대체됨.
일대다는 mappedby 가 있음.
"다대일은 mappedby 가 없음" 이말은 연관관계 주인이 되야된다.
스펙상은 없다.
고급 매핑
상속관계 매핑
상속 관계 매핑
조인 전략
단일 테이블 전략
구현 클래스 마다 테이블 전략
객체 입장에서는 다 똑같음 = 프로그래밍 적으로는 같다.
코딩 시작
@Inheritance(strategy = InheritanceType.JOINED)
자식 테이블의 ID는 PK이면서 FK
Movie movie = new Movie();
movie.setDirector("aaaa");
movie.setActor("bbbb");
movie.setName("바람과 함께 사라지다.");
movie.setPrice(10000);
em.persist(movie);
em.flush();
em.clear();
Movie findMovie = em.find(Movie.class, movie.getId());
System.out.println("findMovie = " + findMovie);
SINGLE_TABLE 전략에서는 DTYPE이 꼭 필요하다.
DB에서 오류를 일으킨다면 @ManyToMany코드를 삭제해 보자.(member, memberProduct, product)
성능이 좋음. 삽입 쿼리 1번, 조회 쿼리 1번
없으면 구별이 안됨.
구현 클래스 마다 테이블 전략
public abstract class Item {
이렇게 만들어 줘야됨.
언제 망하냐면 조회를 하는데 부모클래스 타입으로 조회 할때..
Item item = em.find(Item.class, movie.getId());
다뒤짐
조인 전략
장점 : 정규화, 외래키 활용가능, 저장공간
단점(크게문제안됨) : 성능 저하, 쿼리 복잡, insert 2번
더큰 단점 : 복잡함
단일 테이블
장점 : 성능, 단순
단점 : 자식 컬럼 null 허용(치명적), 성능이 느려질수 있음.
임계점을 넘는일은 거의 없다.
구현 클래스 마다 테이블 전략
장단점 : 쓰지마
개발자, DBA 둘다 싫어함.
기본: 조인
그러나 단순하면 단일 테이블 선택함.
Mapped Superclass - 매핑 정보 상속
상속관계 맵핑이랑 관계가 없음
만약에 "많은 클래스"에
누가 몇시에 등록 했는지
누가 몇시에 수정 했는지
정보가 필요함
쫌 그래 할때 쓰는것
@MappedSuperclass
public class BaseEntity {
private String createBy;
private LocalDateTime createDate;
private String lastModifiedBy;
private LocalDateTime lastModifiedDate;
}
public class Team extends BaseEntity
Member member = new Member();
member.setUsername("user");
member.setCreateBy("kim");
member.setCreateDate(LocalDateTime.now());
em.persist(member);
몇몇 정보들은 다 이벤트라는 기능으로 자동화 가능하다.
저도 많이 씁니다.
abstract 클래스로 쓰는것을 권장
프록시와 연관관계 관리
프록시
어려움
왜 써야되는가
Member member = em.find(Member.class, 1L);
printMemberAndTeam(member);
private static void printMemberAndTeam(Member member) {
String username = member.getUsername();
System.out.println("username = " + username);
Team team = member.getTeam();
System.out.println("team = " + team.getName());
}
말로 설명 드릴게요.
한번에 다가져오면 좋자나요.
근데 상황이 바뀌었어요.
연관관계가 없는데 땡겨오면 손해.
어느때는 팀도 가져오고, 어느때는 맴버만 가져오고 싶다.
낭비를 줄이는 기능이 프록시다.
em.getReference(): 가짜 조회(DB X)
Member member = new Member();
member.setUsername("hello");
em.persist(member);
Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " + findMember.getUsername());
예제 정리
이거 말고
Member findMember = em.getReference(Member.class, member.getId());
쿼리가 나갔음.
레퍼런스가 실행될때는 쿼리를 안하고
사용 할때 쿼리가 실행된다.
프록시 설명
겉모양이 같음
그림 설명
2번 호출하면 1번만 쿼리 한다.
한번만 초기화됨.
target이 초기화되는것일뿐 프록시 객체는 유지
원본 엔티티를 상속받음. == 비교말고 instance of 를 사용해야 된다.
Member member1 = new Member();
member1.setUsername("hello1");
em.persist(member1);
Member member2 = new Member();
member2.setUsername("hello2");
em.persist(member2);
System.out.println("m1 == m2 " + (m1.getClass() == m2.getClass() ));
Member m2 = em.getReference(Member.class, member2.getId());
메소트 추출
어떻게 넘어갈지 예측이 안됨.
System.out.println("m1 == m2 " + (m1 instanceof Member));
System.out.println("m1 == m2 " + (m2 instanceof Member));
힌트
원본이 나오는 2가지 이유
1.이미 있던거 반환
2.(진짜)
2. 항상 트루가 되야된다.(JPA 정책상)
트랜잭션 안에서 같은 값이라고 보장해줘야됨.
하나더 설명(심화)
트루라는 정책이 중요한거지 같은 클래스나 다른 클래스 라는 개념이 아니다.
프록시는 한번 조회가 되면 em.find도 프록시를 반환함.
중요한거
개발할때 프록시든 아니든 개발하는게 중요.
실무에서 이렇게 복잡한거 안써요.
실무 진짜 많이 만나는 경우
em.close(); // 영속성 닫기
em.detach(refMember); // 영속성 끄집어 내기
could not initialize proxy [hellojpa.Member#1]
영속성 컨텍스트가 없다는 이야기.
트랜잭션 구간 이후에 프록시를 조회하려고 할때.
프록시 유틸리티 메소드
-프록시 인스턴스 초기화 여부
System.out.println("idLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
사용을 안했으니 target이 초기화 안됨.
사용했으니 true가 나옴.
-프록시 클래스 확인 방법
System.out.println("refMember = " + refMember.getClass());
JPA에는 강제 초기화가 없다.
그래서 getName을 호출한다.
거의 안써요. 그러나 이해를 해야 즉시,지연 로딩을 이해할수 있음.
즉시 로딩과 지연 로딩
똑같은 이야기(밑밥)
소리가 좀 작네
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(team);
em.persist(member1);
em.flush();
em.clear();
Member m = em.find(Member.class, member1.getId());
System.out.println("m = " + m.getTeam().getClass());
왜 안되지->DB에 값이 없네-> (읽기전용) 이었네
@JoinColumn(name="TEAM_ID",insertable = false, updatable = false)
@JoinColumn(name="TEAM_ID")
결국 연관된 것은 지연 로딩 한다는것.
Team 의 뭔가를 실제로 사용할때 로딩.
m.getTeam(); // 안됨
m.getTeam().getName(); // 초기화됨
반대로
계속 같이 쓴다면 2번씩 쿼리를 날리니 성능상 손해를 봄.
@ManyToOne(fetch = FetchType.EAGER)
System.out.println("===================");
System.out.println("team = " + m.getTeam().getName());
System.out.println("===================");
===================
team = teamA
===================
지금부터가 진짜 중요한 내용
즉시 로딩 쓰지마
N+1 설명
알고있던 객체를 JPA가 가져오는거기 때문에 최적화가 가능
JPQL 은 SQL로 번역
1번 쿼리후 없네? 2번 쿼리 준비.
맴버 쿼리1번, 팀 쿼리 2번
"select m from Member m join fetch m.team"
한방 쿼리
LAZY로 바르고 join fetch
지연 로딩의 활용 (이지만 이론적인거고)
다 LAZY로 발라라.
영속성 전이(CASCADE)와 고아 객체
소리가 작아짐
코딩
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
em.persist(child1);
em.persist(child2);
기차나
페어런트만 딱 하면 좋겟어
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();
그냥 이게 Cscade
ALL, PERSIST 정도만 씀
실무에서 많이 씁니다.
주의할점
게시판의 첨부파일(경로) = 충분히 가능
그러나 파일을 다른데서 관리한다 = 쓰면 안됨
라이프 싸이클이 똑같을때, 소유자가 하나 일때 사용하는것이 좋다.
고아객체 = 연관관계가 끊어지면 삭제됨
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
Parent findParent = em.find(Parent.class, parent.getId());
findParent.getChildList().remove(0);
함부러 쓰면 큰일나긴함.
참조하는곳이 하나일때 사용함.
소리 커짐
특정 엔티티가 개인 소유일때만 사용
둘다 키면 부모의 엔티티로 자식 엔티티를 관리가 가능하다. = DDD 의 Aggregate Root 개념을 구현할때 좋음.
값 타입
기본값 타입
임베디드 타입
임베디드 타입
편안하게 들으세요.
int나 String 처럼 값타입 = 추적안됨
중요함
@Embeddable
@Embedded
장점 : 재사용 가능, 응집도, 추적이 안되지만 생명주기를 의존
DB 입장은 같다.
기본생성자는 꼭 있어야됨 그래야 JPA가 객체를 사용할수 있음.
기본 생성자 protected 만들라고 했었나?
Member member = new Member();
member.setUsername("hello");
member.setHomeAddress(new Address("city", "street", "zipcode"));
member.setWorkPeriod(new Period());
em.persist(member);
임베디드 타입과 테이블 매핑
왜 좋냐면
매핑하는 테이블은 같다
활용하는 함수들을 내장 할수 있고 설계(모델링)이 깔끔하게 떨어진다.
공통 관리, 용어 공통화, 코드 공통화 장점이 많음.
임베디드 타입과 연관관계
workAddress 내부의 같은 필드값 때문에 에러남.
이때 쓰는게 @AttributeOverride
사용법 복사
@AttributeOverrides({
@AttributeOverride(name="city", column = @Column(name = "WORK_CITY")),
@AttributeOverride(name="street", column = @Column(name = "WORK_STREET")),
@AttributeOverride(name="zipcode", column = @Column(name = "WORK_ZIPCODE"))
})
주석이 음써요.
임베디드 타입과 null
값 타입과 불변 객체
쉬운거 같은데 깊이가 있다.
두루두루 도움이 된다.
임베디드 타입같은 엔티티끼리 값타입을 공유할수 있음
부작용 시연
단축키
문제 없네.
이건 뭐냐면 1번째 맴버의 주소만 set("newCity")으로 바꾸면 되겟네.
이런 버그는 못잡아요.
공유할때는 새로운 엔티티를 승격시켜야됨.
복사해서(딥카피) 사용하자.
Address address2 = new Address(address.getCity(), address.getStreet(), address.getZipcode());
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(address2);
em.persist(member2);
member.getHomeAddress().setCity("newCity");
결과
객체타입의 한계
사람이 대입하는것을 막을수 없다.
불변객체(immutable object)로 만들어서 원천 차단
-생성자로만 값으로 설정, 수정자(Setter)는 만들지 않는다.
그래도 값을 변경할수 있습니다.
변경이 아니라 교체 할수 있습니다.
Address newAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
member.setHomeAddress(newAddress);
값 타입은 다 불변으로 만드셔야 됩니다.
값 타입의 비교
값타입의 비교
자바는 객체 비교는 false로 나옴
int a= 10;
int b= 10;
System.out.println("a = b" + (a == b));
Address address1 = new Address("city", "street", "10000");
Address address2 = new Address("city", "street", "10000");
System.out.println("address1 == address2" + (address1 == address2));
동일성 비교, 동등성 비교
값타입은 equals를 재정의 해야됨
false 나오는게 맞지 재정의를 안했으니까
System.out.println("address1 equals address2" + (address1.equals(address2)));
equals를 재정의하는것은 자동으로 해주는것을 써야됩니다.
단축키
참고로 옵션을 사용해서 맴버 접근을 개터로 고쳐야 할수도 있다.
hashCode 도 구현해 주셔야 하는것도 아시죠?
그래야 JAVA 컬렉션에서 효율적으로 사용할수 잇죠.
정리
현업에서 정말 하나요?
요구 사항이 생기면 해요.
집주소랑 자택주소가 비교하는 것이 생긴다면
값 타입 컬렉션
값 타입을 컬렉션에 담아서 쓰는것
문제는 디비 테이블로 구현
값들을 다 묶어서 PK로 설정
@ElementCollection
@CollectionTable(name="FAVORITE_FOOD", joinColumns = @JoinColumn(name="MEMBER_ID"))
private Set<String> faroriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name="Address", joinColumns = @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();
컬럼이 한개라서
값타입을 하나이상 저장할때 사용.
@ElementCollection
@CollectionTable
무슨말이냐면
1:다 라서 테이블에 넣을 방법이 없다.
Member member = new Member();
member.setUsername("member1");
Address address = new Address("homeCity", "street", "10000");
member.setHomeAddress(address);
member.getFaroriteFoods().add("치킨");
member.getFaroriteFoods().add("족발");
member.getFaroriteFoods().add("피자");
member.getAddressHistory().add(new Address("old1", "street", "10000"));
member.getAddressHistory().add(new Address("old2", "street", "10000"));
생명 주기를 공유하기 때문에 Cascade 와 고아 객체 제거 기능을 가짐.
맴버를 조회 했는데 맴버만 가져왔음.
= 다른 테이블 값은 사용할때 가져온다.
= 지연 로딩이다.
System.out.println("==========start=============");
Member findmember = em.find(Member.class, member.getId());
List<Address> addressHistory = findmember.getAddressHistory();
for (Address address :
addressHistory) {
System.out.println("address = " + address.getCity());
}
Set<String> favoriteFoods = findmember.getFaroriteFoods();
for (String favoriteFood:
favoriteFoods) {
System.out.println("favoriteFood = " + favoriteFood);
}
(중요) 값타입 수정
이뮤터블 해야된다. 교체를 해야된다.
그러나 기대한데로 업데이트가 나가는데 공유문제 때문에 쓰면 안된다.
Member findmember = em.find(Member.class, member.getId());
//homeCity -> newCity
Address a = findmember.getHomeAddress();
findmember.setHomeAddress(new Address("newCity", a.getStreet(), a.getZipcode()));
이제 값타입 컬렉션 수정
String 은 값타입 이기 때문에 갈아껴야됨(교체)
delete -> insert
어려운거
//치킨 -> 한식
findmember.getFaroriteFoods().remove("치킨");
findmember.getFaroriteFoods().add("한식");
동작방식 - equals로 동작함.
//주소 변경 old1-> newCity
findmember.getAddressHistory().remove(new Address("old1", "street", "10000"));
그래서 객체 equals가 구현이 안되있으면 망하는거에요.
findmember.getAddressHistory().add(new Address("newCity", "street", "10000"));
여기서 부터 헬이니까 잘보세요.
맴버와 연결된 기록을 다지웠음. 그리고 2개를 다시 넣음.
식별자 개념이 없다. 추적이 안되요.
다지우고 다시 저장함.
delete from ADDRESS where MEMBER_ID=?
insert into ADDRESS(~) values(~) x N개
쓰면 안됩니다.
나중에 대안책이 있음
기본키 구성 = null, 중복 막기
그냥 1:다 로 쓰세요.
여기서 부터는 양방향으로 해서 편의 기능을 만들어줘도 됨.
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name="MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();
@Entity
@Table(name="ADDRESS")
public class AddressEntity {
@Id @GeneratedValue
private Long id;
private Address address;
}
1:다 단방향 쿼리에서는 업데이트가 나갈수 밖에 없다.
= 다른테이블(많은쪽)에 외래키가 있기 때문에
1:다(임베디드) 모양새
실무에서는 이방법을 많이 씁니다.
그래서 언제 쓰는가
기준 : 진짜 단순할때 select box
- 치킨
- 피자
- 한식
주소 이력 = 엔티티
정리
실전 예제 6 - 값 타입 매핑
ERD
코드 손으로 짜지 마세요.
그냥 값이면 프록시 객체 일때 작동이 안됨. 그래서 옵션을 체크 하세요.
좋은점 : 의미 있는 메서드를 만들수 있다.
public String fullAddress(){
return getCity() +" "+ getStreet() +" "+ getZipcode();
}
@Column(length=10)
public boolean isValid(){
boolean result = false;
// if(조건)
return result;
}
실제로 이렇게 많이 함.
스테레오 타입이라고함 UML에서.
객체지향 쿼리 언어1 - 기본 문법
소개
JPQL, QueryDSL
나이가 18살 이상인 회원은?
JPQL
Member 는 객체다!
List<Member> resultList = em.createQuery(
"select m From Member m where m.username like '%kim%'",
Member.class
).getResultList();
for (Member member :
resultList) {
System.out.println("member = " + member);
}
거의 비슷함.
차이점 : 객체대상, (m, * ) 같은 문법이 객체기반
Criteria = 안써요.
동적 쿼리를 만들기가 엄청 어려움
빈칸 없애기네
QueryDSL = 실무사용 권장
장점 : 컴파일러에서 변수를 사용해서 오타 방지
JPQL만 잘하면 QueryDSL은 그냥..
네이티브 SQL - 특정 DB 힌트 기능.사용등
하이버 네이트가 지원은 하긴함.
SpringJdbcTemplate를 사용하세요
영속성 때문에 강제 플러시가 필요함.
정리
JPQL을 철저히 잘해야된다.
5프로가 안됨 = SpringJdbcTemplate 씁니다.
기본 문법과 쿼리 API
새프로젝트 생성
maven
pom.xml 디펜던씨
META-INF 폴더 복사
Member 클래스 생성
Team 클래스 생성
Member : Team 다:1 관계 생성.
Team mapped 객체 생성
메인 클래스 복사
Order 클래스 생성
Address 클래스 생성
Product 클래스 생성
Order-Product 다:1 관계 생성
완성
이름 Order는 DB에서 예약어 이다.
JPQL 문법
대소문자 구분해야된다.
m(as m) 필수
집합과 정렬
ANSI SQL 지원
TypeQuery, Query
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
TypedQuery<Member> select_m_from_member_m = em.createQuery("select m from Member m", Member.class);
TypedQuery<String> query2 = em.createQuery("select m.username from Member m", String.class);
Query query3 = em.createQuery("select m.username, m.age from Member m");
결과 조회 API
query.getResultList(), query.setSingleResult()
iter
1개인줄 알았는데 안나오거나 많이 나오거나
getResultList() = 빈 리스트
getSingleResult = 없으면 NoResultException
둘이상이면 NonUniqueResultException
없는데 예외가 터지면 좀.. 별로에요.
Spring Data JPA 가 1번 옵셔널로 반환 해줍니다.
파라미터 바인딩
TypedQuery<Member> query = em.createQuery("select m from Member m where m.username = :username", Member.class);
query.setParameter("username", "member1");
위치 기반도 지원 = 쓰지마세요.
프로젝션(SELECT)
프로젝션
스칼라 타입(혼합)
생각보다 깊은 내용이 있다.
영속성관리가 될까 안될까
관리가 되네.10개고 20개고 다 관리됨
왠만하면 조인 붙을만한거 쓰지마.
차라리 쿼리에 조인을 넣어서 써라.(예측)
List<Team> result = em.createQuery("select t from Member m join m.team t", Team.class)
.getResultList();
스칼라는 일반 SQL
List<Member> result = em.createQuery("select distinct m.username, m.age from Member m")
.getResultList();
타입이 2개인데 어떻게..
List resultList = em.createQuery("select distinct m.username, m.age from Member m")
.getResultList();
Object o = resultList.get(0);
Object[] result = (Object[]) o;
System.out.println("result = 0" + result[0]);
System.out.println("result = 1" + result[1]);
Object[].class 지정
List<Object[]> resultList = em.createQuery("select distinct m.username, m.age from Member m", Object[].class)
.getResultList();
Object[] result = resultList.get(0);
제일 깔끔한거
new 사용.
List<MemberDTO> resultList = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member m", MemberDTO.class)
.getResultList();
MemberDTO memberDTO = resultList.get(0);
System.out.println("memberDTO = " + memberDTO.getUsername());
System.out.println("memberDTO = " + memberDTO.getAge());
순서도 같아야됨. 기본 로직과 맞추는것
페이징
페이징
정말 아트 의 경지 입니다.
setFirstResult(int startPosition)
setMaxResults(int maxResult)
페이지 네이션의 검증은 오더바이로 해야된다. 소팅이 되면서 순서대로 가져와야 되서.
List<Member> result = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
System.out.println("result = " + result.size());
for (Member member1 : result) {
System.out.println("member1 = " + member1);
}
(집중) toString 만드실때 team(양방향) 되면 큰일납니다.
무한루프
for (int i = 0; i < 100; i++) {
Member member = new Member();
member.setUsername("member"+i);
member.setAge(i);
em.persist(member);
}
각 방언별로 들어감
일반적인 오라클 쿼리
결국 다 JPA가 해주는거임
조인
조인
내부조인 : 있는 컬럼끼리
외부조인 : 없는 컬럼도 null로 출력
세타조인 : 막조인(연관관계가 없음)
이상한예지만 운영자 팀 이름과 사용자 이름이 같은 케이스?
편의 메소드 추가.
String query = "select m from Member m inner join m.team t";
이게바로 1:다 는 패치를 LAZY로 잡아두셔야 됩니다.
String query = "select m from Member m inner join m.team t where t.name = :teamName";
String query = "select m from Member m left join m.team t where t.name = :teamName";
세타 조인 = 크로스 조인
String query = "select m from Member m, Team t where m.username = t.name";
ON 절을 활용한 조인
연관관계가 없는 엔티티 외부 조인할수 있습니다.
조인대상 필터링 예시
연관관계 없는 엔티티 외부 조인 예시
String query = "select m from Member m left join m.team t on t.name='teamA'";
String query = "select m from Member m left join Team t on m.username = t.name";
서브 쿼리
서브쿼리
메인 쿼리와 서브쿼리가 관계가 없다.
밑에 서브에서 메인을 긁어 오면 성능이 안나옴.
[NOT]EXISTS
[NOT]IN
JPA 서브쿼리 한계점
SELECT 절 서브쿼리
String query = "select (select avg(m1.age) From Member m1) from Member m
FROM 절에서 서브쿼리가 불가능
- 조인으로 풀 수 있으면 풀어서 해결
- 아니면 포기
불편한거 2개
FROM절 서브쿼리
그냥 쿼리 2번 날리고 조절하는식으로
이경우에도 네이티브를 안쓰려고함
대부분 FROM 절의 서브쿼리를 이용하는 이유
뷰의 로직이 섞여 들어가 있는 경우가 있음.
JPQL 타입 표현과 기타식
JPQL 타입 표현
String query = "select m.username, 'HELLO', TRUE FROM Member m";
List<Object[]> result = em.createQuery(query)
.getResultList();
for (Object[] objects : result) {
System.out.println("objects = " + objects[0]);
System.out.println("objects = " + objects[1]);
System.out.println("objects = " + objects[2]);
}
String query = "select m.username, 'HELLO', true from Member m " +
"where m.type = jpql.MemberType.ADMIN";
m 뒤에 띄어쓰기 필요함
조심해야됩니다.@Enumerated(EnumType.STRING)
패키지 이름이 길자나요.
String query = "select m.username, 'HELLO', true from Member m " +
"where m.type = :userType";
List<Object[]> result = em.createQuery(query)
.setParameter("userType", MemberType.ADMIN)
.getResultList();
상속관계
아이콘
잘쓰진 않아요.
em.createQuery("select i from Item i where type(i) = Book ", Item.class);
다시 복습
DiscriminatorValue("BB") 는 부모 클래스 DTYPE에 BB 라고 쓰여짐
JPQL 기타
"where m.username is not null";
"where m.username between 0 and 10";
조건식(CASE 등등)
조건식 - CASE 식
기본 CASE 식, 단순 CASE 식
select 뒤에 공백
String query =
"select " +
"case when m.age <= 10 then '학생요금' " +
" when m.age >= 60 then '학생요금' " +
" else '일반 요금' end " +
"from Member m";
List<String> result = em.createQuery(query, String.class)
.getResultList();
for (String s : result) {
System.out.println("s = " + s);
}
query dsl 기대하세요.
coalesce
NULLIF
String query ="select coalesce(m.username, '이름 없는 회원') as username from Member m";
String query ="select nullif(m.username, '관리자') as username from Member m";
관리자면 null을 반환
JPQL 함수
JPQL 기본함수
표준함수 - DB 상관없음
사용자 정의 함수 호출
- DB 고유 함수를 그냥 쓸수 있는건 아니고 추가해 놔야됨
이미 하이버네이트에 등록을 많이 해줬다.
CONCAT-문자열 더하기
String query ="select 'a' || 'b' From Member m";
CONCAT 표준
String query ="select concat('a','b') From Member m";
SUBSTRING- 문자열에서 2번째 3개 짤라네
String query ="select substring(m.username, 2, 3) From Member m";
LOCATE -위치
String query ="select locate('de','abcdegf') From Member m";
오류
TypedQuery [java.lang.String] is incompatible with query return type [class java.lang.Integer
SIZE - 연관관계 컬랙션 크기
String query ="select size(t.members) From Team t";
INDEX - 일반적인 사용은 아니고 @OrderColumn을 쓸때 사용 가능
거의 안씁니다.
사용자 정의함수 호출
dialect 패키지 생성후 디비 를 상속받아 클래스 생성
등록은 소스코드에 자세히 나와있음
<property name="hibernate.dialect" value="dialect.MyH2Dialect"/>
String query ="select function('group_concat', m.username) From Member m";
하이버네이트 쓰면 축약 가능
String query ="select group_concat(m.username) From Member m";
객체지향 쿼리 언어2 - 중급 문법
경로 표현식
경로 표현식
경로에 따라서 내부 동작이 달라짐
앞선 강의들에서 보았듯이 쿼리에 영향을 줄것으로 보임.
다 특성이 달라서 알아둬야 된다.
상태필드 - 단순히 값을 저장
단일값 연관 필드
단일 연관 : ManyToOne, OneToOne 대상이 엔티티
컬렉션 값 연관 : @OneToMany, @ManyToMany 대상이 컬렉션
경로 표현식 특징
기대와 쿼리가 다르다.
inject language
m.team 같은 경우 다른 테이블 이므로 묵시적 내부 조인 발생
String query ="select m.team From Member m";
조심해서 써야되겠구나.
쿼리 튜닝할때 어렵겠는데, 잘못하면 망합니다. = 운영이 힘듬
왠만하면 묵시적 내부조인이 나오게 짜지 마라.
저는 왠만하면 JPQL이랑 SQL이랑 맞춥니다.
직관적으로 튜닝하기가 어렵다.
컬렉션값 연관경로는 탐색을 못함.
1:다 에서 다쪽의 선택을 해서 어떤값을 꺼내야 할지 난감함.
import java.util.Collection;
핵심은 뭐냐면 탐색을 못한다.(점을 찍었는데 내부 맴버가 안나온다.)
쓸수있는게 사이즈 정도 밖에 없다.
String query ="select t.members.size From Team t";
Integer result = em.createQuery(query, Integer.class)
.getSingleResult();
System.out.println("result = " + result);
결과가 1개라도 있어야된다. 아니면 예외 처리
안해봤던것도 해볼까요.(저도 원래는 이렇게 잘 안써요)
값이 2개니까 수정
String query ="select t.members From Team t";
Collection result = em.createQuery(query, Collection.class)
.getResultList();
(TEAM 테이블에서)members.username을 찍고 싶어요.
명시적인 JOIN을 써야됩니다.
String query ="select m.username From Team t join t.members m";
FROM 절에서 명시적 조인을 하면 별칭을 얻는것이 가능
= 서브쿼리 우회?
실무에서는 다 무시하시고 "묵시적 조인을 쓰지마세요"
= 쿼리 체크하고 조인 나가면 수정하세요.
= 명시적 조인을 써야 튜닝 하기가 쉬워요.
정리
경로 표현식 예제
묵시적 조인의 주의사항
실무조언 = 16:28
페치 조인 1 - 기본
JPQL-페치 조인(jetch join)
실무에서 중요해! = 빡 집중해라
성능 최적화를 위해 제공하는 기능
2번 같은데 1방으로
회원 조회할때 팀도 가져오고 싶어.
어디선가 본 쿼리 = 즉시 로딩 쿼리
그러나 내가 원하는 타이밍에 명시적으로 쿼리가 가능하다.
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member = new Member();
member.setUsername("회원1");
member.setTeam(teamA);
em.persist(member);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String query ="select m From Member m";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member member1 : result) {
System.out.println("member = " + member1.getUsername()+ ", " + member1.getTeam());
}
LAZY로 되어있으면 지연로딩(프록시)로 들어옴.
이러면 이제 회원 100명이면?
= N(100번) + 1(첫번째 날린쿼리)
//회원1, 팀A(SQL)
//회원2, 팀A(1차 캐시)
//회원3, 팀B(SQL)
그래서 어떻게 풀어야 되냐 방법이 이거밖에 없습니다.
fetch join
String query ="select m From Member m fetch join m.team";
String query ="select m From Member m left join fetch m.team";
이때부터 포문 돌때 Team은 프록시가 아니다.
영속성 컨텍스트에 데이터가 이미 올라가 있다.
이게 Fetch join 입니다. 실무에서 엄청 많이 씁니다.
지연로딩을 디폴트로 해놓고 fetch join을 원하는 시점에 사용한다.
여기까지 @ManyToOne
컬렉션 fetch join = @OneToMany
컬랙션 일때는 요골 조심해야 됩니다.
DB 입장에서 1:다 는 데이터가 뻥튀기 되는겁니다.
뽑아내는 데이터의 형식이 그럴수 있다는 말
JPA의 능력밖의 일임. 결과값으로 그렇게 나온걸 가져와야 하기 때문에 쿼리만큼 컬렉션을 생성함.
for (Team team : result) {
System.out.println("team = " + team.getName() +"|"+ team.getMembers().size());
for (Member member : team.getMembers()){
System.out.println("member = " + member);
}
}
이런 식의 문제
이런건 DISTINCT 로 제거하면 된다.
-SQL기능 + 엔티티 중복제거
혹시 헤깔리실 까봐 빼고 한번 돌려봅시다.
요렇게 해도.
SQL의 본래 기능
다:1 은 뻥튀기가 안됨. SQL 기본 내용
fetch join 과 일반 조인의 차이
일반(지연) : 쿼리3 + 뻥튀기 + 중복 컬랙션
fetch join(즉시) : 쿼리1 + 뻥튀기 + 중복 컬랙션
이걸로 N + 1 문제를 다 해결함.
페치 조인 2 - 한계
페치 조인의 특징과 한계
별칭을 줄수 없다.
String query ="select t From Team t join fetch t.members as m";
별칭을 주시면 안되요.
as m where m.username
이런거 하시면 안되요.
왜 안되냐면 중간에 걸르고 싶긴하겠지만 그렇게 할거면 fetch join을 쓰면 안되요. 따로 조회하세요.
5명중에 1명만 불러와야지 = 4명 누락
조심해서 특정한 구간에 쓰면 유용함.
그러나 5명중에 3명만 불러와서 따로조작한다 = 이런게 위험한것
팀에서 맴버 찍어서 객체로 가면 원래 5명이 나와야 되는데 (내가 이미 걸러놔서) 3명만 나옴.
= 처음부터 멤버를 조회했어야지
팀A (모두) vs 팀A(5명)
그래서 쓰지마세요. 어쩌다 있는데 거의 없어요.
유일 케이스 - 이런식으로 join fetch 몇단계
String query ="select t From Team t join fetch t.members m join fetch m.team";
하이버네이트에서 고민의 흔적이 보이지만 하지마세요.
운영에서 망해요.
별도의 쿼리를 쓰세요.
둘 이상의 컬렉션은 페치 조인 할수 없다.(정합성 문제)
그래서 1개의 컬렉션만 쓰셔라.
요게 이제 압권
페이징 API를 사용할수 없다.(데이터 뻥튀기가 되서)
DB가 페이지로 자르면 JPA에서 회원들이 누락되는 상황
하이버네이트는 경고로그 남기고 메모리에서 페이징
엄청 위험함
WARN: HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
쿼리도 압권임 limit offset 없음.
DB에서 다끍어옴 = 장애 나기 좋음
100만건이면?
1:다 면 다:1로 쿼리를 다시 날려라.
아니면 일반 쿼리로 전환
소리커짐
String query ="select t From Team t";
List<Team> result = em.createQuery(query, Team.class)
.setFirstResult(0)
.setMaxResults(2)
.getResultList();
System.out.println("result = " + result.size());
for (Team team : result) {
System.out.println("team = " + team.getName() +"|"+ team.getMembers().size());
for (Member member : team.getMembers()){
System.out.println("-> member = " + member);
}
}
성능이 안나옴.
그래서 이제 @BatachSize(size=1000이하) 가 있습니다.
?,? = Team Id 가 2개 들어감
첫 팀 쿼리에서 팀을 100개를 넘김
받은 팀을 지연 로딩으로 이후 쿼리에 대입함.
강사님은 주로 글로벌 세팅으로 가져감.
실무에서 깔고 프로젝트를함
<property name="hibernate.default_batch_fetch_size" value="100"/>
쿼리가 테이블 수만큼 나옴
팀조회 -> 팀 100개 추출 -> 멤버 쿼리에서 매개변수로 대입
쿼리 1번 -> 조작 -> 조작 쿼리 1번
소리 작아짐
3. DTO로 뽑으면 됨 - 그래도 정제 해야됨
대부분 성능 문제 해결됨.
정리
복잡한 통계 쿼리 어떻게하지?
일반조인 + 레이지로딩으로 DTO를 반환뒤 정제
처음부터 new operation 으로 DTO로 가져오기.
너무 중요해요
실무 썰
뉴비 귀여워
엔티티 직접 사용
JPQL 엔티티 직접 사용
따로 설명하는 이유
SQL에서는 엔티티 자체를 넣는 경우는 없음.
그러나 JPA는 그게 가능하다.
객체의 식별자가 디폴트로 적용된다.
String query ="select m From Member m where m = :member";
Member findMember = em.createQuery(query, Member.class)
.setParameter("member", member1)
.getSingleResult();
System.out.println("findMember = " + findMember);
String query ="select m From Member m where m.id = :member";
Member findMember = em.createQuery(query, Member.class)
.setParameter("member", member1.getId())
.getSingleResult();
System.out.println("findMember = " + findMember.getId());
2명이상이라서 에러남
String query ="select m From Member m where m.team = :team";
List<Member> members = em.createQuery(query, Member.class)
.setParameter("team", teamA)
.getResultList();
for (Member member : members) {
System.out.println("member = " + member);
}
Named 쿼리
Named 쿼리 - 정적 쿼리
결과
어마어마한 메리트가 있어요.
쿼리를 서버 로딩 시점에 파싱후 캐시로 들고 있음.
서버 로딩 시점에 쿼리를 검증할수 있음
어마어마하게 막강
List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
for (Member member : resultList) {
System.out.println("member = " + member);
}
실수로 잘못치면
.QuerySyntaxException: MemberQQ is not mapped
가장 좋은 에러 컴파일 타임에 나는 에러
가장 나쁜에러는 사용자가 사용할때 나는 오류
중간의 오류 = 런타임 오류 = 로딩 시점
양심이 있으면 실행 한번은 해야지
XML에 정의
사실 이렇게는 안쓰는데요.
스프링 Data Jpa에서는 @Query 로 인터페이스 위에서 사용할수 있음. = named 쿼리로 등록시켜버림
이게 어마어마 한거죠.
좀 별로에요.
DAO 에다가
벌크 연산
벌크 연산
변경감지가 루프 돌리면서 하기가 좀 그래..
N 번의 쿼리
JPA = 단건 최적화
그외 벌크연산을제공
모든 회원 나이 20살
MappQQ 오류남 - 수정
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
System.out.println("resultCount = " + resultCount);
INSERT(insert into ... select) 도 지원함 - 하이버네이트
벌크 연산의 주의점
영속성 컨텍스트 무시 하므로
-연산만 먼저 실행 하거나
-수행후 영속성 컨텍스트만 초기화
이전 조회 -> 벌크 연산 -> 이전조회??
//FLUSH 자동 호출 commit, query, flush
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
결과
다시 가져와도 망함.
Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember = " + findMember.getAge());
findMember = 0
영속성 컨텍스트가 초기화 안되서 DB 조회(SELECT)를 안함.
참고로@Modifying 쿼리에 이런 옵션이 있음.
장애 버그..
그냥 where 쓰지말고 제공해주는거로 사용하세요.