继第一个Hibernate应用程序的代码基础上做的hibernate单项Set-based关联。
package org.hibernate.tutorial.domain;
import java.util.HashSet;
import java.util.Set;
public class Person {
private Long id;
private int age;
private String firstname;
private String lastname;
private Set events = new HashSet();
//setter and getter
}
此处采用双向关联,Hibernate many-to-many映射,Person映射文件如下:
<class name="Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
<set name="events" table="PERSON_EVENT">
<key column="PERSON_ID"/>
<many-to-many column="EVENT_ID" class="Event"/>
</set>
</class>
Hibernate提供广泛的集合映射,Set是比较常用的一种。对于many-to-may(n:m)这样的关联关系,关联表是需要的。表中的每条记录表示person 和event的一个连接。
set元素中的table属性表示表名。标识符列名,在person这端用key元素表示,在event端用many-to-many元素的column表示。
show create table person看下建表语句:
| person | CREATE TABLE `person` (
`PERSON_ID` bigint(20) NOT NULL AUTO_INCREMENT,
`age` int(11) DEFAULT NULL,
`firstname` varchar(255) DEFAULT NULL,
`lastname` varchar(255) DEFAULT NULL,
PRIMARY KEY (`PERSON_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
| person_event | CREATE TABLE `person_event` (
`PERSON_ID` bigint(20) NOT NULL,
`EVENT_ID` bigint(20) NOT NULL,
PRIMARY KEY (`PERSON_ID`,`EVENT_ID`),
KEY `FK_7bh2i9xjn8v93x5ku5w61npwf` (`EVENT_ID`),
CONSTRAINT `FK_8xsbl2jbyqaf5ocifr7hcjt2x` FOREIGN KEY (`PERSON_ID`) REFERENCES
`person` (`PERSON_ID`),
CONSTRAINT `FK_7bh2i9xjn8v93x5ku5w61npwf` FOREIGN KEY (`EVENT_ID`) REFERENCES
`events` (`EVENT_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
数据库表结构之间的关系:
关联测试(方法在EventManager中):private void addPersonToEvent(Long personId, Long eventId) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session.load(Person.class, personId);
Event anEvent = (Event) session.load(Event.class, eventId);
aPerson.getEvents().add(anEvent);
session.getTransaction().commit();
}
automatic dirty checking
注意到代码里没有显示的update()和save(),Hibernate自动检测collection的变化,称为自动脏检测(automatic dirty checking),你可以随意改变任一对象的数据属性。只要他们都是持久态(persistent state),被org.hibernate.Session管辖。Hibernate监听变化并采用延迟写的方式执行SQL,和数据库同步内存状态的过程,通常在工作单元的末尾,称为flusing阶段。在我们的代码里,数据库事务的工作单元以commit或者rollback来结束。
你可以以load的形式获取person和event。你也可以在托管(detached 对象不再org.hibernate.Session里),你甚至可以在detached时修改集合。
private void addPersonToEvent(Long personId, Long eventId) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session
.createQuery("select p from Person p left join fetch p.events where p.id = :pid")
.setParameter("pid", personId)
.uniqueResult(); // Eager fetch the collection so we can use it detached
Event anEvent = (Event) session.load(Event.class, eventId);
session.getTransaction().commit();
// End of first unit of work
aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached
// Begin second unit of work
Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();
session2.beginTransaction();
session2.update(aPerson); // Reattachment of aPerson
session2.getTransaction().commit();
}
通过绑定一个新的工作单元(事务),update()使得detached对象再一次持久化。所有你做的修改都会被保存到数据库。
例子里的这样的实现并不常用,但是一个重要的概念你可以并入你自己的应用。接下来完成这个联系。
代码并入EventManger:
else if (args[0].equals("addpersontoevent")) {
Long eventId = mgr.createAndStoreEvent("My Event", new Date());
Long personId = mgr.createAndStorePerson("Foo", "Bar");
mgr.addPersonToEvent(personId, eventId);
System.out.println("Added person " + personId + " to event " + eventId);
}
看下执行的sql:
Hibernate: insert into EVENTS (EVENT_DATE, title) values (?, ?)
Hibernate: insert into PERSON (age, firstname, lastname) values (?, ?, ?)
Hibernate: select person0_.PERSON_ID as PERSON_I1_1_0_, event2_.EVENT_ID as EVENT_ID1_0_1_, person0_.age as age2_1_0_, person0_.firstname as firstnam3_1_0_, person0_.lastname as lastname4_1_0_, event2_.EVENT_DATE as EVENT_DA2_0_1_, event2_.title as title3_0_1_, events1_.PERSON_ID as PERSON_I1_1_0__, events1_.EVENT_ID as EVENT_ID2_3_0__ from PERSON person0_ left outer join PERSON_EVENT events1_ on person0_.PERSON_ID=events1_.PERSON_ID left outer join EVENTS event2_ on events1_.EVENT_ID=event2_.EVENT_ID where person0_.PERSON_ID=?
Hibernate: update PERSON set age=?, firstname=?, lastname=? where PERSON_ID=?
Hibernate: insert into PERSON_EVENT (PERSON_ID, EVENT_ID) values (?, ?)
==》aPerson.getEvents().add(anEvent);这段代码发出Hibernate: insert into PERSON_EVENT (PERSON_ID, EVENT_ID) values (?, ?)
需要注意的是这里的aPerson.getEvents()并不在org.hibernate.Session内,此时session已经管理,aPerson处于detached状态。
值的集合:
往Person实例中增加邮件地址集合。
package org.hibernate.tutorial.domain;
import java.util.HashSet;
import java.util.Set;
public class Person {
private Long id;
private int age;
private String firstname;
private String lastname;
private Set events = new HashSet();
private Set emailAddresses = new HashSet();
//setter and getter
}
此时的Person.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
<set name="events" table="PERSON_EVENT">
<key column="PERSON_ID"/>
<many-to-many column="EVENT_ID" class="Event"/>
</set>
<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
<key column="PERSON_ID"/><!-- 外键 -->
<element type="string" column="EMAIL_ADDR"/><!-- element表示元素 -->
</set>
</class>
</hibernate-mapping>
set中的key PERSON_ID为集合表的外键名,element元素的column属性定义存储邮件地址值的列名。
表结构之间的关系如下:
测试代码如下:
private void addEmailToPerson(Long personId, String emailAddress) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session
.createQuery("from Person p where p.id = :pid")
.setParameter("pid", personId)
.uniqueResult(); // Eager fetch the collection so we can use it detached
// Hibernate.initialize(aPerson.getEmailAddresses()); //实例化代理
session.getTransaction().commit();
// End of first unit of work
aPerson.getEmailAddresses().add(emailAddress); // aPerson (and its collection) is detached
// Begin second unit of work
Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();
session2.beginTransaction();
session2.update(aPerson); // Reattachment of aPerson
session2.getTransaction().commit();
}
注意createQuery中并没有用fectch。运行结果如下:
Hibernate: select person0_.PERSON_ID as PERSON_I1_1_, person0_.age as age2_1_, person0_.firstname as firstnam3_1_, person0_.lastname as lastname4_1_ from PERSON person0_ where person0_.PERSON_ID=?
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.hibernate.tutorial.domain.Person.emailAddresses, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:572)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:212)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:551)
at org.hibernate.collection.internal.PersistentSet.add(PersistentSet.java:202)
at org.hibernate.tutorial.EventManager.addEmailToPerson(EventManager.java:142)
at org.hibernate.tutorial.EventManager.main(EventManager.java:35)
报错了,could not initialize proxy-no Session(没有实例化代理-没有Session)。
因为
Hibernate: select person0_.PERSON_ID as PERSON_I1_1_, person0_.age as age2_1_, person0_.firstname as firstnam3_1_, person0_.lastname as lastname4_1_ from PERSON person0_ where person0_.PERSON_ID=?
并没有查询emailAddress,返回org.hibernate.tutorial.domain.Person.emailAddresses是一个为初始化的代理。
解决办法:
1.把Hibernate.initialize(aPerson.getEmailAddresses());//实例化代理 的注解打开,在session中实例化org.hibernate.tutorial.domain.Person.emailAddresses
2.改懒加载为false
<set name="emailAddresses" table="PERSON_EMAIL_ADDR" lazy="false">
<key column="PERSON_ID"/><!-- 外键 -->
<element type="string" column="EMAIL_ADDR"/><!-- element表示元素 -->
</set>