看了一对一的实现之后,我们来看一下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"。