JpaMain.java
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member1 = new Member();
member1.setUsername("관리자1");
em.persist(member1);
Member member2 = new Member();
member2.setUsername("관리자2");
em.persist(member2);
em.flush();
em.clear();
String query = "select m.team from Member m";
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team);
}
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
/* */ : jpql
나머지 부분은 sql 쿼리!
sql쿼리에서 멤버랑 팀을 조인해서 팀을 select projection에 나열한것을 확인할 수 있다.
실무에서는 이렇게 묵시적인 내부조인이 발생하게 쿼리를 짜면 안된다.
컬렉션은 컬렉션 그대로 가지고 오거나 사이즈만 가지고 올 수 있다.
ex) String query = "select t.members.size from Member m";
또는 컬렉션을 가지고 올 수 있다.
String query = "select t.members from Member m";
List<Collection> result = em.createQuery(query, Collection.class);
select m.username from Team t join t.members m
묵시적 조인 말고 명시적 조인을 써라!
명시적 조인을 써야 쿼리 튜닝이 쉽다.
실무에서 정말정말 중요!!
select projection에 m만 적었는데, 실제로 실행된 sql 에서는 M의 데이터와 T의 데이터를 모두 나열한다.
JpaMain.java
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamB);
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 member : result) {
System.out.println("member = " + member.getUsername()+ ", " + member.getTeam().getName());
//회원1, 팀A(SQL쿼리로 가져옴. 영속성 컨텍스트에 없기 때문)
//회원2, 팀A(jpa한테 팀A를 달라고함, 그럼 jpa가 1차캐시에서 가져옴.그래서 쿼리 없이 바로 출력된것)
//회원3, 팀B(1차 캐시에 없어서 쿼리를 날려서 1차 캐시에 올리고 결과를 반환)
}
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
N+1 문제 : 1번째 날린 쿼리로 얻은 결과만큼 n번 나옴.
문제 해결 -> 패치 조인
이때에는 getTeam()할때 프록시가 아니라 실제 팀이 들어감.
JpaMain.java
try {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
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();
//조인을 하긴 하는데 fetch(한번에 가져옴)
String query = "select m from Member m join fetch m.team";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member = " + member.getUsername()+ ", " + member.getTeam().getName());
}
tx.commit();
}
inner join으로 쿼리가 한방에 나가는 것을 확인 가능
JpaMain.java
try {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
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();
//조인을 하긴 하는데 fetch(한번에 가져옴)
String query = "select t from Team t join fetch t.members";
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName() + " |members = " + team.getMembers().size());
}
tx.commit();
}
근데 중복으로 출력되지?
db에서는 일대다 조인하면 데이터가 뻥튀기가 된다.
팀A입장에서는 회원1,회원2가 있다.
이걸 멤버와 조인하면 팀A입장에서는 두개의 row가 발생.
따라서 db에서 두 줄을 가져오는 것이다.
JpaMain.java
try {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
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();
//조인을 하긴 하는데 fetch(한번에 가져옴)
String query = "select t from Team t join fetch t.members";
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName() + " |members = " + team.getMembers().size());
for (Member member : team.getMembers()) {
System.out.println(" --> member = " + member);
}
}
tx.commit();
}
중복 싫은데 어떡하지?
애플리케이션에서 올라올때, 이 컬렉션에 담기는 애가 중복 제거해야하는구나 하고 jpa가 걸러준다.
참고로 다대일 관계는 뻥튀기가 안된다.
일대다만 갯수가 뻥튀기됨.
일반조인은 select절에서 team만 조회함.
데이터가 로딩 시점에 로딩이 안되어서 select쿼리가 다 나감.
패치 조인 대상에는 별칭을 줄 수 없다.
다시 정리할것.(13:00~19:00)
@BatchSize를 쓰게 되면 N+1이 아니라 테이블 수 만큼 맞출 수가 있다.
1.Team.java에
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
추가
2.persistence.xml에 <property name="hibernate.default_batch_size" value="100" />
추가
엔티티가 db로 넘어가면 db의 pk값이다
JpaMain.java
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 {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
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 where m= :member";
Member findMember = em.createQuery(query, Member.class)
.setParameter("member", member1)
.getSingleResult();
System.out.println("findMember = " + findMember);
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
JpaMain.java
String query = "select m from Member m where m.id= :memberId";
Member findMember = em.createQuery(query, Member.class)
.setParameter("memberId", member1.getId())
.getSingleResult();
System.out.println("findMember = " + findMember);
tx.commit();
두 경우다 출력이 같다.
m.team = TEAM_ID이다.
m.team은 fk랑 매핑이 되어있다.
JpaMain.java
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);
쿼리에 이름을 부여할 수 있다.
애플리케이션 로딩 시점에 초기화 후 재사용
: 애플리케이션 로딩 시점에, jpa가 sql로 파싱을 해서 캐시하고 있다.
Member.java
@Entity
@NamedQuery(
name = "Member.findByUsername", // 엔티티명. 으롶 관례상 표현
query = "select m from Member m where m.username = :username"
)
public class Member {
JpaMain.java
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 {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
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();
List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
for (Member member : resultList) {
System.out.println("member = " + member);
}
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
JpaMain.java
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 {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
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();
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
System.out.println("resultCount = " + resultCount);
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리가 들어간다.
잘못하면 꼬일 수 있다.
JpaMain.java
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 {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
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();
//flush 자동 호출 ; commit 될 때나, 쿼리 나가거나,강제 플러시 호출 할 때
//flush가 되는 순간 셋다 디비에 0 0 0 으로 insert가 되고, update쿼리가 나간다..flush라는건 영속성 컨텍스트에 있는 것을 디비에 반영하는 것.
int resultCount = em.createQuery("update Member m set m.age = 20") //db에 강제로 업데이트함. 영속성 컨텍스트에는 반영이 안되어 있음.
.executeUpdate();
System.out.println("resultCount = " + resultCount);
System.out.println("member1.getAge() = " + member1.getAge());
System.out.println("member2.getAge() = " + member2.getAge());
System.out.println("member3.getAge() = " + member3.getAge());
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
영속성 컨텍스트에는 반영이 안되어있지만,
db에는 정상 반영이 되어있다.
어떻게 풀까?
db에 데이터가 먼저 있을때 ! 이 예제에서는 사용 안됨.
JpaMain.java
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 {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
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();
//flush 자동 호출 ; commit 될 때나, 쿼리 나가거나,강제 플러시 호출 할 때
int resultCount = em.createQuery("update Member m set m.age = 20") //db에 강제로 업데이트함. 영속성 컨텍스트에는 반영이 안되어 있음.
.executeUpdate();
em.clear(); // 영속성 컨텍스트에 있는 것을 지운다
//영속성 컨텍스트에 값이 없기 때문에 디비에서 가져온다
Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember = " + findMember.getAge());
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}