Hibernate框架ORM基础(一对多关系的save分析)
相信各位技术大牛应该一眼就看懂了,我呢,只是个菜鸟,只不过想在交流中进步。故工作之余会谢谢博客。下面是Hibernate的一对多关系的对象保存到数据库的一个图文分析。在使用Hibernate框架开发时,一般不会涉及这些Hibernate的API语句,不过一对多关系在开发中却经常用到,相关的业务需求也是非常多。
备注一下:下边的代码有些不是很标准,不过本人没有去改,不过大致可以看出各个图的各种情况所对应的代码。
以下情况是在非注解方式的分析(即一个实体类对应一个xml配置文件)。
环境:
1、假设有俩个实体类(客户Customer【一方】和联系人LinkMan【多方】);
2、实体类中互相有对方的属性,如:
客户实体类中有private Set<LinkMan> custLinkMan = new HashSet<LinkMan>(0);
联系人实体类中有private Customer lkmCustomer;
3、客户类对应的Customer.hbm.xml中除了其他属性的配置,还有的就是如下的一对多关系的映射
<set name="custLinkMan"> <!-- 此处不写inverse属性,后面图文中体现-->
<key column="lkm_cust_id"></key>
<one-to-many class="com.syh.entity.LinkMan" />
</set>
联系人类对应的LinkMan.hbm.xml中除了其他属性的配置,也有如下的关系映射配置
<many-to-one cascade="save-update" name="lkmCustomer" class="com.syh.entity.Customer" column="lkm_cust_id"></many-to-one>
4、准备一个HibernateUtil工具类,使用里面绑定了线程的方法得到session,其实因为这个不是正经开发,刻意使用普通的得到session的方法。
在保存一对多关系的实体对象到数据库的时候我们需要关注几个影响条件:
1、保存的方向性:在客户实体类中有设置联系人属性的方法,也就是那个setter方法,不过因为是set集合引用对象,实际上getter方法就够了,例如customer.getCustLinkMan().add(linkman)这个A(为了减少代码量,我先用A和B替代这个)方法就可以了;而在联系人实体类中使用的是如linkman.setLkmCustomer(costomer)这个B方法。
前面简单说明一下,这里保存的方向性指的是程序中有A方法而没有B方法(单向关系:客户知道了联系人),有B方法而没有A方法(单向关系:联系人知道了客户),有A和B俩个都存在(双向关系)。
2、保存的顺序(先保存客户还是先保存联系人的问题);
3、客户的配置文件中是否放弃维护权,即是否设置了inverse=“true”,这个只有一方有,而多方是一定会维护的,因为外键本来就是多方表的;
造成的结果我们需要关注几个东西:
1、Hibernate发送到数据库的sql语句数;
2、是否成功插入到数据库;
3、Hibernate发送的sql语句的异同;
在这里我们只分析上述影响条件下的情况,根据排列组合3*2*2=12种情况,实际上还有单双向保存(只保存客户或只保存联系人的情况)以及级联cascade属性(不级联、客户级联、联系人级联、都级联)的问题,但加上这些,情况会变成12*3*4=144种情况,本人才疏学浅时间有限,就简单说一下以下12种情况吧。
先看图:
图一如下:
该图为联系人知道客户,即只有linkman.setLkmCustomer(costomer)这个方法。然后根据保存顺序和是否放弃维护权来看结果。
而该图的结果:
1)sql语句数在图里了;
2)都成功插入数据到数据库;
3)Hibernate发送的sql语句也体现在图里了;
图中右下角是简单的分析,需要一级缓存和快照机制的知识为基础,如果不明白欢迎给本人留言,欢迎互相交流。
如果看到图二图三,会发现第3句sql语句有不一样的地方,这是因为外键的维护权在不在一方也就是维护权在不在客户的问题。
图一对应的代码:
public class TestOneToMany1 { @Test public void test1() { /** * 单向关系:联系人知道客户 * 保存顺序:先保存联系人,后保存客户 * 配置:默认(客户参与维护外键) * * 结果:出现3条sql语句 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户001"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("1联系人1"); //单向关系:联系人->客户 l.setLkmCustomer(c); //保存 session.save(l); //保存联系人 LinkMan session.save(c); //保存客户 Customer //=================================================== transaction.commit(); } @Test public void test2() { /** * 单向关系:联系人知道客户 * 保存顺序:先保存联系人,后保存客户 * 配置:inverse="true"(客户不参与维护外键) * * 结果:出现3条sql语句 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户001"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("2联系人1"); //单向关系:联系人->客户 l.setLkmCustomer(c); //保存 session.save(l); //保存联系人 LinkMan session.save(c); //保存客户 Customer //=================================================== transaction.commit(); } @Test public void test3() { /** * 单向关系:联系人知道客户 * 保存顺序:先保存客户,后保存联系人 * 配置:inverse="true"(客户不参与维护外键) * * 结果:出现2条sql语句 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户001"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("3联系人1"); //单向关系:联系人->客户 l.setLkmCustomer(c); //保存 session.save(c); //保存客户 Customer session.save(l); //保存联系人 LinkMan //=================================================== transaction.commit(); } @Test public void test4() { /** * 单向关系:联系人知道客户 * 保存顺序:先保存客户,后保存联系人 * 配置:默认(客户参与维护外键) * * 结果:出现2条sql语句 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户001"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("4联系人1"); //单向关系:联系人->客户 l.setLkmCustomer(c); //保存 session.save(c); //保存客户 Customer session.save(l); //保存联系人 LinkMan //=================================================== transaction.commit(); } }
图二如下:
该图为客户知道联系人,即只有customer.getCustLinkMan().add(linkman)这个方法。然后根据保存顺序和是否放弃维护权来看结果。
而该图的结果:
1)sql语句数在图里了;
2)插入数据到数据库中有俩条记录没有外键;
3)Hibernate发送的sql语句也体现在图里了;
左上角为第一种情况最左边那种情况的简单的分析,右上角是插入到数据库的情况。
插入数据异常的原因:
客户知道联系人,但由于设置了inverse="true"放弃了维护权,而联系人却不知道客户,想维护外键也维护不了,因此,无论先保存联系人还是先保存客户,设置了inverse=“true”的时候都会造成外键为空。
图二对应代码:
public class TestOneToMany2 { @Test public void test1() { /** * 单向关系:客户知道联系人 * 保存顺序:先保存联系人,后保存客户 * 配置:默认(客户参与维护外键) * * 结果:出现3条sql语句 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户002"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("1联系人2"); //单向关系:客户->联系人 //l.setLkmCustomer(c); c.getCustLinkMan().add(l); //保存 session.save(l); //保存联系人 LinkMan session.save(c); //保存客户 Customer //=================================================== transaction.commit(); } @Test public void test2() { /** * 单向关系:联系人知道客户 * 保存顺序:先保存联系人,后保存客户 * 配置:inverse="true"(客户不参与维护外键) * * 结果:出现2条sql语句,无法建立外键 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户002"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("2联系人2"); //单向关系:客户->联系人 //l.setLkmCustomer(c); c.getCustLinkMan().add(l); //保存 session.save(l); //保存联系人 LinkMan session.save(c); //保存客户 Customer //=================================================== transaction.commit(); } @Test public void test3() { /** * 单向关系:联系人知道客户 * 保存顺序:先保存客户,后保存联系人 * 配置:inverse="true"(客户不参与维护外键) * * 结果:出现2条sql语句,无法建立外键 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户002"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("3联系人2"); //单向关系:客户->联系人 //l.setLkmCustomer(c); c.getCustLinkMan().add(l); //保存 session.save(c); //保存客户 Customer session.save(l); //保存联系人 LinkMan //=================================================== transaction.commit(); } @Test public void test4() { /** * 单向关系:联系人知道客户 * 保存顺序:先保存客户,后保存联系人 * 配置:默认(客户参与维护外键) * * 结果:出现3条sql语句 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户002"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("4联系人2"); //单向关系:客户->联系人 //l.setLkmCustomer(c); c.getCustLinkMan().add(l); //保存 session.save(c); //保存客户 Customer session.save(l); //保存联系人 LinkMan //=================================================== transaction.commit(); } }
图三如下:
该图为双向关系,即既有customer.getCustLinkMan().add(linkman)这个方法,也有linkman.setLkmCustomer(costomer)。然后根据保存顺序和是否放弃维护权来看结果。
而该图的结果:
1)sql语句数在图里了;
2)都成功插入数据到数据库(图二右上角的最后4条数据);
3)Hibernate发送的sql语句也体现在图里了;
在这里解释一下为什么第3句sql语句会有所不同:
假如所有情况都由客户去维护外键,那么发送的第3句sql语句一定是只维护了主键,即下图中中间那个sql语句;
但由于有些情况是多方维护了外键,也就是联系人自己维护外键,所以会出现下图右下角的那个sql语句。
至于多方自己维护外键会更新自己全部的信息,这个要去研究Hibernate的源码,本人菜鸟一个,这个就交给各位大神了。
图三对应代码:
public class TestOneToMany3 { @Test public void test1() { /** * 单向关系:联系人知道客户,客户知道联系人 * 保存顺序:先保存联系人,后保存客户 * 配置:默认(客户参与维护外键) * * 结果:出现3条sql语句 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户003"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("1联系人3"); //单向关系:联系人->客户、客户->联系人 l.setLkmCustomer(c); c.getCustLinkMan().add(l); //保存 session.save(l); //保存联系人 LinkMan session.save(c); //保存客户 Customer //=================================================== transaction.commit(); } @Test public void test2() { /** * 单向关系:联系人知道客户,客户知道联系人 * 保存顺序:先保存联系人,后保存客户 * 配置:inverse="true"(客户不参与维护外键) * * 结果:出现3条sql语句 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户003"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("2联系人3"); //单向关系:联系人->客户、客户->联系人 l.setLkmCustomer(c); c.getCustLinkMan().add(l); //保存 session.save(l); //保存联系人 LinkMan session.save(c); //保存客户 Customer //=================================================== transaction.commit(); } @Test public void test3() { /** * 单向关系:联系人知道客户,客户知道联系人 * 保存顺序:先保存客户,后保存联系人 * 配置:inverse="true"(客户不参与维护外键) * * 结果:出现2条sql语句 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户003"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("3联系人3"); //单向关系:联系人->客户、客户->联系人 l.setLkmCustomer(c); c.getCustLinkMan().add(l); //保存 session.save(c); //保存客户 Customer session.save(l); //保存联系人 LinkMan //=================================================== transaction.commit(); } @Test public void test4() { /** * 单向关系:联系人知道客户,客户知道联系人 * 保存顺序:先保存客户,后保存联系人 * 配置:默认(客户参与维护外键) * * 结果:出现3条sql语句 * 原因: */ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //=================================================== //客户实例化 Customer c = new Customer(); c.setCustName("客户003"); //联系人实例化 LinkMan l = new LinkMan(); l.setLkmName("4联系人3"); //单向关系:联系人->客户、客户->联系人 l.setLkmCustomer(c); c.getCustLinkMan().add(l); //保存 session.save(c); //保存客户 Customer session.save(l); //保存联系人 LinkMan //=================================================== transaction.commit(); } }