Hibernate one-to-Many详解

在这里主要是来讲讲one-to-Many中的save方法详解:

首先来说说业务需求:

我们手中有一个订单和条目,每一个订单都有多个条目,就像是超市的回单,每一张回单中有多个你购买的物品,这里的回单就是订单,物品就是所说的条目。Many-to-one的需求就是:根据一个订单表,关联到每一个条目中,然后执行期中的增删改查方法。

首先我们先创建两个数据库表,分别为t_order表和t_item表,以下是创建表sql语句:

T_item

DROP TABLE IF EXISTS t_item;

CREATE TABLE t_item (

t_id int(11) NOT NULL AUTO_INCREMENT,

t_product_name varchar(50) NOT NULL,

t_unit_price double(9,2) NOT NULL,

t_amount int(11) NOT NULL,

t_order_id int(11) NOT NULL,

PRIMARY KEY (t_id)

) ENGINE=InnoDB;

T_order

DROP TABLE IF EXISTS t_order;

CREATE TABLE t_order (

t_id int(11) NOT NULL AUTO_INCREMENT,

t_create_date timestamp NOT NULL,

PRIMARY KEY (t_id)

) ENGINE=InnoDB;


然后我们在项目中的entity目录下创建两个实体类分别是Order.java 和Item.java一下是简要代码:

Order.java

Public class Order{

private Integer id;

private Date createDate;

private Set<Item> items = new HashSet<Item>();

//---get set----- 方法略

}

Item.java

Public class item{

private Integer id;

privateString productName;

private BigDecimal unitPrice;

privateint amount;

private Integer orderId;

//---get set----- 方法略

}


在Item.hbm.xml和Order.hbm.xml写对应的映射文件

Item.hbm.xml

<?xmlversion="1.0"encoding="utf-8"?>

<!DOCTYPEhibernate-mappingPUBLIC

"-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mappingpackage="com.tarena.entity">

<classname="Item"table="t_item">

<idname="id"type="integer" column="t_id">

<generatorclass="identity">

</generator>

</id>

<propertyname="productName" type="string" column="t_product_name"></property>

<propertyname="unitPrice" type="big_decimal"column="t_unit_price"></property>

<propertyname="amount" type="integer"

column="t_amount"></property>

<property name="orderId" type="integer" column="t_order_id"></property>

</class>

</hibernate-mapping>

Order.hbm.xml

<?xmlversion="1.0"encoding="utf-8"?>

<!DOCTYPEhibernate-mappingPUBLIC

"-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mappingpackage="com.tarena.entity">

<classname="Order"table="t_order">

<idname="id" type="integer"column="t_id">

<generatorclass="identity">

</generator>

</id>

<propertyname="createDate" type="timestamp"column="t_create_date"></property>

<set name="items" >

<key column="t_order_id" />

<one-to-many class="Item" />

</set>

</class>

</hibernate-mapping>

以上的是基本的配置值的一说的是Order.hbm.xml配置文件中的

<set name="items" > àitems 是在实体类中的set的属性名

<key column="t_order_id" />

这里的key比较重要

看到colum就知道一定是某个数据库中的某个列名,这里colum是指

t_order表的t_id 相等的t_item 的键即使 t_order_id

<one-to-many class="Item" /> àItem many一方的类名

</set>

在hibernate.cfg.xml配置文件中添加关联映射文件

<?xmlversion="1.0"encoding="UTF-8"?>

<!DOCTYPEhibernate-configurationPUBLIC

"-//Hibernate/Hibernate Configuration DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>

<!-- 数据库连接信息 -->

<propertyname="connection.url">

jdbc:mysql://localhost:3306/tarena_jd11

</property>

<propertyname="connection.username">root</property>

<propertyname="connection.password">123456</property>

<propertyname="connection.driver_class">

com.mysql.jdbc.Driver

</property>

<!-- Hibernate配置信息 -->

<!-- dialect方言,用于配置生成针对哪个数据库的SQL语句 -->

<propertyname="dialect">

<!--方言类,Hibernate提供的,用于封装某种特定数据库的方言 -->

org.hibernate.dialect.MySQLDialect

</property>

<propertyname="hibernate.show_sql">true</property>

<!-- 在配置文件中关联映射文件 -->

<mapping resource="com/tarena/entity/Order.hbm.xml" />

<mapping resource="com/tarena/entity/Item.hbm.xml" />

</session-factory>

</hibernate-configuration>


写一个测试类:

publicclass TestOneToMany {

@Test

publicvoid testSaveOrder() {

Order order = new Order();

order.setCreateDate(new Date());

Item item1 = newItem();

item1.setProductName("111");

item1.setUnitPrice(new BigDecimal("100.1"));

item1.setAmount(3);

item1.setOrder(order);


Item item2 = newItem();

item2.setProductName("2222");

item2.setUnitPrice(new BigDecimal("1100.1"));

item2.setAmount(4);

item2.setOrder(order);


order.getItems().add(item1);

order.getItems().add(item2);


Session session = HibernateUtil.getSession();

Transaction tx = session.beginTransaction();

session.save(order);

tx.commit();

HibernateUtil.closeSession();

}

}

当你运行的时候你会发现了如下的错误:

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.tarena.entity.Item

同时你也能够在控制看到如下的SQL语句

Hibernate: insert into t_order (t_create_date) values (?)

Hibernate: update t_item set t_order_id=? where t_id=?

看到这里是不是应该产生了一种困惑,我明明是想将orderitem1item2这三个对象插入到数据库中,可是从控制台上看,我们并非是插入三条数据,而是插入了order对象,更新了t_item中的数据呢,为什么会这样的。

这是因为hibernate默认的认为order的Map中的item1、和item2是存在Order表中的。所以当hibernate要去更新t_item中的数据的时候发生了错误,所以必须在Order.hbm.xml配置中的set添加一个属性

<set name="items" cascade="save-update"> à配置了级联保存

<key column="t_order_id" />

<one-to-many class="Item" />

</set>

那么我们配置好了之后再次运行测试类。你又会发现出错了,那么我们来看一下到底是哪里错了呢?

以下是错误信息

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:

Column 't_order_id' cannot be null

控制台信息:

insert into t_order (t_create_date) values (?)

insert into t_item (t_product_name, t_unit_price, t_amount, t_order_id) values (?, ?, ?, ?)

以上的信息说明了我们的第一步修改是很有意义的,数据已经插入了,不再是更新了。但是明明是三条数据的插入,这里指显示了两条,这是为什么,而之前的那个错误又是怎么回事呢?

既然报错我说们的t_order_id不能为空,那我们就将order数据表中的值设置允许为空。

再次运行,你会发现数据已经全都插到数据库中了。

那么为什么一定要设置t_order_id允许为空呢,我不设置难道不行吗?

因为配置了<cascade=”save-update”>之后,hibernate发现了item不存在,所以将执行了session.save()保存,此时的t_item中的t_order_id由于没有设置,所以为空,但是数据表中的t_order_id又不允许为空,所以发生了错误。

我们如何去解决这个问题,我们只需要在many的一方设置反向关联,Item里面关联Order,所以many-to-one一般是双向关联的。

首先需要在Item类中去掉OrderId的属性,换成Order对象的属性

private Order order;

再加上getset方法

然后在item.hbm.xml中添加many-to-one的关联:

<many-to-one name="order" column="t_order_id">

</many-to-one>

再在测试类中添加setOrder

item1.setOrder(order);

item2.setOrder(order);

然后再次运行,终于不会报错误了。

可是,我们再来看看控制台显示的信息:

insert into t_order (t_create_date) values (?)

insert into t_item (t_product_name, t_unit_price, t_amount, t_order_id) values (?, ?, ?, ?)

insert into t_item (t_product_name, t_unit_price, t_amount, t_order_id) values (?, ?, ?, ?)

update t_item set t_order_id=? where t_id=?

update t_item set t_order_id=? where t_id=?

三条插入3条记录可以理解,2条更新的记录又是怎么回事呢?

这是因为Order为了维持关联关系,所以hibernate执行了更新的操作。我们需要在这里设置反转,将维护关联关系的任务交给item来处理。

在Order.hbm.xml配置中的set添加一个属性 inverse =”true”

<set name="items" cascade="save-update" inverse=”true”> <key column="t_order_id" />

<one-to-many class="Item" />

</set>

我们再次运行的时候,控制台终于只有三条插入记录的信息,此时我们的目的也都已经达到了。

Hibernate: insert into t_order (t_create_date) values (?)

Hibernate: insert into t_item (t_product_name, t_unit_price, t_amount, t_order_id) values (?, ?, ?, ?)

Hibernate: insert into t_item (t_product_name, t_unit_price, t_amount, t_order_id) values (?, ?, ?, ?)


今天我们的教程就到这里,感谢大家的支持。