Hibernate에서 제공하는 기본 API를 호출함으로써, Persistence 객체를 이용하여 특정
DB에 데이터를 입력,수정,삭제,조회하는 방법에 대해 알아보도록 한다.
단건 조회
get() 또는 load() 메소드를 호출하여 DB로부터 단건의 데이터를 조회할 수 있다.
get() 또는 load() 메소드 호출시 대상이 되는 Persistence 클래스와
Primary Key 값에 해당하는 속성값을 입력 인자로 전달해야 한다.
- get()
: 호출 시점에 SELECT 쿼리 실행
- load()
: 객체의 값이 실제로 필요한 시점에 쿼리 실행
Persistence 클래스인
Country
에
대한 매핑 정보가 다음과 같이 정의되어 있다라고 가정해 보자.
<class name="anyframe.sample.model.bidirection.Country" table="COUNTRY"
lazy="true" schema="PUBLIC">
<id name="countryCode" type="string">
<column name="COUNTRY_CODE" length="12" />
<generator class="assigned" />
</id>
...
</class>
Country의 식별자인 countryCode의 값을 이용하여 단건 Country 정보를 조회하고자 할 경우에는
HibernateBasicCRUDTest
의
assertCountryInfo(...) 메소드에서와 같이 호출하면 된다.
private void assertCountryInfo(String countryCode, Country country)
throws Exception {
Country result = (Country) session.get(Country.class, countryCode);
assertEquals("fail to match country id.", country.getCountryId(),
result.getCountryId());
assertEquals("fail to match country name.", country.getCountryName(),
result.getCountryName());
}
load() 메소드의 경우 SELECT 쿼리를 실행하지 않고, 전달된 식별자에 해당하는 객체의
Proxy를 리턴한 후, 해당 객체를 통해 테이블에 저장된 식별자 이외의 값 접근시 SELECT 문을 실행하여 결과값을
Proxy 객체에 저장한다. 다음과 같이 load() 메소드 수행 결과 전달된 객체의 클래스명을 출력해 보면,
Proxy 객체가 전달되었음을 알 수 있을 것이다.
User user = session.load(User.class, "test");
// expected to print : com.sds.emp…User$$EnhancerByCGLIB$$...
System.out.println(user.getClass().getName());
단건 저장
save() 또는 persist() 메소드를 호출하여 DB에 단건의 데이터를 추가할 수 있다.
save() 또는 persist() 메소드 호출시 대상이 되는 Persistence 객체를 입력 인자로 전달해야 한다.
- save()
: 단건의 데이터를 추가한 후, 해당 객체의 식별자를 return
- persist()
: 단건의 데이터 추가. return 값이 없음
신규 Country 정보를 추가하고자 할 경우에는
HibernateBasicCRUDTest
의
addCountry() 메소드에서와 같이 호출하면 된다.
private Country addCountry() throws Exception {
// 1. insert a new country information
Country country = new Country();
String countryCode = "COUNTRY-0001";
country.setCountryCode(countryCode);
country.setCountryId("KR");
country.setCountryName("Korea");
session.save(country);
// ...
}
Tip. A:B=1:m인 경우 A에 대한 save()
A 객체에서 Collection B에 대한 cascade 속성을 true로 정의하고, Collection B를 포함한 해당 객체 A에 대해
save() 메소드를 호출하는 경우를 가정해 보자. 상위 객체인 A에 대해서는 예상하는 바와 동일하게 동작하나, Collection B에
대해서는 saveOrUpdate()와 동일하게 동작함을 알 수 있다.
이를 확인하기 위해서 Country:Movie = 1:m 관계에 대한
HibernateSaveOrUpdateParentChildTest
의
각 테스트 메소드 실행 결과를 중심으로 확인해보도록 하자.
1. DB에 추가되어 있지 않은 Country 정보에 대해 save() 메소드 호출하는 경우
Transaction Commit시 신규 생성한 Country 객체에 대해 INSERT문이 실행된다.
public void testAddCountryCallingSave() throws Exception {
// 1. try to insert a country information without movies
newSession();
Country country1 = makeNewCountry();
session.save(country1);
closeSession();
// ...
}
* 콘솔 - 실행된 SQL문
insert
into PUBLIC.COUNTRY (COUNTRY_ID, COUNTRY_NAME, COUNTRY_CODE)
values ('KR', 'Korea', 'COUNTRY-0001')
2. DB에 추가되어 있지 않은 Country 정보에 대해 saveOrUpdate() 메소드를 호출하는 경우
신규 생성한 Country 객체가 DB에 존재하지 않으므로 Transaction Commit시
해당 객체에 대해 INSERT문이 실행된다.
public void testAddCountryCallingSaveOrUpdate() throws Exception {
// 1. try to insert a country information without movies
newSession();
Country country1 = makeNewCountry();
session.saveOrUpdate(country1);
closeSession();
// ...
}
* 콘솔 - 실행된 SQL문
insert
into PUBLIC.COUNTRY (COUNTRY_ID, COUNTRY_NAME, COUNTRY_CODE)
values ('KR', 'Korea', 'COUNTRY-0001')
3. DB에 추가되어 있지 않은 Movie 정보를 포함한 Country에 대해 update() 메소드를 호출하였을 경우
첫번째 Transaction에서 신규 Country 정보를 추가하였고, 두번째 Transaction에서 앞서 등록한 Country 객체에
신규 Movie Collection 정보를 셋팅한 후 update() 메소드를 호출한 경우이다. 두번째 Transaction Commit시
Country 객체에 대해서는 변경 정보가 있다면 UPDATE문이 실행되고, 신규 Movie Collection 정보에 대해서는
INSERT문이 실행된다.
public void testAddMoviesCallingUpdate() throws Exception {
// 1. try to insert a country information without movies
newSession();
Country country1 = makeNewCountry();
session.save(country1);
closeSession();
// 2. try to insert a country information with movies.
newSession();
Country country2 = makeNewMovieSet(country1.getCountryCode());
session.update(country2);
closeSession();
// ...
}
* 콘솔 - 실행된 SQL문
// 첫번째 Transaction
insert
into PUBLIC.COUNTRY (COUNTRY_ID, COUNTRY_NAME, COUNTRY_CODE)
values ('KR', 'Korea', 'COUNTRY-0001')
...
// 두번째 Transaction
insert
into PUBLIC.MOVIE (COUNTRY_CODE, TITLE, DIRECTOR, RELEASE_DATE, MOVIE_ID)
values ('COUNTRY-0001', 'My Sassy Girl', 'Jaeyong Gwak', 2001-07-27, 'MV-00001')
insert
into PUBLIC.MOVIE (COUNTRY_CODE, TITLE, DIRECTOR, RELEASE_DATE, MOVIE_ID)
values ('COUNTRY-0001', 'My Little Bride', 'Hojun Kim', 2004-04-02, 'MV-00002')
...
4. DB에 추가되어 있지 않은 Country 정보를 추가한 후, Movie 정보에 대해 save() 메소드를 호출하였을 경우
첫번째 Transaction에서 신규 Country 정보를 추가하였고, 두번째 Transaction에서 앞서 등록한 Country 객체에
신규 Movie Collection 정보를 셋팅한 후 save() 메소드를 호출한 경우이다. 3번의 경우와 동일하게 동작한다.
public void testAddMoviesCallingSave() throws Exception {
// 1. try to insert a country information without movies
newSession();
Country country1 = makeNewCountry();
session.save(country1);
closeSession();
// 2. try to insert a country information with movies.
newSession();
Country country2 = makeNewMovieSet(country1.getCountryCode());
session.save(country2);
closeSession();
// ...
}
* 콘솔 - 실행된 SQL문
// 첫번째 Transaction
insert
into PUBLIC.COUNTRY (COUNTRY_ID, COUNTRY_NAME, COUNTRY_CODE)
values ('KR', 'Korea', 'COUNTRY-0001')
...
// 두번째 Transaction
insert
into PUBLIC.MOVIE (COUNTRY_CODE, TITLE, DIRECTOR, RELEASE_DATE, MOVIE_ID)
values ('COUNTRY-0001', 'My Sassy Girl', 'Jaeyong Gwak', 2001-07-27, 'MV-00001')
insert
into PUBLIC.MOVIE (COUNTRY_CODE, TITLE, DIRECTOR, RELEASE_DATE, MOVIE_ID)
values ('COUNTRY-0001', 'My Little Bride', 'Hojun Kim', 2004-04-02, 'MV-00002')
...
단건 수정
update() 메소드를 호출하여 DB의 단건 데이터를 수정할 수 있다.
update() 메소드 호출시 대상이 되는 Persistence 객체를 입력 인자로 전달해야 한다.
입력 인자로 전달된 객체에는 모든 값이 설정되어 있어야 함에 유의하도록 한다. 속성값이 설정되어
있지 않은 경우 해당 속성값이 null로 저장된다.
기 등록된 Country 정보를 수정하고자 할 경우에는
HibernateBasicCRUDTest
의
testUpdateCountry() 메소드에서와 같이 호출하면 된다.
public void testUpdateCountry() throws Exception {
// 1. insert a new country information
Country country = addCountry();
// 2. update a country information
country.setCountryName("Republic of Korea");
session.update(country);
// ...
}
특정 객체가 Persistent 상태이고, 동일한 Session 내에서 해당 객체의 속성 값에 변경이 발생한 경우
update() 메소드를 직접적으로 호출하지 않아도 트랜잭션 종료 시점에 Hibernate에 의해 변경 여부가
체크되어 변경 사항이 DB에 반영된다.
public void testUpdateCountry() throws Exception {
// start transaction
Country country = addCountry();
country.setCountryName("Republic of Korea");
// commit. successful update!!!
}
단건 저장 또는 수정
기 등록된 객체에 대해 save() 메소드를 호출한 경우 또는 DB에 존재하지 않는 객체에 대해 update() 메소드를
호출한 경우 testAddCountryCallingUpdate() 메소드에서처럼 Exception이 발생한다.
public void testAddCountryCallingUpdate() throws Exception {
// 1. start a new session and transaction
newSession();
// 2. try to insert a country information without movies
Country country1 = makeNewCountry();
session.update(country1);
// 3. close session
try {
closeSession();
fail("expected throw HibernateException");
} catch (Exception e) {
assertTrue("fail to check exception type.",
e instanceof HibernateException);
}
}
두 메소드(save(), update())의 특징을 포함한 saveOrUpdate() 메소드는 해당 객체가 존재하는 경우에는
update()와 같은 역할을 수행하고 존재하지 않을 경우에는 save()를 수행한다.
saveOrUpdate() 메소드 호출시 대상이 되는 Persistence 객체를 입력 인자로 전달해야 한다.
saveOrUpdate() 메소드는
HibernateSaveOrUpdateParentChildTest
의
testAddCountryCallingSaveOrUpdate() 메소드에서와 같이 호출하면 된다.
public void testAddCountryCallingSaveOrUpdate() throws Exception {
// 1. try to insert a country information without movies
newSession();
Country country1 = makeNewCountry();
session.saveOrUpdate(country1);
closeSession();
// ...
}
복수건 저장
하나의 트랜잭션 내에서 동일한 Persistence 클래스에 대해 복수건의 데이터 저장 또는 수정이 발생할 경우에는
loop을 수행하면서 save(), update() 메소드를 호출해 주도록 한다. 단, 이 때 1st Level Cache, 2nd
Level Cache에 Persistent 상태의 객체들이 Caching되면서 memory overflow가 발생할 수 있으므로
로직 구성에 주의가 필요하다.
- 2nd Level Cache Mode : 해당 메소드 수행시에는 2LC를 적용하지 않도록 Cache Mode를 IGNORE로 설정.
- Session Flush : memory size를 고려하여 적절한 수의 Persistence 객체에 대한 save()가 이루어진 후에
session.flush() 메소드를 호출하여 DB에 반영할 수 있도록 한다. 한번에 flush할 객체의 수는 hibernate
configuration file (hibernate.cfg.xml) 내에 정의한 hibernate.jdbc.batch_size와 동일하게 맞추는 것이 좋다.
hibernate.jdbc.batch_size 속성에 대해 알고자 하면, 여기
를 참조하도록 한다.
- 1st Level Cache Clear : memory size를 고려하여 적절한 수의 Persistence 객체에 대한 save()가 이루어진 후에
1LC에 Caching된 Persistent 상태의 객체들을 지워주도록 한다.
다음은 하나의 트랜잭션 내에서 복수건의 데이터를 저장하는 testMultiSave()를 포함한
HibernateMultiDataSaveTest
의
일부이다.
public void testMultiSave() throws Exception {
session.setCacheMode(CacheMode.IGNORE);
// insert country
for (int i = 0; i < 90; i++) {
Country country = new Country();
String countryCode = "COUNTRY-000" + i;
country.setCountryCode(countryCode);
country.setCountryId("KR" + i);
country.setCountryName("Korea" + i);
session.save(country);
if (i != 0 && i % 9 == 0) {
session.flush();
session.clear();
}
}
}