Life Developer
인생 개발자
분류 전체보기 (141)
[JPA]JPQL

JPQL이란 테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리다

 

 

ex)

List<Member> result = em.createQuery("select m from Member m where m.userName like '%kim%'",
Member.class).getResultList();
for (Member member : result) {
System.out.println("member = " + member);
}

 

 

 

===========================================================================

 

 

 

Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);

TypedQuery<Member> query1 = 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");

 

 

여러개 - getResultList()는 결과가 없어도 빈 리스트 가져옴

TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class);
List<Member> resultList = query1.getResultList();

for (Member member1 : resultList) {
System.out.println("member1 = " + member1);
}

 

 

 

1개 - getSingleResult() 는 조심해야함. 정확하게 결과가 1개 나와야함

TypedQuery<Member> query1 = em.createQuery("select m from Member m where m.id=1", Member.class);
Member singleResult = query1.getSingleResult();
System.out.println("singleResult = " + singleResult.getUsername());

 

 

 

파라미터를 넘기기 - ' : ' 를 사용하고 체이닝기법을 사용하는게 깔끔하다.

Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);

Member singleResult = em.createQuery("select m from Member m where m.username=:username", Member.class)
.setParameter("username", "member1").getSingleResult();
System.out.println("singleResult = " + singleResult.getAge());

 

 

 

 

  Comments,     Trackbacks
[JPA]테이블에 컬렉션을 저장한다-@ElementCollection,@CollectionTable

웬만하면 다 Entity 객체 타입이다.

 

값타입 컬렉션은 진짜 정말 단순한거 저장하거나 할때 사용. 추적 필요없을때 사용하는거임.

 

값타입은 식별자가 없어서 엔티티에 의존한다.

 

공유되더라도 불변객체로 만드는게 안전하다.

 

정말 값타입이라 판단될때 사용하자.

 

Entity로 만들어서 사용하는것을 권장한다.

 

 

 

 

 

FK가진놈이 주인이라고 생각하고 Entity연관관계매핑을 해왔는데 그것과 헷갈린다.

 

자동으로 테이블을 생성해준다.

@ElementCollection을 써주고 @CollectionTable에 테이블 정보를 입력하면 된다.

아 ㅡㅡ 머리 빠개지네

 

 

@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD",joinColumns = @JoinColumn(name="MEMBER_ID"))
@Column(name="FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<>();

@ElementCollection
@CollectionTable(name = "ADDRESS",joinColumns = @JoinColumn(name="MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();

 

 

 

 

중요한것은 값타입 이기 때문에 member만 persist 해도 전부 쿼리 때려버린다.

 

Member member = new Member();
member.setUserName("member1");
member.setHomeAddress(new Address("homecity", "street", "12312"));

member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("스파게티");
member.getFavoriteFoods().add("족발");

member.getAddressHistory().add(new Address("old1", "street", "12312"));
member.getAddressHistory().add(new Address("old2", "street", "12312"));

em.persist(member);

System.out.println(" ================================== ");

 

 

 

 

========================================================================

 

그럼 이 컬렉션들은 지연로딩일까??

그렇다.

 

아래코드를 실행하면 일단 member만 select 때리고, address와 food 관련 정보를 건드리면 select가 나간다.ㅋ

LAZY가 기억이 나는군.

 

Member member = new Member();
member.setUserName("member1");
member.setHomeAddress(new Address("homecity", "street", "12312"));

member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("스파게티");
member.getFavoriteFoods().add("족발");

member.getAddressHistory().add(new Address("old1", "street", "12312"));
member.getAddressHistory().add(new Address("old2", "street", "12312"));

em.persist(member);

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

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.getCity() = " + address.getCity());
}

Set<String> favoriteFoods = findMember.getFavoriteFoods();
for (String favoriteFood : favoriteFoods) {
System.out.println("favoriteFood= " + favoriteFood);
}

System.out.println(" ======================================================= ");
tx.commit(); //이시점에 DB 쿼리가 날라감

 

보니까 LAZY군

 

'Developer' 카테고리의 다른 글

[JPA]Criteria  (0) 2020.09.21
[JPA]JPQL  (0) 2020.09.21
[JPA]@Embeddable 문제점  (0) 2020.09.20
[JPA]공통된거 묶기-@Embedded, @Embeddable  (0) 2020.09.20
[JPA]불쌍한 고아객체..  (0) 2020.09.20
  Comments,     Trackbacks
[JPA]@Embeddable 문제점

만약

 

@Embedded
private Address homeAddress;

@Embedded
@AttributeOverrides({@AttributeOverride(name = "city", column = @Column(name = "city2")), @AttributeOverride(name = "zipcode", column = @Column(name = "zipcode2")),
@AttributeOverride(name = "street", column = @Column(name = "street2"))})
private Address workAddress;

 

이렇게 member 코드를 짜고

 

Address address = new Address("city", "street", "zipcode");

Member member1 = new Member();
member1.setUserName("member1");
member1.setHomeAddress(address);

em.persist(member1);

Address mAddress = new Address(address.getCity(),address.getStreet(),address.getZipcode());

Member member2 = new Member();
member2.setUserName("member2");
member2.setHomeAddress(mAddress);

em.persist(member2);

mAddress.setCity("new City");

 

이렇게 main으로 실행하면 같은 클래스를 바라보고 있지만 각각 변경이 가능하다.

 

객체는 주소값으로 저장하기 때문에 아예 객체를 하나 더 만들어서 set을 해주면 된다.

 

이렇게 안하면 값이 동시에 변경될수 있다.

 

참조를 막을수 있는 방법이 없다.

 

그래서 객체타입을 수정하지 못하게 만드는 방법으로 사용할수 있다.

 

불변객체로 만든다는 것이다.

 

생성자로 생성하고 setter 메서드에 private를 쓰던지 아예 setter를 안만들면 된다.

  Comments,     Trackbacks
[JPA]공통된거 묶기-@Embedded, @Embeddable

사용전에 꼭 Equals와 hashcode 메서드를 만들어 동등성비교를 꼭 해야한다.

 

@Entity는 하지 않아 테이블로 생성하는게 아니다.

 

멤버에서 그룹짓고 싶은 놈들을 묶어 클래스에 다 짱박아두고 사용하는 전략.

 

사용법은 추가된 클래스에 @Embeddable을 입력,

그것을 쓸 Entity에 @Embedded를 입력해 멤버변수로 추가.

 

만약 똑같은 클래스를 사용하는 멤버변수를 선언하려면 아래처럼 @AttributeOverrides를 사용하여

컬럼명을 바꿔줘야함.

 

 

직접해보면

묶어서 클래스에 박아놓고,

 

@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;

public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}

public Address() {
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getStreet() {
return street;
}

public void setStreet(String street) {
this.street = street;
}

public String getZipcode() {
return zipcode;
}

public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
}

 

 

 

 

멤버에서 이렇게 추가해준다.

 

@Embedded
private Address homeAddress;

@Embedded
@AttributeOverrides({@AttributeOverride(name = "city", column = @Column(name = "city2"))@AttributeOverride(name = "zipcode", column = @Column(name = "zipcode2")),
@AttributeOverride(name = "street", column = @Column(name = "street2"))})
private Address workAddress;

  Comments,     Trackbacks
[JPA]불쌍한 고아객체..

 

 

고아객체가 뭐냐면 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 고아객체라고 하는데

 

이것을 자동으로 삭제해주는 기능이있다.

 

orphanRemoval = true 를 주면된다.

 

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL,orphanRemoval = true)
private List<Child> childList = new ArrayList<>();

 

 

예제코드는 아래다.

 

 

Child child1 = new Child();
Child child2 = new Child();

Parent parent = new Parent();

parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);

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

Parent findParent = em.find(Parent.class, parent.getId());
for (Child c : findParent.getChildList()) {
System.out.println("c.getId() = " + c.getId());
}
findParent.getChildList().remove(0);
for (Child c : findParent.getChildList()) {
System.out.println("c.getId() = " + c.getId());
}

System.out.println(" ================================== ");
tx.commit(); //이시점에 DB 쿼리가 날라감

 

 

그리고 부모를 제거하면 자식도 제거된다. (고아기 때문에)

  Comments,     Trackbacks
[JPA]CASCADE - persist 1번에 자식까지

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(child1);

 

System.out.println(" ================================== ");
tx.commit(); //이시점에 DB 쿼리가 날라감

 

여기서 언제 전부 퍼시스트 하고있나.

걍 아래처럼 부모에 cascade걸어서 한방에 해결하자.

 

em.persist(parent); 한방이면 shild 자동으로 퍼시스트가 된다

 

 

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();

 

단, Child가 parent에서만 관리가 되고 연관된테이블이 없을시에만 쓰자.

  Comments,     Trackbacks
[JPA]즉시로딩(EAGER)의 문제점

 

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name="TEAM_ID")
private Team team;

 

처럼 즉시로딩하면 문제점이 생긴다.

 

List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();

System.out.println(" ================================== ");
tx.commit(); //이시점에 DB 쿼리가 날라감

 

 

이럴 경우 select 쿼리가 2번 나가게 된다.

 

select * from member 한번 나가고, 나가는 도중 team이 즉시로딩이네? 라고 판단되어

 

또다시 select * from team 쿼리가 나가게 되는것이다.

 

그냥 .find 같은 경우에는 pk를 찍어서 가져오는거기 때문에 JPA가 걍 알아서 해주는데 이건 아니다.

 

이거 심각한 문제다.

 

아래 경우엔 team select를 2번하게 된다.

 

Team team = new Team();
team.setName("teamA");

Team team2 = new Team();
team2.setName("teamB");

Member member1 = new Member();
member1.setUserName("member1");
member1.setTeam(team);

Member member2 = new Member();
member2.setUserName("member1");
member2.setTeam(team2);


em.persist(team);
em.persist(team2);
em.persist(member1);
em.persist(member2);


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

// Member m = em.find(Member.class, member1.getId());

List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();

System.out.println(" ================================== ");
tx.commit(); //이시점에 DB 쿼리가 날라감

 

 

물론 LAZY로 설정하고

 

List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();

for(Member m:members){
System.out.println("m.getTeam().getName() = " + m.getTeam().getName());
}

 

이렇게 해줘도 뭐 foreach로 team 정보 가져올때마다 team을 계속 부르는건 매한가지지만 대안이있다.

 

1. fetch 조인 (한방쿼리) - List<Member> members = em.createQuery("select m from Member m join fetch m.team", Member.class).getResultList();

2. 엔티티그래프(어노테이션) -모름

3. 배치사이즈 -모름

 

일단 3가지가 있다고 한다.

 

 

 

실무에서는 그냥 무조건 지연로딩(LAZY)를 사용하는것이 좋다.

 

지연로딩 때려박고 프록시 초기화하면 된다.

  Comments,     Trackbacks
[JPA]지연로딩 - LAZY(관련 클래스는 프록시 객체로 가져옴)

 

 

위의 사진과 아래코드를 같이 보자.

 

지연로딩이란 것은 말그대로 로딩을 지연한다 는뜻이다.

 

만약 멤버를 호출을 한다고 치자.

 

Member m = em.find(Member.class, member1.getId()); 이렇게 호출한다고 치면

select 문을 때릴거고 값을 가져와 m에 넣게 된다.

 

이때 아무것도 하지 않으면 자동으로 JPA가 Team과 조인을 해서 Team 정보까지 불러온다.

 

하지만 필요도 없는 Team을 자꾸 호출하기는 싫은거다.

 

이럴때 아래처럼 Member 클래스에 아래처럼 정의한다.

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="TEAM_ID")
private Team team;

 

이렇게 fetch속성을 LAZY로 주게 되면. 지연 로딩이 적용된다.

 

그냥 Member만 불러오게 되고 find 를 해도 member 테이블만 뒤져서 member만 가져온다.

 

그때 여기서 System.out.println("m = " + m.getTeam().getClass()); 를 하면 뭐가 나올까??

 

신기하게도 Team$HibernateProxy$kewiOF6H <- 처럼 프록시 객체가 출력된다....

 

어케 된걸까?

 

단순히 Member만 뽑아 주는줄 알았지만 관련된 Team객체도 프록시로만 일단 전달은 해준다는 것이다.

 

Team 프록시 객체에서 Team 관련 작업을 하는 순간 Team 프록시가 초기화가 되고, 그때 Team select 쿼리도 날라온다.

 

아래는 예제 코드이고 빨간 글씨에 다다르면 그때 Team 프록시 객체도 초기화 된다.

 

 

Team team = new Team();
team.setName("teamA");

Member member1 = new Member();
member1.setUserName("member1");
member1.setTeam(team);

em.persist(team);
em.persist(member1);


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

Member m = em.find(Member.class, member1.getId());

System.out.println("m = " + m.getTeam().getClass());
System.out.println(" ============================ ");
m.getTeam().getName();


System.out.println(" ================================== ");
tx.commit(); //이시점에 DB 쿼리가 날라감

  Comments,     Trackbacks