|
データは以下のようになっているとします。 mysql> select * from Samples.CUSTOMER; +----+-----------+---------+ | ID | NAME | USER_ID | +----+-----------+---------+ | 1 | CUSTOMER1 | 1 | | 2 | CUSTOMER2 | NULL | | 3 | CUSTOMER3 | 3 | | 4 | CUSTOMER4 | 2 | | 5 | CUSTOMER5 | 2 | +----+-----------+---------+ 5 rows in set (0.00 sec) mysql> select * from Samples.USER; +----+-------+ | ID | NAME | +----+-------+ | 1 | USER1 | | 2 | USER2 | | 3 | USER3 | +----+-------+ 3 rows in set (0.00 sec) mysql> HQLで検索する †通常通りやってみる †まずはHQLで検索してみます。 public static void main(String[] args) {
DOMConfigurator.configure("log4j.xml");
try {
final SessionFactory sessionFactory = getSessionFactory();
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Query query = session.createQuery("from Customer");
List models = query.list();
Iterator it = models.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
session.flush();
session.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
結果は以下の通り。 org.hibernate.SQL - select customer0_.ID as ID1_, customer0_.USER_ID as USER2_1_,
customer0_.NAME as NAME1_ from Samples.CUSTOMER customer0_
org.hibernate.SQL - select user0_.ID as ID0_0_, user0_.NAME as NAME0_0_ from Samples.USER user0_ where user0_.ID=?
nu.mine.kino.entity.Customer@1d439fe[id=1,name=CUSTOMER1,担当者=nu.mine.kino.entity.User@1e78c96[id=1,name=USER1]]
nu.mine.kino.entity.Customer@148f8c8[id=2,name=CUSTOMER2,担当者=<null>]
org.hibernate.SQL - select user0_.ID as ID0_0_, user0_.NAME as NAME0_0_ from Samples.USER user0_ where user0_.ID=?
nu.mine.kino.entity.Customer@1c5466b[id=3,name=CUSTOMER3,担当者=nu.mine.kino.entity.User@c2ee15[id=3,name=USER3]]
org.hibernate.SQL - select user0_.ID as ID0_0_, user0_.NAME as NAME0_0_ from Samples.USER user0_ where user0_.ID=?
nu.mine.kino.entity.Customer@922804[id=4,name=CUSTOMER4,担当者=nu.mine.kino.entity.User@1d0d45b[id=2,name=USER2]]
nu.mine.kino.entity.Customer@1815338[id=5,name=CUSTOMER5,担当者=nu.mine.kino.entity.User@1d0d45b[id=2,name=USER2]]
CUSTOMERが全件検索されて、その後に関連つくUSERが検索されています。 結合をHQLでやる(from句で) †Joinをhbmの設定を利用してやってると、デフォルトではSQL一発ではなくCustomerごとにUserを取ってくる模様。これだとパフォーマンス的に悲惨になりそうなので、HQLでつなげてみました。 public static void main(String[] args) {
DOMConfigurator.configure("log4j.xml");
try {
final SessionFactory sessionFactory = getSessionFactory();
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Query query = session
.createQuery("from Customer as customer left join customer.user");
List models = query.list();
Iterator it = models.iterator();
while (it.hasNext()) {
Object element = it.next();
Object[] objects = (Object[]) element;
System.out.println(objects[0]); // 明示的にjoinすると
System.out.println(objects[1]); // CustomerとUserの配列になる??
}
session.flush();
session.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
結果は以下の通り。 org.hibernate.SQL - select customer0_.ID as ID1_0_, user1_.ID as ID0_1_,customer0_.USER_ID as USER2_1_0_,
customer0_.NAME as NAME1_0_,user1_.NAME as NAME0_1_
from Samples.CUSTOMER customer0_ left outer join Samples.USER user1_
on customer0_.USER_ID=user1_.ID
nu.mine.kino.entity.Customer@17e845a[id=1,name=CUSTOMER1,担当者=nu.mine.kino.entity.User@1ba0f36[id=1,name=USER1]]
nu.mine.kino.entity.User@1ba0f36[id=1,name=USER1]
nu.mine.kino.entity.Customer@3caa4b[id=2,name=CUSTOMER2,担当者=<null>]
null
nu.mine.kino.entity.Customer@d0220c[id=3,name=CUSTOMER3,担当者=nu.mine.kino.entity.User@6b496d[id=3,name=USER3]]
nu.mine.kino.entity.User@6b496d[id=3,name=USER3]
nu.mine.kino.entity.Customer@1a19458[id=4,name=CUSTOMER4,担当者=nu.mine.kino.entity.User@1124746[id=2,name=USER2]]
nu.mine.kino.entity.User@1124746[id=2,name=USER2]
nu.mine.kino.entity.Customer@105691e[id=5,name=CUSTOMER5,担当者=nu.mine.kino.entity.User@1124746[id=2,name=USER2]]
nu.mine.kino.entity.User@1124746[id=2,name=USER2]
SQL内でOuter Joinしてくれるようになりました。しかし戻りが配列になってるのがいかんですねぇ。 ちなみにleft joinを ただの joinと書くと内部結合になります。つまりUSER_IDがNULLのCUSTOMERは検索されません。 配列のところを改善する †先のクエリ Query query = session
.createQuery("from Customer as customer left join customer.user");
では返り値がCustomer,Userの配列(のリスト)となっていました。これをCustomerだけを返すようにするには以下のようにクエリを変更すればOKです。 Query query = session
.createQuery("select customer from Customer as customer left join customer.user");
このようにselect [クラスのエイリアス名] とすれば、指定したクラスだけを返すことができます。結果は以下の通り。 org.hibernate.SQL - select customer0_.ID as ID1_, customer0_.USER_ID as USER2_1_, customer0_.NAME as NAME1_
from Samples.CUSTOMER customer0_ left outer join Samples.USER user1_
on customer0_.USER_ID=user1_.ID
org.hibernate.SQL - select user0_.ID as ID0_0_, user0_.NAME as NAME0_0_ from Samples.USER user0_ where user0_.ID=?
nu.mine.kino.entity.Customer@1815338[id=1,name=CUSTOMER1,担当者=nu.mine.kino.entity.User@16917ee[id=1,name=USER1]]
nu.mine.kino.entity.Customer@12368df[id=2,name=CUSTOMER2,担当者=<null>]
DEBUG org.hibernate.SQL - select user0_.ID as ID0_0_, user0_.NAME as NAME0_0_ from Samples.USER user0_ where user0_.ID=?
nu.mine.kino.entity.Customer@1ba0f36[id=3,name=CUSTOMER3,担当者=nu.mine.kino.entity.User@1d0d45b[id=3,name=USER3]]
DEBUG org.hibernate.SQL - select user0_.ID as ID0_0_, user0_.NAME as NAME0_0_ from Samples.USER user0_ where user0_.ID=?
nu.mine.kino.entity.Customer@3caa4b[id=4,name=CUSTOMER4,担当者=nu.mine.kino.entity.User@11db6bb[id=2,name=USER2]]
nu.mine.kino.entity.Customer@d0220c[id=5,name=CUSTOMER5,担当者=nu.mine.kino.entity.User@11db6bb[id=2,name=USER2]]
確かに戻り値が配列ではなくなったのですが、selectがまた乱発されてしまった。。。うーん。 配列のところを改善する、かつフェッチ済み †Query query = session
.createQuery("select customer from Customer as customer left join customer.user");
だとCustomerから参照されているUserを参照したときにselectが発生してまずい状態になってました。すでにフェッチ済みにするには以下のようにすればOKです。 Query query = session
.createQuery("select customer from Customer as customer left join fetch customer.user");
結果は以下の通り。 org.hibernate.SQL - select customer0_.ID as ID1_0_, user1_.ID as ID0_1_, customer0_.USER_ID as USER2_1_0_,
customer0_.NAME as NAME1_0_, user1_.NAME as NAME0_1_
from Samples.CUSTOMER customer0_
left outer join Samples.USER user1_ on customer0_.USER_ID=user1_.ID
nu.mine.kino.entity.Customer@17e845a[id=1,name=CUSTOMER1,担当者=nu.mine.kino.entity.User@1ba0f36[id=1,name=USER1]]
nu.mine.kino.entity.Customer@3caa4b[id=2,name=CUSTOMER2,担当者=<null>]
nu.mine.kino.entity.Customer@d0220c[id=3,name=CUSTOMER3,担当者=nu.mine.kino.entity.User@6b496d[id=3,name=USER3]]
nu.mine.kino.entity.Customer@1a19458[id=4,name=CUSTOMER4,担当者=nu.mine.kino.entity.User@1124746[id=2,name=USER2]]
nu.mine.kino.entity.Customer@105691e[id=5,name=CUSTOMER5,担当者=nu.mine.kino.entity.User@1124746[id=2,name=USER2]]
完璧です。。。 またこのfetch指定を付けた場合は select customer を付けなくてもよいみたいです。実際 Query query = session
.createQuery("from Customer as customer left join customer.user");
の戻り値はCustomerクラスでした。 結合をHQLでやる(where句で) †次にwhere句で結合します。 Query query = session
.createQuery("from Customer as customer,User as user where customer.user.id=user.id");
これの結果はCustomer,Userの配列です。 org.hibernate.SQL - select customer0_.ID as ID1_0_, user1_.ID as ID0_1_, customer0_.USER_ID as USER2_1_0_,
customer0_.NAME as NAME1_0_, user1_.NAME as NAME0_1_
from Samples.CUSTOMER customer0_, Samples.USER user1_
where customer0_.USER_ID=user1_.ID
nu.mine.kino.entity.Customer@3caa4b[id=1,name=CUSTOMER1,担当者=nu.mine.kino.entity.User@6b496d[id=1,name=USER1]]
nu.mine.kino.entity.Customer@1a19458[id=3,name=CUSTOMER3,担当者=nu.mine.kino.entity.User@1124746[id=3,name=USER3]]
nu.mine.kino.entity.Customer@105691e[id=4,name=CUSTOMER4,担当者=nu.mine.kino.entity.User@383118[id=2,name=USER2]]
nu.mine.kino.entity.Customer@11f23e5[id=5,name=CUSTOMER5,担当者=nu.mine.kino.entity.User@383118[id=2,name=USER2]]
だんだんSQLみたいになってきました。これにselect句をつけてCustomerだけを取得することもできますが、例によってselectが乱発されます。またこれでは内部結合なので、外部結合にする方法をさがしたのですがHIBERNATE イン アクション 7.3.5 シータスタイル結合 の項目によると「Hibernateはマッピングされた関連を持たない2テーブルを外部結合できない」となっていますね。。へぇ。。 Criteriaで検索する †通常通りやってみる †次にCriteriaを使って検索します。 public static void main(String[] args) {
DOMConfigurator.configure("log4j.xml");
try {
final SessionFactory sessionFactory = getSessionFactory();
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Criteria criteria = session.createCriteria(Customer.class);
// criteria.setFetchMode("user", FetchMode.JOIN); <-フェッチの指定
List models = criteria.list();
Iterator it = models.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
session.flush();
session.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
結果は以下の通り。 org.hibernate.SQL - select this_.ID as ID1_0_, this_.USER_ID as USER2_1_0_,
this_.NAME as NAME1_0_ from Samples.CUSTOMER this_
org.hibernate.SQL - select user0_.ID as ID0_0_, user0_.NAME as NAME0_0_ from Samples.USER user0_ where user0_.ID=?
nu.mine.kino.entity.Customer@32060c[id=1,name=CUSTOMER1,担当者=nu.mine.kino.entity.User@1e1be92[id=1,name=USER1]]
nu.mine.kino.entity.Customer@1efb4be[id=2,name=CUSTOMER2,担当者=<null>]
org.hibernate.SQL - select user0_.ID as ID0_0_, user0_.NAME as NAME0_0_ from Samples.USER user0_ where user0_.ID=?
nu.mine.kino.entity.Customer@435a3a[id=3,name=CUSTOMER3,担当者=nu.mine.kino.entity.User@1860038[id=3,name=USER3]]
org.hibernate.SQL - select user0_.ID as ID0_0_, user0_.NAME as NAME0_0_ from Samples.USER user0_ where user0_.ID=?
nu.mine.kino.entity.Customer@1d8c528[id=4,name=CUSTOMER4,担当者=nu.mine.kino.entity.User@1b33a0e[id=2,name=USER2]]
nu.mine.kino.entity.Customer@77eaf8[id=5,name=CUSTOMER5,担当者=nu.mine.kino.entity.User@1b33a0e[id=2,name=USER2]]
やっぱりCUSTOMERが全件検索されて、その後に関連付くUSERが検索されています。 フェッチの指定をする †コメントアウトしていた criteria.setFetchMode("user", FetchMode.JOIN); <-フェッチの指定
をアンコメントしてフェッチのモードを変更してやってみます。結果は以下の通り。 org.hibernate.SQL - select this_.ID as ID1_1_, this_.USER_ID as USER2_1_1_,
this_.NAME as NAME1_1_, user2_.ID as ID0_0_,
user2_.NAME as NAME0_0_ from Samples.CUSTOMER this_
left outer join Samples.USER user2_ on this_.USER_ID=user2_.ID
nu.mine.kino.entity.Customer@e35bb7[id=1,name=CUSTOMER1,担当者=nu.mine.kino.entity.User@1d8c528[id=1,name=USER1]]
nu.mine.kino.entity.Customer@9a8a68[id=2,name=CUSTOMER2,担当者=<null>]
nu.mine.kino.entity.Customer@1038de7[id=3,name=CUSTOMER3,担当者=nu.mine.kino.entity.User@1f4e571[id=3,name=USER3]]
nu.mine.kino.entity.Customer@183e7de[id=4,name=CUSTOMER4,担当者=nu.mine.kino.entity.User@5976c2[id=2,name=USER2]]
nu.mine.kino.entity.Customer@10fe2b9[id=5,name=CUSTOMER5,担当者=nu.mine.kino.entity.User@5976c2[id=2,name=USER2]]
あらかじめOuter Joinして検索してくれています。 SQLのLike指定をしてみる †(この辺から諸般の事情で顧客とかユーザとか日本語になってたり、いきなりOracleになってたりしますが、気にしないでください) Criteria criteria = session.createCriteria(Customer.class).add(
Expression.like("name", "%顧客%"));
criteria.setFetchMode("user", FetchMode.JOIN);
List<Customer> list = criteria.list();
for (Customer customer : list) {
System.out.println(customer);
}
で where Customer.name like '%顧客%' な検索ができます。 %はメソッドの引数で制御することもできて、たとえば Criteria criteria = session.createCriteria(Customer.class).add(
Expression.like("name", "顧客", MatchMode.ANYWHERE));
は上の where Customer.name like '%顧客%' と同じだったり、 Criteria criteria = session.createCriteria(Customer.class).add(
Expression.like("name", "顧客", MatchMode.START));
は where Customer.name like '顧客%' だし Criteria criteria = session.createCriteria(Customer.class).add(
Expression.like("name", "顧客", MatchMode.END));
は where Customer.name like '%顧客' となります。便利ですね。 関連先の条件で検索する †たとえばユーザを検索するときに「顧客4を担当しているユーザ」など、関連先の条件が○○なんて検索をしたいときがあります。 例として他には、顧客で、複数持ってる口座のうちある区分の口座が閉塞状態の顧客、、なんて場合とか。 そんな場合はCriteriaをくっつけて検索することができます。 Criteria criteria = session.createCriteria(User.class)
.createCriteria("customers").add(
Expression.like("name", "顧客4"));
List<User> list = criteria.list();
for (User user : list) {
Set<Customer> customers = user.getCustomers();
for (Customer customer : customers) {
System.out.println(customer);
}
System.out.println("---------- 以上: " + user);
}
これで「顧客4という名前のCustomerを持っているUser」を検索できます。 実行結果は以下の通り select this_.ID as ID0_1_, this_.NAME as NAME0_1_, customer1_.ID as ID1_0_,
customer1_.USER_ID as USER2_1_0_, customer1_.NAME as NAME1_0_
from USER_ATTR this_, CUSTOMER customer1_
where this_.ID=customer1_.USER_ID
and customer1_.NAME like ?
select customers0_.USER_ID as USER2_1_, customers0_.ID as ID1_,
customers0_.ID as ID1_0_, customers0_.USER_ID as USER2_1_0_,
customers0_.NAME as NAME1_0_
from CUSTOMER customers0_ where customers0_.USER_ID=?
nu.mine.kino.entity.Customer@15881588[id=4,name=顧客4,担当者=nu.mine.kino.entity.User@5a3e5a3e[id=2,name=ユーザ2]]
nu.mine.kino.entity.Customer@b820b82[id=5,name=顧客5,担当者=nu.mine.kino.entity.User@5a3e5a3e[id=2,name=ユーザ2]]
---------- 以上: nu.mine.kino.entity.User@5a3e5a3e[id=2,name=ユーザ2]
はじめのSQLで、Customer.name like ? としてユーザを検索しています。ただ戻り値のUserの顧客(s)を表示してみると、顧客5も含まれていることが分かります。つまり、Criteriaで関連先の条件を指定しても、あくまではじめのテーブルの選択に使用されるだけという事ですね。オブジェクト的に考えれば「顧客4をもってるユーザの担当してる顧客は?」「顧客4と顧客5です」なので当たり前ですが、SQLをベースに考えるとちょっと理解しにくいかもしれません。 DetachedCriteria? †Criteriaは通常 session.createCriteria(User.class); などとsessionから生成しますが、セッションにひも付いていない状態のCriteriaを作りたいときはDetachedCriteria?*1を使う(?)みたいです。 DetachedCriteria userCriteria = DetachedCriteria
.forClass(User.class);
DetachedCriteria customerCriteria = userCriteria
.createCriteria("customers");
customerCriteria.add(Expression.like("name", "顧客4",
MatchMode.ANYWHERE));
Criteria criteria = userCriteria.getExecutableCriteria(session);
List<User> list = criteria.list();
ほとんど同じですが、DetachedCriteria?をつくりきったあと、 Criteria criteria = userCriteria.getExecutableCriteria(session); とCriteriaをセッションにひも付く形で取得して実行、となります。 たとえばSpringでHibernateTemplate?にCriteriaを渡したいときに使いますね。 HQLいろいろ †サイズ指定 †Query query = session
.createQuery("from Customer customer left join fetch customer.user user where size(user.customers) > 1");
とかで、複数の顧客を抱える担当者を検索できます。 from Customer customer left join fetch customer.user user で
となります。で、 where size(user.customers) > 1 で、UserクラスのCustomersのコレクション(Set)のサイズが1以上のもの、を検索します。 実行結果は以下の通り。 org.hibernate.SQL - select customer0_.ID as ID1_0_, user1_.ID as ID0_1_, customer0_.USER_ID as USER2_1_0_,
customer0_.NAME as NAME1_0_, user1_.NAME as NAME0_1_
from Samples.CUSTOMER customer0_ left outer join Samples.USER user1_
on customer0_.USER_ID=user1_.ID
where (
select count(customers2_.USER_ID)
from Samples.CUSTOMER customers2_
where user1_.ID=customers2_.USER_ID
)> 1
nu.mine.kino.entity.Customer@16dc861[id=4,name=CUSTOMER4,担当者=nu.mine.kino.entity.User@2d189c[id=2,name=USER2]]
nu.mine.kino.entity.Customer@aae86e[id=5,name=CUSTOMER5,担当者=nu.mine.kino.entity.User@2d189c[id=2,name=USER2]]
なるほど。。 検索条件にプロパティを使う †下のように検索条件としてname=xxxってあるときgetName()というGetterをもつ任意のクラスをsetPropertiesで渡すことによって、検索条件を代入することができます。 user = (User) session.load(User.class, user.getId());
Query query = session.createFilter(user.getCustomers(),
"where this.name=:name");
CustomerModoki modoki = new CustomerModoki();
modoki.setName("顧客1");
query.setProperties(modoki);
List list = query.list();
class CustomerModoki {
private String name;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
この場合は名前が"顧客1"という検索を行うことができます。 ちなみにgetSessionFactory?()ってこんな内容 †private static SessionFactory getSessionFactory() {
Configuration config = new Configuration();
return config.configure(new File("hibernate.cfg.xml"))
.buildSessionFactory();
}
ちなみにInsertってすぐに行われるワケじゃない †データをインサートして、その後検索するってばあい、DBに実際にデータが挿入されるのは検索の直前みたい。たとえば上のテーブルに対して Customer c1 = new Customer();
c1.setName("顧客1");
customerDao.create(c1);
System.out.println("検索を開始します");
Customer[] customers = customerDao.readAll();
for (Customer customer : customers) {
System.out.println(customer);
}
ってやった場合*2 Hibernate: select max(ID) from CUSTOMER -----------------------------onSave 検索を開始します -----------------------------preFlush Hibernate: insert into CUSTOMER (USER_ID, NAME, ID) values (?, ?, ?) -----------------------------postFlush Hibernate: select this_.ID as ID1_0_, this_.USER_ID as USER2_1_0_, this_.NAME as NAME1_0_ from CUSTOMER this_ nu.mine.kino.entity.Customer@76627662[id=1,name=顧客1,担当者=<null>] org.springframework.orm.hibernate3.HibernateTransactionManager - Triggering beforeCompletion synchronization org.springframework.orm.hibernate3.HibernateTransactionManager - Initiating transaction rollback org.springframework.orm.hibernate3.HibernateTransactionManager - Rolling back Hibernate transaction on Session [org.hibernate.impl.SessionImpl@519e519e] -----------------------------afterTransactionCompletion org.springframework.orm.hibernate3.HibernateTransactionManager - Triggering afterCompletion synchronization org.springframework.orm.hibernate3.HibernateTransactionManager - Closing Hibernate Session [org.hibernate.impl.SessionImpl@519e519e] after transaction org.springframework.orm.hibernate3.SessionFactoryUtils - Closing Hibernate Session nu.mine.kino.dao.CustomerDaoTest - Rolled back transaction after test execution データを挿入した後に「検索を開始します」と書きましたが、実際はその後にInsert文が実行されていることが分かります。 この記事は
現在のアクセス:38374 |