看了一对一的实现之后,我们来看一下hibernate中一对多的实现,实际上还是不难的,只是有些概念第一次用时比较难理解。
废话不多说,直接上代码:
先看一下实体类:
public class Address implements Serializable{
private static final long serialVersionUID = 1L;
private int id;
private String address;
private String zipcode;
private String tel;
private String type;
private TUser user;
//省略Get/Set方法
}
public class TUser implements Serializable{
private static final long serialVersionUID = 1L;
private int id;
private int age;
private String name;
private Set<Address> addresses = new HashSet<Address>();
//省略Get/Set方法
}
看完实体类,我们再来看一下映射文件:
<hibernate-mapping package="org.hibernate.tutorial.domain6"> <class name="Address" table="t_address" dynamic-insert="false" dynamic-update="false"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native" /> </id> <property name="address" column="address" type="java.lang.String" /> <property name="zipcode" column="zipcode" type="java.lang.String" /> <property name="tel" column="tel" type="java.lang.String"/> <property name="type" column="type" type="java.lang.String" /> <many-to-one name="user" class="TUser" column="user_id" not-null="true"></many-to-one> </class> </hibernate-mapping>
再看另外一个TUser的映射文件,这个才是重要的,我们接下来要讲的重点,要仔细看:
<hibernate-mapping package="org.hibernate.tutorial.domain6">
<class name="TUser" table="t_user" dynamic-insert="true" dynamic-update="true">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" type="java.lang.String" column="name"/>
<property name="age" type="java.lang.Integer" column="age"/>
<set name="addresses" cascade="all" table="t_address" inverse="true">
<key column="user_id" />
<one-to-many class="Address"/>
</set>
</class>
</hibernate-mapping>
注意看,我们这里用了一个inverse="true",这个是什么意思呢?很多人在这里会有疑问。
我们先把它删了,修改后如下:
<hibernate-mapping package="org.hibernate.tutorial.domain6">
<class name="TUser" table="t_user" dynamic-insert="true" dynamic-update="true">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" type="java.lang.String" column="name"/>
<property name="age" type="java.lang.Integer" column="age"/>
<set name="addresses" cascade="all" table="t_address">
<key column="user_id" />
<one-to-many class="Address"/>
</set>
</class>
</hibernate-mapping>
我们用一个测试类来测试一下插入:
public static void main(String[] args) {
Configuration cfg = new Configuration().configure();
SessionFactory sessionFactory = cfg.buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
TUser user = new TUser();
Address address = new Address();
address.setAddress("Test1");
address.setTel("123123");
address.setType("14423213");
address.setZipcode("4444");
address.setUser(user);
user.getAddresses().add(address);
session.save(user);
session.getTransaction().commit();
session.close();
}
这里我们进行插入的时候是没问题的,我们把address.setUser(user)去掉后看一下错误:
org.hibernate.PropertyValueException: not-null property references a null or transient value: org.hibernate.tutorial.domain6.Address.user
这是指我们向非空的字段插入了一个空的值,这是因为我们在Address里面限定了user必须非空,而我们这里没有进行set入,就会出现这样的错误。
而我们说的inverse="true"的情况就是避免了单向关联时的这个错误,单向关联时首先会进行一条insert操作:
我们直接看一下hibernate打印出的语句:
Hibernate: insert into t_user (age) values (?)
Hibernate: insert into t_address (address, zipcode, tel, type, user_id) values (?, ?, ?, ?, ?)
Hibernate: update t_address set user_id=? where id=?
它首先会插入两条语句,然后再根据第一条语句的ID去更新第二条语句的user_id,我们这里进行了setUser,所以没问题,如果把那句删了就出问题了,hibernate尝试把null插入赋给user_id,这是肯定会出错的。
这是我们没有用inverse="true"的情况,需要由User来维护需要插入给Address的user_id,所以它需要先插入一条user确定user的id,然后再插入一条address,再根据user的id来更新address的user_id。逻辑很正常。
但当我们加上inverse="true"之后,我们再重新运行测试类,可以看到打印的语句为:
Hibernate: insert into t_user (age) values (?)
Hibernate: insert into t_address (address, zipcode, tel, type, user_id) values (?, ?, ?, ?, ?)
我们看到三条语句变成了两条。hibernate在第二条语句中直接把user_id插入t_user表,即User的关联表。很容易理解吧,inverse="true"就是让对方来管理跟自己关联的属性,这里表明Address管理user属性,它在t_user插入后然后把id取得,并作为user_id插入到t_address表中。
如果你的项目出现上面的那个异常,首先检查一下是否用了inverse="true",默认值是inverse="false"。