这是个老生长谈的问题,在学习Hibernate的时候,当时是认为永远都用不上,因为现在数据库设计并没有严格按照范式来进行设计,更多的时候采用逻辑外键关系,在代码中去控制这种业务逻辑。但是,这次在'景点+门票'的1-N关系模型时候产品设计了插入景点信息时候就录入N个门票类型信息。所以,个人认为在插入(CMS中数据的录入时候)使用起来这种级联增删改比较方便,因为插入不会带来巨大数据库压力(hibbernate的这种级联操作确实效率不高,从打印出来的HQL中可以明显看到,Hibernate对N的一端也是一条插入的)。
注意:在数据库表设计的时候,子表中设置物理外键关联父表 不是必须的 ,而且现在的数据库设计更多的是偏重逻辑外键关系,这的确会为后期的工作带来很大的方便。
景点 和 门票类型的POJO和hbm代码
景点POJO
public class TbScenery { private String id; private String jdname; private List<TbSceneryTickets> tbSceneryTickets; //getter setter.... }
景点hbm
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" > <hibernate-mapping> <class name="TbScenery" table="TB_SCENERY"> <id name="id" type="java.lang.String"> <column name="ID" length="32" /> <generator class="sequence" > <param name="sequence">SEQ_SCENERY</param> </generator> </id> <property name="jdname" type="java.lang.String"> <column name="JDNAME" length="500" /> </property> <!-- 这里需要Parent一端设置inverse="true" cascade="all"--> <list name="tbSceneryTickets" lazy="false" inverse="true" cascade="all"> <key><column name="SCENERY_ID" /></key> <index column="ID" type="java.lang.String" /> <one-to-many class="TbSceneryTickets" /> </list> </class> </hibernate-mapping>
门票POJO:
public class TbSceneryTickets implements java.io.Serializable { private String id; private String sceneryId;//外键 private String typeName; //getter setter.... }
门票hbm
<hibernate-mapping> <class name="TbSceneryTickets" table="TB_SCENERY_TICKETS" lazy="false"> <id name="id" type="java.lang.String"> <column name="ID" length="32" /> <generator class="sequence" > <param name="sequence">SEQ_SCENERY_TICKET</param> </generator> </id> <property name="sceneryId" type="java.lang.String" column="SCENERY_ID" length="32" /> <property name="typeName" type="java.lang.String" column="TYPE_NAME" length="100" /> </class> </hibernate-mapping>
测试后发现在做更新,删除操作的时候的时候没有任何问题,只是在做如下的插入操作的时候会发现插入失败(这里的方法用了事务控制):
public void test() { TbScenery scenery = new TbScenery(); scenery.setJdname("东方明珠"); //由于景点对象的id主键 和 门票对象的id主键都是从序列中获取到的,这里没法设置这些值。 //也没有办法为门票对象赋景点外键sceneryId的值 TbSceneryTickets ticket1 = new TbSceneryTickets(); ticket1.setTypeName("成人票"); TbSceneryTickets ticket2 = new TbSceneryTickets(); ticket1.setTypeName("儿童票"); List<TbSceneryTickets> tbSceneryTickets = new ArrayList<TbSceneryTickets>(); tbSceneryTickets.add(ticket1); tbSceneryTickets.add(ticket2); scenery.setTbSceneryTickets(tbSceneryTickets); dao.saveOrUpdate(scenery); }
原因是:在新建’景点‘,并且有了n个’门票类型的时候‘,不能一次性提交这个’景点‘对象,tbSceneryTickets赋值给scenery前需要先save一下scenery,因为scenery是一个new出来的对象,不是一个已经被持久化过的对象,对它进行tbSceneryTickets赋值,Hibernate并不能够检查到它的状态。其实说得浅显些,就是scenery(景点对象)自己都没有id标识,那每个TbSceneryTickets对象(门票类型对象)怎么知道外键是什么呢?
对于one-to-many,一般都是如下操作:
public void test() { TbScenery scenery = new TbScenery(); scenery.setJdname("东方明珠"); //这个步骤结束后,scenery就有自己的主键了,通过debug可以非常清楚的看到 dao.saveOrUpdate(scenery); TbSceneryTickets ticket1 = new TbSceneryTickets(); ticket1.setTypeName("成人票"); //当然如果不放心,可以自己手工给ticket赋外键 ticket1.setSceneryId(scenery.getId()); TbSceneryTickets ticket2 = new TbSceneryTickets(); ticket1.setTypeName("儿童票"); ticket2.setSceneryId(scenery.getId()) List<TbSceneryTickets> tbSceneryTickets = new ArrayList<TbSceneryTickets>(); tbSceneryTickets.add(ticket1); tbSceneryTickets.add(ticket2); scenery.setTbSceneryTickets(tbSceneryTickets); dao.saveOrUpdate(scenery); }
在项目中可以使用这个原理来轻松完成one-to-many的级联操作(增删改查)。