Hibernateのお勉強、やりなおし(Hibernate Rebirth)
動機
最初にHibernateを勉強して、早数年が経ち、その後Perlなどの仕事が多くて、あまりさわっていない間にバージョンも3へと進化し、記憶もだいぶ曖昧になってきて、もともとわかっていなかった部分などが絡み合って、こりゃ駄目だあ!と諦め、再度勉強し直すことを、ここに宣言します。
とは言え、1から10まで復習する時間は毛頭ありません。したがって、次の観点を中心に調べたり、実験したりしたいと思います。
- テーブルの関係、「1:1」、「1:多」、「多:多」の復習と、そのhbmの定義
- 前項に絡むことだけど、cascadeの挙動(特に「多:多」などの場合)
- inverseの意味をしっかりつかむ
はじめに
開発の方向性
最近使っていたときの開発の方向は
- DBをモデリングソフトなどで設計
- 前項を元にスキマーを作成し、DB・テーブルを構築
- DTOを作成
- 前項にXDOCLET用のタグを書いておき、hbmファイルを作成してもらう
- hibernate.cfg.xmlに増えた分のhbmを追加
- DAOを作成
というような流れで作成していましたが、視点を変えるためにも、方向を変えてみようと思います。つまり、
- hbmファイルを作成
- hibernate-toolsを使って、DTOを自動作成
- schemaexportでスキーマを作成、DB・テーブルを構築
- hibernate.cfg.xmlに増えた分のhbmを追加
- DAOを作成
という感じです。まずはHibernate用のマッピングファイルから作成しようというわけです。ただそのためには、多少環境を整える必要があります。
まずは、環境設定
-
J2EEのインストール(単にHibernateのtoolsで使うので・・・)
http://java.sun.com/javaee/downloads/index.jspからダウンロード。
[<li></li> is illegal in <li>][<li></li> is illegal in <li>]- hbm2java---hbmからDTOを作成
- schemaexport---hbmからスキマーを作成
を作成します。
<target name="schemaexport" description="make tables on Database"> <taskdef name="hibernatetool" classname="org.hibernate.tool.ant.HibernateToolTask" classpathref="build.classpath"/> <hibernatetool destdir="./"> <classpath refid="build.classpath"/> <annotationconfiguration configurationfile="WEB-INF/resources/hibernate.cfg.new.xml"/> <hbm2ddl drop="false" create="false" export="true" outputfilename="schemaexport.sql" delimiter=";" format="true" /> </hibernatetool> </target> <target name="hbm2java" description="hbmファイルからjava Beanを作成"> <taskdef name="hibernatetool" classname="org.hibernate.tool.ant.HibernateToolTask" classpathref="build.classpath" > </taskdef> <hibernatetool destdir="src2"> <configuration configurationfile="WEB-INF/resources/hibernate.cfg.new.xml"> <fileset dir="src/main/java"> <include name="**/*.hbm.xml"/> </fileset> </configuration> <hbm2java/> </hibernatetool> </target>
ここで重要なのは
- DTOの書き出しディレクトリーを destdir="src2"として、間違って上書きされないようにしておくこと(何度泣いたことか!!)
- configurationfileの中身から、ご丁寧にも、作って欲しくないものまで作成するので、実際の hibernate.cfg.xmlじゃなく、hibernate.cfg.new.xmlにしていた方が良いかも。
-
DTO作成時に、先走って(これに気づくのに30分以上時間を無駄にした)
<mapping resource="com/chikkun/sample/database/Track.hbm.xml"/> <mapping resource="com/chikkun/sample/database/Artist.hbm.xml"/>を書き込んでおくと、
org.hibernate.DuplicateMappingException: Duplicate collection \ role mappingなどと怒られる。DTO作成後に上記を書き込み、そしてschemaを作成すること。
つまり、
<?xml version="1.0"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property> <property name="show_sql">false</property> <property name="connection.driver_class">org.postgresql.Driver</property> <property \ name="connection.url">jdbc:postgresql://localhost:5432/mpn</property> <property name="connection.username">chikkun</property> <property name="connection.password">kazukun</property> </session-factory> </hibernate-configuration>という、hbmが全く書かれていない状態でOK。
hbmの作成から、DTO、スキマー作成まで
DEVELOPER'S NOTEBOOKから
まずは楽曲を表すTrackテーブルとその音楽を演奏しているArtistテーブルを想定します。これは題名の「DEVELOPER'S NOTEBOOK」のmany-to-manyで使われている例をそのまま借用しています。イメージ的には次のようなER図を想定してます。

上記の図のように、今回作成しようと考えているテーブルは4つ。
- TRACKテーブル
-
TRACK_ID
このフィールドでTRACK_ARTISTとリンクし、それを通じてARTISTテーブルと多:多(many-to-many)の関係を付けている
- TITLE
- filePath
- playTime
- added
- volume
- TRACK_COMMENTSテーブル
-
TRACK_ID
このフィールドでTRACKテーブルとリンク
- COMMENT
- TRACK_ARTISTテーブル(関連テーブル)
-
TRACK_ID
このフィールドでTRACKテーブルとリンク
-
ARTIST_ID
このフィールドでARTISTテーブルとリンク
- ARTISTテーブル
-
ARTIST_ID
このフィールドでTRACK_ARTISTとリンクし、それを通じてTRACKテーブルと多:多(many-to-many)の関係を付けている
- NAME
Track.hbm.xml
というわけで、2つのhbmファイルを見ていきましょう。
<?xml version="1.0" encoding="Windows-31J"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.chikkun.sample.database.Track" table="TRACK">
<meta attribute="class-description">
Represents a single playable track in the music database.
@author chikkun
</meta>
<id name="id" type="int" column="TRACK_ID">
<meta attribute="scope-set">protected</meta>
<generator class="native"/>
</id>
<property name="title" type="string">
<meta attribute="use-in-tostring">true</meta>
<column name="TITLE" not-null="true" index="TRACK_TITLE"/>
</property>
<property name="filePath" type="string" not-null="true"/>
<property name="playTime" type="time">
<meta attribute="field-description">Playing time</meta>
</property>
<set name="artists" table="TRACK_ARTISTS">
<key column="TRACK_ID"/>
<many-to-many class="com.chikkun.sample.database.Artist"
column="ARTIST_ID"/>
</set>
<set name="comments" table="TRACK_COMMENTS">
<key column="TRACK_ID"/>
<element column="COMMENT" type="string"/>
</set>
<property name="added" type="date">
<meta attribute="field-description">
When the track was created
</meta>
</property>
<property name="volume" type="short" not-null="true">
<meta attribute="field-description">
How loud to play the track
</meta>
</property>
</class>
</hibernate-mapping>
ここでのポイントは
-
TRACKテーブルにはTRACK_IDというフィールドがあって、これをPKにする。また、クラス側のフィールド名(変数名)はidで、スコープはprotectedにする。さらに、シーケンスの作成方法はnative---DBに任せる。nativeだと、マニュアルにあるので、今回はPostgreSQLを利用するので、sequenceになるはず。
<Class name="com.chikkun.sample.database.Track" table="TRACK">
-
<id name="id" type="int" column="TRACK_ID"> <meta attribute="scope-set">protected</meta> <generator class="native"/> </id>[<li></li> is illegal in <li>]- identity supports identity columns in DB2, MySQL, MS SQL Server, Sybase and HypersonicSQL. The returned identifier is of type long, short or int.
-
sequence
uses a sequence in DB2, PostgreSQL, Oracle, SAP DB, McKoi or a generator in Interbase. The returned identifier is of type long, short or int
-
<property name="title" type="string"> <meta attribute="use-in-tostring">true</meta> <column name="TITLE" not-null="true" index="TRACK_TITLE"/> </property>テーブルには文字列のTITLE、DTOにはtitleがあり、このTITLEでTRACK_TITLEというindexを作成する。
また、DTOを作成するとtoStringにこれを入れよ、と指示。
[<li></li> is illegal in <li>][<li></li> is illegal in <li>][<li></li> is illegal in <li>][<li></li> is illegal in <li>]- one-to-manyの関係にある。
-
単方向の関係しかないので、双方向関連を表すone-to-many等は書き入れていない。
<set name="comments" table="TRACK_COMMENTS"> <key column="TRACK_ID"/> <element column="COMMENT" type="string"/> </set>
<property name="added" type="date"> <meta attribute="field-description"> When the track was created </meta> </property> <property name="volume" type="short" not-null="true"> <meta attribute="field-description"> How loud to play the track </meta> </property>
Artist.hbm.xml
次は、ARTISTテーブル。
<?xml version="1.0" encoding="Windows-31J"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.chikkun.sample.database.Artist" table="ARTIST">
<meta attribute="class-description">
Represents an artist who is associated
with a track or album.
@author chikkun
</meta>
<id name="id" type="int" column="ARTIST_ID">
<meta attribute="scope-set">protected</meta>
<generator class="native"/>
</id>
<property name="name" type="string">
<meta attribute="use-in-tostring">true</meta>
<column name="NAME" not-null="true" unique="true" index="ARTIST_NAME"/>
</property>
<set name="tracks" table="TRACK_ARTISTS" inverse="true">
<meta attribute="field-description">Tracks by this artist</meta>
<key column="ARTIST_ID"/>
<many-to-many class="com.chikkun.sample.database.Track" column="TRACK_ID"
/>
</set>
</class>
</hibernate-mapping>
これから、とりあえずDTO作成
さっきのbuild.xmlの中のtarget、「hbm2java」を実行します。再度注意点を書くと
<target name="hbm2java" description="hbmファイルからjava Beanを作成">
<taskdef name="hibernatetool"
classname="org.hibernate.tool.ant.HibernateToolTask"
classpathref="build.classpath"
>
</taskdef>
<hibernatetool destdir="src2">
<configuration configurationfile="WEB-INF/resources/hibernate.cfg.new.xml">
<fileset dir="src/main/java">
<include name="**/*.hbm.xml"/>
</fileset>
</configuration>
<hbm2java/>
</hibernatetool>
</target>
-
hibernate.cfg.xmlは、すでにある場合は別途用意すべし。
<configuration configurationfile="WEB-INF/resources/hibernate.cfg.new.xml">
-
DTOの書き出し先をかえとかないと、もともとあるDTOが上書きされてしまうので、下記のように変えて、そこから実際のソースディレクトリーにコピーしよう。
<hibernatetool destdir="src2">
ant hbm2java
と打ち込めば、2つのソースファイルがsrc2ディレクトリー以下にできているはずなので、それをソースディレクトリーにコピーしましょう。
Track.java
package com.chikkun.sample.database;
// Generated 2007/12/02 13:39:22 by Hibernate Tools 3.2.0.b9
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/**
* Represents a single playable track in the music database.
*
* @author chikkun
*/
public class Track implements java.io.Serializable {
private int id;
private String title;
private String filePath;
/**
* Playing time
*/
private Date playTime;
private Set artists = new HashSet(0);
private Set comments = new HashSet(0);
/**
* When the track was created
*/
private Date added;
/**
* How loud to play the track
*/
private short volume;
public Track() {
}
public Track(String title, String filePath, short volume) {
this.title = title;
this.filePath = filePath;
this.volume = volume;
}
public Track(String title, String filePath, Date playTime, Set artists,
Set comments, Date added, short volume) {
this.title = title;
this.filePath = filePath;
this.playTime = playTime;
this.artists = artists;
this.comments = comments;
this.added = added;
this.volume = volume;
}
public int getId() {
return this.id;
}
protected void setId(int id) {
this.id = id;
}
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public String getFilePath() {
return this.filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
/**
* * Playing time
*/
public Date getPlayTime() {
return this.playTime;
}
public void setPlayTime(Date playTime) {
this.playTime = playTime;
}
public Set getArtists() {
return this.artists;
}
public void setArtists(Set artists) {
this.artists = artists;
}
public Set getComments() {
return this.comments;
}
public void setComments(Set comments) {
this.comments = comments;
}
/**
* * When the track was created
*/
public Date getAdded() {
return this.added;
}
public void setAdded(Date added) {
this.added = added;
}
/**
* * How loud to play the track
*/
public short getVolume() {
return this.volume;
}
public void setVolume(short volume) {
this.volume = volume;
}
/**
* toString
*
* @return String
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(getClass().getName()).append("@").append(
Integer.toHexString(hashCode())).append(" [");
buffer.append("title").append("='").append(getTitle()).append("' ");
buffer.append("]");
return buffer.toString();
}
}
Artist.java
package com.chikkun.sample.database;
// Generated 2007/12/02 13:39:22 by Hibernate Tools 3.2.0.b9
import java.util.HashSet;
import java.util.Set;
/**
* Represents an artist who is associated with a track or album.
*
* @author chikkun
*
*/
public class Artist implements java.io.Serializable {
private int id;
private String name;
/**
* Tracks by this artist
*/
private Set tracks = new HashSet(0);
public Artist() {
}
public Artist(String name) {
this.name = name;
}
public Artist(String name, Set tracks) {
this.name = name;
this.tracks = tracks;
}
public int getId() {
return this.id;
}
protected void setId(int id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
/**
* * Tracks by this artist
*/
public Set getTracks() {
return this.tracks;
}
public void setTracks(Set tracks) {
this.tracks = tracks;
}
/**
* toString
*
* @return String
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(getClass().getName()).append("@").append(
Integer.toHexString(hashCode())).append(" [");
buffer.append("name").append("='").append(getName()).append("' ");
buffer.append("]");
return buffer.toString();
}
}
次はスキマーの作成
<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="show_sql">false</property>
<property name="connection.driver_class">org.postgresql.Driver</property>
<property \
name="connection.url">jdbc:postgresql://localhost:5432/mpn</property>
<property name="connection.username">chikkun</property>
<property name="connection.password">kazukun</property>
</session-factory>
</hibernate-configuration>
に2行書き加えて、
<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="show_sql">false</property>
<property name="connection.driver_class">org.postgresql.Driver</property>
<property \
name="connection.url">jdbc:postgresql://localhost:5432/mpn</property>
<property name="connection.username">chikkun</property>
<property name="connection.password">kazukun</property>
<mapping resource="com/chikkun/sample/database/Track.hbm.xml"/>
<mapping resource="com/chikkun/sample/database/Artist.hbm.xml"/>
</session-factory>
</hibernate-configuration>
として、
ant schemaexport
とすると、build.xmlのある場所に
schemaexport.sql
alter table TRACK_ARTISTS
drop constraint FK72EFDAD8D430600D;
alter table TRACK_ARTISTS
drop constraint FK72EFDAD88831A887;
alter table TRACK_COMMENTS
drop constraint FK105B2688D430600D;
drop table ARTIST;
drop table TRACK;
drop table TRACK_ARTISTS;
drop table TRACK_COMMENTS;
drop sequence hibernate_sequence;
create table ARTIST (
ARTIST_ID int4 not null,
NAME varchar(255) not null unique,
primary key (ARTIST_ID)
);
create table TRACK (
TRACK_ID int4 not null,
TITLE varchar(255) not null,
filePath varchar(255) not null,
playTime time,
added date,
volume int2 not null,
primary key (TRACK_ID)
);
create table TRACK_ARTISTS (
TRACK_ID int4 not null,
ARTIST_ID int4 not null,
primary key (TRACK_ID, ARTIST_ID)
);
create table TRACK_COMMENTS (
TRACK_ID int4 not null,
COMMENT varchar(255)
);
create index ARTIST_NAME on ARTIST (NAME);
create index TRACK_TITLE on TRACK (TITLE);
alter table TRACK_ARTISTS
add constraint FK72EFDAD8D430600D
foreign key (TRACK_ID)
references TRACK;
alter table TRACK_ARTISTS
add constraint FK72EFDAD88831A887
foreign key (ARTIST_ID)
references ARTIST;
alter table TRACK_COMMENTS
add constraint FK105B2688D430600D
foreign key (TRACK_ID)
references TRACK;
create sequence hibernate_sequence;
ができています。これを使って(drop部分は初めてだといらないので削除してから)テーブルを作成します。
暫定的なDAOを作成
とりあえず、2つのソースを書きます。SampleBaseDAO.javaとSampleDAO.javaです。
実験用のメソッド等は後で、追加するとして、とりあえず、データを保存したりするような感棚なものだけを作成します。
SampleBaseDAO.java
package com.chikkun.sample.database;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
* @author Administrator
* 単にsessionをもらうメソッドを実装。
*/
/**
* DAOベースクラス
*/
public class SampleBaseDAO {
/** セッションファクトリ */
private static SessionFactory sessionFactory = null;
/**
* セッションファクトリを取得します。 最初に呼び出されたときに、初期化処理を行います。
*/
static synchronized SessionFactory getSessionFactory()
throws HibernateException {
if (sessionFactory == null) {
Configuration cfg = new Configuration();
cfg.configure("hibernate.cfg.xml");
sessionFactory = cfg.buildSessionFactory();
}
return sessionFactory;
}
/**
* セッションを取得します。
*/
public Session getSession() throws HibernateException {
SessionFactory sf = getSessionFactory();
return sf.openSession();
}
}
hibernate.cfg.xmlの置き場所ですが、とりあえずテストで利用するのでテストのclassesディレクトリーに置く必要があります(上記の設定では)
SampleDAO.java
package com.chikkun.sample.database;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
public class SampleDAO extends SampleBaseDAO{
private final Logger logger = Logger.getLogger(this.getClass());
public void initializeSeq(int num){
Session session = null;
Transaction transaction = null;
try {
session = getSession();
transaction = session.beginTransaction();
Query sqlQuery = session.createSQLQuery("SELECT \
setval('hibernate_sequence'," + num + ", false)");
sqlQuery.list();
transaction.commit();
} catch (HibernateException ex) {
if (transaction != null) {
try {
transaction.rollback();
} catch (HibernateException ignore) {
logger.error("trackのデータを登録できませんでした");
}
}
throw new RuntimeException(ex.getMessage());
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException ignore) {
}
}
}
}
public List findArtistByName(String name){
Session session = null;
List list = null;
try {
session = getSession();
Query query = session
.createQuery("FROM Artist as artist where artist.name like :name");
query.setString("name", "%" + name + "%");
list = query.list();
} catch (HibernateException ex) {
throw new RuntimeException(ex.getMessage());
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException ignore) {
}
}
}
return list;
}
public List findTrackByTitle(String name){
Session session = null;
List list = null;
try {
session = getSession();
Query query = session
.createQuery("FROM Track as track where track.title like :name");
query.setString("name", "%" + name + "%");
list = query.list();
} catch (HibernateException ex) {
throw new RuntimeException(ex.getMessage());
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException ignore) {
}
}
}
return list;
}
public Set findTrackCommentsByTitle(String name){
Session session = null;
List list = null;
Set set = new HashSet();
try {
session = getSession();
Query query = session
.createQuery("FROM Track as track where track.title like :name");
query.setString("name", "%" + name + "%");
list = query.list();
if(list == null || list.size() == 0){
return set;
}
Track track = (Track) list.get(0);
set = track.getComments();
set.size();//for lazy
} catch (HibernateException ex) {
throw new RuntimeException(ex.getMessage());
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException ignore) {
}
}
}
return set;
}
public List findTracksByArtistName(String name){
Session session = null;
List list = null;
List tracks = new ArrayList();
try {
session = getSession();
Query query = session
.createQuery("FROM Artist as artist where artist.name like :name");
query.setString("name", "%" + name + "%");
list = query.list();
if(list == null || list.size() == 0){
return tracks;
}
Artist art = (Artist) list.get(0);
Set tracksSet = art.getTracks();
for(Iterator it = tracksSet.iterator();it.hasNext();){
Track track = (Track) it.next();
tracks.add(track);
}
} catch (HibernateException ex) {
throw new RuntimeException(ex.getMessage());
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException ignore) {
}
}
}
return tracks;
}
public void saveTrackWithArtists(Track track){
Session session = null;
Transaction transaction = null;
try {
session = getSession();
transaction = session.beginTransaction();
session.save(track);
transaction.commit();
} catch (HibernateException ex) {
if (transaction != null) {
try {
transaction.rollback();
} catch (HibernateException ignore) {
logger.error("trackのデータを登録できませんでした。");
}
}
throw new RuntimeException(ex.getMessage());
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException ignore) {
}
}
}
}
}
DBUnit用のエクセルファイルを作成
サンプルデータがないとテストができないので、今回はエクセルでデータを作成。
- sheet名がテーブル名
- sheetの1行目にフィールド名
- 一応、「DatabaseSequenceFilter」なるものを使うと、順番を考慮してくれるらしいが、通常はsheetの左側のシートから読み込んでいるらしいので、念のため親テーブルを より左側 に書いておく方が無難でしょう。
というような方法で、次のようなエクセルファイルを作成。
テストプログラムを作成
まずは動作か確認を含めて、簡単なテストを作成。しっかり、エクセルからファイルが登録できているかなどのテストもかねて、テスト終了後。データをクリアしていないでいます(sqlで確認するため)。
DatabaseTestTrack
package com.chikkun.sample.database;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Time;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import junit.framework.TestCase;
import org.apache.log4j.xml.DOMConfigurator;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.DatabaseSequenceFilter;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.FilteredDataSet;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.dataset.filter.ITableFilter;
import org.dbunit.operation.DatabaseOperation;
public class DatabaseTestTrack extends TestCase {
private ResourceBundle resourceBundle;
private IDatabaseConnection conn;
public void testSeq() {
SampleDAO dao = new SampleDAO();
dao.initializeSeq(18);
List list = dao.findArtistByName("John");
Artist artist = (Artist) list.get(0);
System.out.println("John=" + artist.getName());
Track track = new Track();
track.setAdded(new Date());
track.setFilePath("/john/mother.mp3");
track.setPlayTime(Time.valueOf("00:10:12"));
track.setTitle("Mother");
track.setVolume((short) 0);
Set artists = new HashSet();
artists.add(artist);
track.setArtists(artists);
// Set tracks = artist.getTracks();
// tracks.add(track);
// artist.setTracks(tracks);
dao.saveTrackWithArtists(track);
List tracks = dao.findTracksByArtistName("John");
assertEquals("John's songs number", 4, tracks.size());
Set comments = dao.findTrackCommentsByTitle("White");
assertEquals("White Room's comments number", 2, comments.size());
}
protected void setUp() throws Exception {
DOMConfigurator.configure("WEB-INF/log4j.xml");
super.setUp();
// ResourceBundleの初期化
// test用のclasses/resources/ApplicationResource.propertiesがないとエラーになる。
// mvnを一度実行しておくとコピーされるが、注意を要する。
resourceBundle = ResourceBundle
.getBundle("resources.ApplicationResource");
conn = getDBConnection();
ITableFilter filter = new DatabaseSequenceFilter(conn);
IDataSet dataset = new XlsDataSet(new File("test/artist.xls"));
IDataSet fdataset = new FilteredDataSet(filter, dataset);
DatabaseOperation.CLEAN_INSERT.execute(conn, dataset);
}
public final IDatabaseConnection getDBConnection() throws Exception {
ResourceBundle resourcebundle = ResourceBundle
.getBundle("resources.ApplicationResource");
String url = resourcebundle.getString("system.db.url");
String user = resourcebundle.getString("system.db.user");
String password = resourcebundle.getString("system.db.pass");
String driver = resourcebundle.getString("system.db.driver");
// コネクションの取得
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, user, password);
IDatabaseConnection dbConn = new DatabaseConnection(conn);
return dbConn;
}
private Connection getConnection() throws Exception {
Class.forName("org.postgresql.Driver");
String url = resourceBundle.getString("system.db.url");
String user = resourceBundle.getString("system.db.user");
String pass = resourceBundle.getString("system.db.pass");
Connection con = DriverManager.getConnection(url, user, pass);
return con;
}
}
実験を、ようやく、開始
とりあえず、一通り実験できる状況になったので、すこしずつ実験を開始しようと思います。現状の設定のポイントは
- TrackとArtistは TRACK_ARTISTという関連テーブルによって many-to-mayで関係づけられている。
-
TrackとTRACK_COMMENTSがString型のCOMMENTSというフィールドの値を複数持つと設定されている。
※他のone-to-manyの場合とどこが違うかというと、いちいちDTOのクラスを介しておらず、バリュー型(というのかな)で関係づけており、単に値を参照したいような場合は、これで十分のよう。
lazyの確認
エクセルのデータから、Trackを取得し、それからArtistを参照してみましょう。
まずはDAOは以下を使用する。
SampleDAO
public List findTrackByTitle(String name){
Session session = null;
List list = null;
try {
session = getSession();
Query query = session
.createQuery("FROM Track as track where track.title like :name");
query.setString("name", "%" + name + "%");
list = query.list();
} catch (HibernateException ex) {
throw new RuntimeException(ex.getMessage());
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException ignore) {
}
}
}
return list;
}
次に、テストでは
DatabaseTestTrack
public void testLazy(){
List searched = dao.findTrackByTitle("White Room");
assertEquals("White Roomの数", 1,searched.size());
//lazy指定していないけど
Track track = (Track) searched.get(0);
Set artists = track.getArtists();
assertEquals("White Roomを演奏しているアーティスト", 3,artists.size());
}
何も指定しないと、lazyはfalseになっているはずなんだが、なぜか、次のような例外が起こって、テストは途中で止まってしまう。
org.hibernate.LazyInitializationException:
failed to lazily initialize a collection of role:
com.chikkun.sample.database.Track.artists, no session or session was closed
なので、これをTrack.hbm.xmlに
Track.hbm.xml
<set name="artists" table="TRACK_ARTISTS" lazy="false">
<key column="TRACK_ID"/>
<many-to-many class="com.chikkun.sample.database.Artist" \
column="ARTIST_ID"/>
</set>
とlazy="true"を書き入れると、すんなりテストが通ります。じゃあ、ということで反対から行くと・・・DAOのメソッドは
SampleDAO
public List findArtistByName(String name){
Session session = null;
List list = null;
try {
session = getSession();
Query query = session
.createQuery("FROM Artist as artist where artist.name like :name");
query.setString("name", "%" + name + "%");
list = query.list();
} catch (HibernateException ex) {
throw new RuntimeException(ex.getMessage());
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException ignore) {
}
}
}
return list;
}
で、テストは
public void testLazy2(){
List searched = dao.findArtistByName("Jack");
assertEquals("Jack Bruceの数", 1,searched.size());
//lazy指定していないけど
Artist artist = (Artist) searched.get(0);
Set tracks = artist.getTracks();
assertEquals("Jack Bruceが演奏しているTrackの数", 1,tracks.size());
}
です。これも最後のアサートで、例外が起こります。例外は
org.hibernate.LazyInitializationException:
failed to lazily initialize a collection of role:
com.chikkun.sample.database.Track.artists, no session or session was closed
で、前回と同じ。つまり、これも
Artist.hbm.xml
<set name="tracks" table="TRACK_ARTISTS" inverse="true" lazy="false">
<meta attribute="field-description">Tracks by this artist</meta>
<key column="ARTIST_ID"/>
<many-to-many class="com.chikkun.sample.database.Track" column="TRACK_ID"
/>
</set>
とやると、やはりすんなり、テストが通ります。
つまり、少なくとも「many-to-may」では、デフォルトはlazy="true"になっているようなので、要注意ですな。
cascade
次は、cascadeの指定による振る舞いを研究。まずはデフォルトのままArtistを削除してみよう。DAOに削除するメソッドを追加して、
SampleDAO
public final void deleteArtist(final Artist artist) {
Session session = null;
Transaction transaction = null;
try {
session = getSession();
transaction = session.beginTransaction();
session.delete(artist);
transaction.commit();
} catch (HibernateException ex) {
if (transaction != null) {
try {
transaction.rollback();
} catch (HibernateException ignore) {
logger.error("artistの削除がきませんでした。");
}
}
throw new RuntimeException(ex.getMessage());
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException ignore) {
}
}
}
}
一応、削除後のArtistの数を数えたいので、これも追加。
SampleDAO
public final List findAllArtists(){
Session session = null;
List list = null;
try {
session = getSession();
Query query = session
.createQuery("FROM Artist as artist order by order.name");
list = query.list();
} catch (HibernateException ex) {
throw new RuntimeException(ex.getMessage());
} finally {
if (session != null) {
try {
session.close();
} catch (HibernateException ignore) {
}
}
}
return list;
}
そんで、テストでは
DatabaseTestTrack
public void testCascade1(){
List searched = dao.findArtistByName("Jack");
assertEquals("Jack Bruceの数", 1,searched.size());
//lazy指定していないけど
Artist artist = (Artist) searched.get(0);
dao.deleteArtist(artist);
List artists = dao.findAllArtists();
assertEquals("1人削除した後の数", 12,artists.size());
}
このテストを実行すると、次のような例外が発生する。
2007-12-05 10:23:37,758,JDBCExceptionReporter.java,ERROR: update or delete on table "artist" violates foreign key constraint "fk72efdad88831a887" on table "track_artists" Detail: Key (artist_id)=(10) is still referenced from table "track_artists".
これは、hbmが
Arttist.hbm.xml
by chikkun


