Life Developer
인생 개발자
Developer (136)
[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]지연로딩 - 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
[JPA]프록시객체 - getReference (와 find 차이)

 

프록시는 이렇게 만든다.

 

.getReference(~~~)

 

실제 사용 예는 밑에서..

 

 

 

 

 

 

 

아래 설명도 써놨지만 Member findMember = em.getReference(Member.class, member.getId());

이런 식으로 사용한다.

 

곧 findMember는 프록시이다.


Member findMember = em.find(Member.class, member.getId()); 와는 달리

Member findMember = em.getReference(Member.class, member.getId()); 를 딱 실행하면

 

select 쿼리가 db로 안날라간다.

 

즉, findMember는 진짜 엔티티가 아니라 가짜 엔티티이며, 실제 객체의 참조를 보관한다.

Member findMember = em.getReference(Member.class, member.getId()); 를 실행하면

프록시 객체를 가져온다. (가짜 엔티티, 엔티티가 아님)

그 객체에는 get요청한 Id 정보만 있다.

그래서 System.out.println(findMember.getId()); 를 하면 Id가 출력이 된다.

db에 갈 필요가 없다는것이다.

 

근데 딱 findMember.getUserName() 을 하는순간 가짜 엔티티(프록시객체)에는 name이 없기 때문에

DB를 통해 name 값을 가져온다. 이것을 프록시 객체의 초기화라고 한다.

 

프록시의 초기화는 딱 한번만 이루어진다.

 

 

 

 

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.getClass()); //class hellojpa.Member$HibernateProxy$7XonaGYN 가짜클래스임(proxy가만듦)
System.out.println(findMember.getId());//이미 위에서 id로 찾았기 때문에 db에서 안가져옴
System.out.println(findMember.getUserName()); //모르는 정보니까 이때 db에 쿼리를 날려서 team이랑 조인함

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

 

 

 

 

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

 

그리고 주의 할점도 있다.

 

아래처럼 진짜 member entity를 영속성 컨텍스트에 등록하고 clear 없이 getReference를 하면 영속성 컨텍스트에

등록되어 있는 member를 가지고 온다.

그래서 이전것을 재활용한다.

 

하지만 clear()를 호출해 영속성 컨텍스트를 비우고 getReference를 하면 프록시객체를 불러오게 되므로

서로 같은 객체가 아니게 된다.

 

getReference를 했는데 프록시 객체가 아닌 상태가 되는것이다.......ㅎ후

 

Member m1= em.find(Member.class,member1.getId());
System.out.println("m1.getClass() = " + m1.getClass());
em.clear();
Member m2= em.getReference(Member.class,member1.getId());
System.out.println("m2.getClass() = " + m2.getClass());

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

 

 

여기서 더 심화해보자.

 

Member m1= em.getReference(Member.class,member1.getId());
System.out.println("m1.getClass() = " + m1.getClass());

Member m2= em.find(Member.class,member1.getId());
System.out.println("m2.getClass() = " + m2.getClass());
System.out.println("m1==m2"+(m1.getClass() == m2.getClass()));

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

 

그렇다면 이건 어떻게 나올까?

1. 내가 예상한것은 영속성 컨텍스트에 프록시 객체가 담길것이고,

2. find 해서 가져오려 했으나 영속성 컨텍스트에 Member객체가 있기에 db로 안가고

    프록시 객체를 그냥 가져와서 true가 나온다...

 

가 예상이었다.

 

근데 결과는 2번이 약간 달랐다.

일단 db로 쿼리는 select 때린다.

근데 가져온것은 프록시 객체.  ㅡㅡ.... 아니 DB왜 간거지.

 

그냥 DB까지 갔으나 영속성 컨텍스트에 있는것을 가져다 쓴다...기억하자

 

 

 

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

 

프록시 초기화가 되었는지 확인 하는법도 있다.

EntityManageFactory 객체 .getPersistenceUnitUtil().isLoaded(프록시객체) 를 하면 boolean 값으로 나오게 된다.!

 

Member m1 = em.getReference(Member.class, member1.getId());
System.out.println("m1.getClass() = " + m1.getClass());

m1.getId();
System.out.println("초기화 되었냐?" + emf.getPersistenceUnitUtil().isLoaded(m1));
m1.getUserName();
System.out.println("초기화 되었냐?" + emf.getPersistenceUnitUtil().isLoaded(m1));

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

 

 

 

여기까지 이해했으면 프록시 정복한거임 푸헤ㅔㅎ

 

 

  Comments,     Trackbacks
[JPA]@MappedSuperClass - 공통된 컬럼 관리

공통의 매핑정보가 필요할때 사용.

 

 

공통된놈이 객체마다 있으면 이것도 중복이다.

 

이것을 JPA에서 공통클래스를 만들어 관리할수 있다.

 

아래처럼 클래스 하나 만들어서 @MappedSuperClass를 해주면 된다.

물론 @Column을 사용해서 이름도 바꿀수있다.

 

그런다음 사용할 클래스에 extends를 쓰면됨.

 

근데 상속관계 매핑은 아니다!!

 

Entity가 아니다. 테이블도 만들지 않음.

 

MappedSuperClass는 조회 검색 불가함.

 

직접 생성해서 사용하는게 아니라 abstract 클래스로 만드는것 추천

 

 

 

 

 

@MappedSuperclass
public class BaseEntity {

@Column(name="Insert_Member")
private String createBy;
private LocalDateTime createDate;
private String lastModifiedBy;
private LocalDateTime lastModifiedDate;

public String getCreateBy() {
return createBy;
}

public void setCreateBy(String createBy) {
this.createBy = createBy;
}

public LocalDateTime getCreateDate() {
return createDate;
}

public void setCreateDate(LocalDateTime createDate) {
this.createDate = createDate;
}

public String getLastModifiedBy() {
return lastModifiedBy;
}

public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}

public LocalDateTime getLastModifiedDate() {
return lastModifiedDate;
}

public void setLastModifiedDate(LocalDateTime lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}

  Comments,     Trackbacks
[JPA] 상속매핑(Inheritance) 전략 - TABLE_PER_CLASS

쓰면 안되는 전략..

ORM세계는 객체지향 프로그래머, DBA 의 역할이있는데 둘다 꺼려함.

뭔가 묶어낼게 없음.

만약 정산같은거 한다고 하면 복잡해지는거임. 노답이다 그냥.

 

장점 : not null제약조건 사용가능. (DBA가 이딴 이유로 이걸 쓰라고 하면 . 뺨떄리면됨)

 

 

 

상속전략에 마지막이 TABLE_PER_CLASS이다.

이 전략을 간단히 말하면 부모 테이블은 그냥 만들지도 않는다.

부모테이블의 속성을 자식테이블에 그냥 테이블 마다 다때려박는다. 무식하다.

아래처럼 테이블이 만들어진다.

DTYPE 그딴거 필요도 없음.(@Discriminator)

 

 

 

 

뭐 이런식이다.

 

뭔가 비효율적인 느낌이 든다.

 

우리가 select 때려서 조회한다고 치면 만약 ITEM_ID가 5라고 하자.

그럼 5를 찾으려면 테이블 3개 다뒤져봐야한다. 미치고 환장하는거다ㅋ

 

직접 실험해보았다.

 

결과는?

 

 

ㅋㅋ union all하고 난리남.

 

아주 골떄리는 놈이다. 그걸 자동으로 해주는 JPA 이놈도 미띤놈임

  Comments,     Trackbacks
[JPA] 상속매핑(Inheritance) 전략 - JOINED*

이게 정석 전략임

 

장점 : 테이블 정규화, 외래키 참조 무결성 제약조건 활용 가능,저장공간 효율, 설계가 깔끔함

단점 : 조회시 조인을 많이함. 성능저하(조회쿼리 복잡), 저장시 insert 쿼리 2번 호출(생각보다 단점 아님)

 

싱글 테이블 상속 전략과는 약간 차이가 있다.

그냥 @Inheritance에 타입을 JOINED라고만 고치면 된다.

그럼 테이블이 각각 생성이된다.

뭐 이런식으로.

 

 

여기서 나는 DTYPE 의 이름을 TYPE_NAME으로 내맘대로 정해주었다.

그럼 컬럼명이 바뀐다.

JOINED 은 SINGLE_TABLE과는 달리 @DiscriminatorColumn 을 안써줘도 된다.

나는 굳이 써준거다.

빼면 이렇다.

 

 

굳이 타입을 정하지 않아도 뭐 개발하는 사람 입장에서는 무엇인지 유추가 가능하니까 안써도 된다.

시스템 운영할때는 사용하는것이 편하다.

 

JOINED는 테이블이 어떻게 생성될까.

create table Item (
       id bigint not null,
        name varchar(255),
        price integer not null,
        primary key (id)
    )

 

create table Movie (
       actor varchar(255),
        director varchar(255),
        id bigint not null,
        primary key (id)
    )

 

알아서 이렇게 생성해준다.

FK는? 이것도 뭐 자동이지 뭐.

alter table Movie 
       add constraint FK5sq6d5agrc34ithpdfs0umo9g 
       foreign key (id) 
       references Item

 

ㅋㅋ대박이네!

 

 

 

 

 

 

 

아래는 실습코드다.

 

 

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name="TYPE_NAME")
public class Item {
@Id
@GeneratedValue
private Long id;

private String name;
private int price;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getPrice() {
return price;
}

public void setPrice(int price) {
this.price = price;
}
}

 

 

 

@Entity
@DiscriminatorValue("MV")
public class Movie extends Item{
private String director;
private String actor;

public String getDirector() {
return director;
}

public void setDirector(String director) {
this.director = director;
}

public String getActor() {
return actor;
}

public void setActor(String actor) {
this.actor = actor;
}
}

  Comments,     Trackbacks
[JPA] 상속매핑(Inheritance) 전략 - SINGLE_TABLE

장점 : 조인이 필요가없다. 성능빠름, 조회쿼리가 단순함

단점 : 자식 entity가 안가지는 컬럼의 값을 null 허용해야함....(치명적, DBA 개빡침), 테이블이 커질수있다. 오히려 역효과 날수 있다.

 

 

상속을 받아서 테이블을 만들수도 있다.

 

기본적으로 @Entity는 당연히 써줘야 하는거고.

단순히 Movie클래스가 Item 클래스를 extends로 상속받는다고 입력하면 됨.

그리고 부모에 @Inheritance를 입력하면 default 값으로 상속 타입을 SINGLE_TABLE을 사용한다.

그럼 성능상 좋긴하다. 

그리고 싱글 타입은 DTYPE이라고 자동 생성을해준다.

무슨 클래스를 가져왔는지 표시를 해준다는 것이다.

실행해보면 테이블 생성시 DTYPE이라고 자동 생성된다.

그리고 DTYPE이 아니라 컬럼명을 바꾸고 싶다면 @DiscriminatorColumn(name="~~~") 을 써주면 된다.

진짜 너무 JPA에서 자동으로 이놈이 무슨클래스지, 어디서 온놈이지? 라는것을 자동으로 생각해주니까 좀 이상하다.

적응이 안된다.

 

 

설마 이것까지 자동으로 해줄까? 라는 의심을 품고 실행하면 해주고 있다.

테이블을 DB에서 쿼리칠 필요가 없어서 미친듯이 편하다. 이런 미띤 ㅡㅡ

 

아래는 싱글 테이블 실습.

 

 

create table Item (
       TYPE_NAME varchar(31) not null,
        id bigint not null,
        name varchar(255),
        price integer not null,
        artist varchar(255),
        autor varchar(255),
        isbn varchar(255),
        actor varchar(255),
        director varchar(255),
        primary key (id)
    )

 

 

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="TYPE_NAME")
public class Item {
@Id
@GeneratedValue
private Long id;

private String name;
private int price;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getPrice() {
return price;
}

public void setPrice(int price) {
this.price = price;
}
}

 

 

 

@Entity
@DiscriminatorValue("MV")
public class Movie extends Item{
private String director;
private String actor;

public String getDirector() {
return director;
}

public void setDirector(String director) {
this.director = director;
}

public String getActor() {
return actor;
}

public void setActor(String actor) {
this.actor = actor;
}
}

 

 

  Comments,     Trackbacks