Java 재활 훈련 18일차 - Hibernate3 - Eager, Lazy fetching, Cache

0

java

목록 보기
18/18

Hibernate

Eager and Lazy fetching

이전 시간에 만들었던 database table 정보들을 모두 삭제하도록 하자.

DROP TABLE laptop CASCADE;
DROP TABLE alien CASCADE;
DROP TABLE alien_laptop CASCADE;

삭제했다면 Alien class를 다음과 같이 수정하도록 하자.

  • Alien
@Entity
public class Alien {
    @Id
    private int aid;
    private String aname;
    private String tech;
    @OneToMany
    private List<Laptop> laptop;

    public void setAid(int aid) {
        this.aid = aid;
    }

    public void setAname(String aname) {
        this.aname = aname;
    }

    public void setTech(String tech) {
        this.tech = tech;
    }

    public void setLaptop(List<Laptop> laptop) {
        this.laptop = laptop;
    }

    @Override
    public String toString() {
        return "Alien{" +
                "aid=" + aid +
                ", aname='" + aname + '\'' +
                ", tech='" + tech + '\'' +
                ", laptop=" + laptop +
                '}';
    }
}
  • Laptop
@Entity
public class Laptop {
    @Id
    private int lid;
    private String brand;
    private String model;
    private int ram;

    public void setLid(int lid) {
        this.lid = lid;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public void setRam(int ram) {
        this.ram = ram;
    }

    @Override
    public String toString() {
        return "Laptop{" +
                "lid=" + lid +
                ", brand='" + brand + '\'' +
                ", model='" + model + '\'' +
                ", ram=" + ram +
                '}';
    }
}
  • Main
public class Main {
    public static void main(String[] args) {
        Alien alien = new Alien();

        Laptop l1 = new Laptop();
        l1.setLid(1);
        l1.setBrand("Asus");
        l1.setModel("Rog");
        l1.setRam(16);

        Laptop l2 = new Laptop();
        l1.setLid(2);
        l1.setBrand("Dell");
        l1.setModel("XPS");
        l1.setRam(32);

        alien.setAid(104);
        alien.setAname("Navin");
        alien.setTech("Java");
        alien.setLaptop(Arrays.asList(l1, l2));

        Configuration cfg = new Configuration().configure();
        cfg.addAnnotatedClass(Alien.class);
        cfg.addAnnotatedClass(Laptop.class);

        SessionFactory sf = cfg.buildSessionFactory();
        Session session = sf.openSession();
        Transaction tx = session.beginTransaction();

        session.persist(l1);
        session.persist(l2);
        session.persist(alien);
        tx.commit();

        Alien a1 = session.get(Alien.class, 104);
        System.out.println(a1);

        session.close();
        sf.close();
    }
}

위 코드를 실행하면 다음과 같은 결과가 나온다.

Hibernate: 
    create table Alien (
        aid integer not null,
        aname varchar(255),
        tech varchar(255),
        primary key (aid)
    )
Hibernate: 
    create table Alien_Laptop (
        Alien_aid integer not null,
        laptop_lid integer not null
    )
Hibernate: 
    create table Laptop (
        lid integer not null,
        brand varchar(255),
        model varchar(255),
        ram integer not null,
        primary key (lid)
    )
...
Alien{aid=104, aname='Navin', tech='Java', laptop=[Laptop{lid=2, brand='Dell', model='XPS', ram=32}, Laptop{lid=0, brand='null', model='null', ram=0}]}

그런데 왜 인지는 모르겠지만 session.get을 실행했는데, select문이 없는 것을 볼 수 있다. 이는 기본적으로 cache 때문에 발생하는 문제이다. hibernate는 jdbc가 제공하지 않는 cache라는 개념을 제공한다. 이는 hibernate의 최대 장점으로 level 1 cache와 level 2 cache가 존재한다.

  1. level 1 cache: session-level에서의 cache를 말한다. (기본 활성화)
  2. level 2 cache: Ehcache, Caffeine 등을 통한 session 보다 상위 level의 cache이다. (기본 비활성화)

이번에는 새로운 session을 열어서 실행해보도록 하자. 새로운 session을 열어서 query를 진행하는 이유는 level 1 cache에 영향을 받지 않기 위해서이다. 즉, select문이 어떻게 실행되는 지를 보고 싶어서 이다.

public class Main {
    public static void main(String[] args) {
        ...
        SessionFactory sf = cfg.buildSessionFactory();
        Session session = sf.openSession();
        Transaction tx = session.beginTransaction();

        session.persist(l1);
        session.persist(l2);
        session.persist(alien);
        tx.commit();

        Alien a1 = session.get(Alien.class, 104);
        System.out.println(a1);

        session.close();

        Session session1 = sf.openSession();
        Alien a2 = session1.get(Alien.class, 104);
        //System.out.println(a2);

        session1.close();
        sf.close();
    }
}

새로운 session인 session1을 열어서 id가 104인 alien instance를 가져오고 있다.

그런데, 실행 결과를 잘보면 sql문이 재밌다.

Hibernate: 
    select
        a1_0.aid,
        a1_0.aname,
        a1_0.tech 
    from
        Alien a1_0 
    where
        a1_0.aid=?

뭔가가 하나 빠져있는데, Laptop class에 대한 인스턴스 정보가 없다. 이는 사실 hibernates에서 List와 같은 collection들에 대해서 lazy loading을 사용하기 때문이다.

재밌는 것은 System.out.println으로 해당 인스턴스를 출력하려고 하면 로딩된다는 것이다.

Session session1 = sf.openSession();
Alien a2 = session1.get(Alien.class, 104);
System.out.println(a2);

실행 결과를 확인하면 다음과 같다.

    select
        l1_0.Alien_aid,
        l1_1.lid,
        l1_1.brand,
        l1_1.model,
        l1_1.ram 
    from
        Alien_Laptop l1_0 
    join
        Laptop l1_1 
            on l1_1.lid=l1_0.laptop_lid 
    where
        l1_0.Alien_aid=?
Alien{aid=104, aname='Navin', tech='Java', laptop=[Laptop{lid=2, brand='Dell', model='XPS', ram=32}, Laptop{lid=0, brand='null', model='null', ram=0}]}

AlienLaptop과 관련된 정보들이 loading된 것을 볼 수 있다.

그렇다면 만약 lazy loading을 하고 싶지 않다면 어떻게 해야할까?? 위에서 언급했듯이 collection에 관해서 lazy loading이 발생한다고 했는데, collection들은 보통 다른 entity에 대한 관계성을 가지는 것들이다. 따라서, @OneToMany@ManyToMany와 같은 관계성을 갖을 수 밖에 없다. 이 annotation에 fetchFetchType.EAGER를 설정해주면 된다.

@Entity
public class Alien {
    @Id
    private int aid;
    private String aname;
    private String tech;
    @OneToMany(fetch = FetchType.EAGER)
    private List<Laptop> laptop;
    ...
}

fetch = FetchType.EAGER 이렇게 두면 lazy loading이 되지 않는다.

참고로 getload가 있는데, get은 eager loading으로 데이터를 바로 로딩하는 sql문을 실행한다. 반면에 load는 데이터를 바로 로딩하는 sql문을 실행하지 않고 데이터를 요청할 때 데이터를 로딩하는 lazy loading을 지원한다. load는 이제는 deprecated 되었으므로 사용하진 않고 byId().getReference를 대신 사용한다.

Laptop l3 =session.byId(Laptop.class).getReference(1);
// System.out.println(l3);

Laptop l4 = session.get(Laptop.class, 2);
// System.out.println(l4);

실행시키면 하나의 select sql문만 실행되는 것을 볼 수 있다.

HQL(Hibernate Query Language)

복잡한 query를 진행하고 싶다면, HQL을 사용하는 것이 좋다. HQL은 SQL의 파생 버전으로 대부분의 문법은 동일하지만, 단지 SQL에서의 entity에 대해서 java class 이름을 쓰고, SQL column들에 대해서는 java 맴버 변수 이름을 사용한다.

가령 student라는 table이 HQL에서는 다음과 같다.

FROM Student;

다음은 HQL을 통해서 Laptop data들을 모두 가져오는 코드이다.

  • Main.java
public class Main {
    public static void main(String[] args) {
        Laptop l1 = new Laptop();
        l1.setLid(1);
        l1.setBrand("Asus");
        l1.setModel("Rog");
        l1.setRam(16);

        Laptop l2 = new Laptop();
        l2.setLid(2);
        l2.setBrand("Dell");
        l2.setModel("xms");
        l2.setRam(32);

        Configuration cfg = new Configuration().configure();
        cfg.addAnnotatedClass(Laptop.class);

        SessionFactory sf = cfg.buildSessionFactory();
        Session session = sf.openSession();
        Transaction tx = session.beginTransaction();

        session.persist(l1);
        session.persist(l2);

        // select * from laptop where ram=32 -> SQL
        // from Laptop where ram=32 -> HQL
        Query query = session.createQuery("from Laptop");
        List<Laptop> laptops = query.getResultList();
        System.out.println(laptops);
        tx.commit();

        session.close();
        sf.close();
    }
}

결과는 아래와 같다.

[Laptop{lid=1, brand='Asus', model='Rog', ram=16}, Laptop{lid=2, brand='Dell', model='xms', ram=32}]

l1, l2를 저장하고 HQL from Laptop을 통해서 laptop에 대한 data들을 모두 가져오는 코드이다. session.createQuery를 통해서 HQL을 실행할 수 있으며, query 객체에서 getResultList를 통해 결과를 가져올 수 있다.

그 결과는 List 형식으로 나온다.

이처럼 HQL에서 모든 column을 가져올 때에는 SELECT이 생략된다. table의 이름은 java 객체의 이름으로 처리해야한다.

아래와 같이 파라미터를 받을 수도 있다.

String brand = "Asus";
Query query = session.createQuery("from Laptop where brand like ?1");
query.setParameter(1, brand);

JDBC와 달리 hibernate의 경우 ?만이 아니라 ?1, ?2 등 숫자를 통해서 파라미터를 받는다. 따라서, query.setParameter에 주어진 첫번째 인자가 ? 뒤에 붙는 숫자에 해당하는 파라미터이며, 두 번째 인자가 그 값이다.

column을 적어준다면 select를 추가하여 다음과 같이 HQL을 사용할 수 있다.

select brand, model from Laptop where brand like ?1

단, select문으로 column들을 적어주면 배열 형식으로 column들의 값들이 나오기 때문에 List<Object[]>가 반환 타입이 된다.

public class Main {
    public static void main(String[] args) {
        Laptop l1 = new Laptop();
        l1.setLid(1);
        l1.setBrand("Asus");
        l1.setModel("Rog");
        l1.setRam(16);

        Laptop l2 = new Laptop();
        l2.setLid(2);
        l2.setBrand("Dell");
        l2.setModel("xms");
        l2.setRam(32);
        
        ...

        Query query = session.createQuery("select brand, model from Laptop");
        List<Object[]> laptops = query.getResultList();
        for (Object[] data : laptops) {
            System.out.println(data[0] + " " + data[1]);
        }
        ...
    }
}

결과는 아래와 같다.

Asus Rog
Dell xms

데이터들을 잘 가져온 것을 볼 수 있다.

0개의 댓글