이전 시간에 만들었던 database table 정보들을 모두 삭제하도록 하자.
DROP TABLE laptop CASCADE;
DROP TABLE alien CASCADE;
DROP TABLE alien_laptop CASCADE;
삭제했다면 Alien
class를 다음과 같이 수정하도록 하자.
@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 +
'}';
}
}
@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 +
'}';
}
}
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가 존재한다.
이번에는 새로운 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}]}
Alien
에 Laptop
과 관련된 정보들이 loading된 것을 볼 수 있다.
그렇다면 만약 lazy loading을 하고 싶지 않다면 어떻게 해야할까?? 위에서 언급했듯이 collection에 관해서 lazy loading이 발생한다고 했는데, collection들은 보통 다른 entity에 대한 관계성을 가지는 것들이다. 따라서, @OneToMany
나 @ManyToMany
와 같은 관계성을 갖을 수 밖에 없다. 이 annotation에 fetch
로 FetchType.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이 되지 않는다.
참고로 get
과 load
가 있는데, 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문만 실행되는 것을 볼 수 있다.
복잡한 query를 진행하고 싶다면, HQL을 사용하는 것이 좋다. HQL은 SQL의 파생 버전으로 대부분의 문법은 동일하지만, 단지 SQL에서의 entity에 대해서 java class 이름을 쓰고, SQL column들에 대해서는 java 맴버 변수 이름을 사용한다.
가령 student
라는 table이 HQL에서는 다음과 같다.
FROM Student;
다음은 HQL
을 통해서 Laptop
data들을 모두 가져오는 코드이다.
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
데이터들을 잘 가져온 것을 볼 수 있다.