Hibernate中的对象状态

在Hibernate中持久化对象具有三种状态: 瞬时态, 持久态, 游离态.

瞬时态: 对象没有与Hibernate产生关联(transient,session中没有缓存), 数据库中也没有对应记录=> 对象无id, 没有关联
持久态: 对象与Hibernate产生关联(persistent, session中有缓存), 数据库中会存在记录=> 对象存有id, 有关联
游离态: 对象没有与Hibernate产生关联(detached,session中没有缓存), 数据库中有记录=> 对象有id, 没有关联

持久化对象状态之间的相互转化

  • 瞬时(无id, 无关联)=>持久(有id, 有关联) save操作:
    修改id, 与session的关联状态
让瞬时对象拥有id, 并且与Hibernate产生关联
//u此时没有id, 没有与Hibernate产生关联
User u = new User(); //瞬时态
u.setName("tom"); //瞬时态
u.setPassword("123"); //瞬时态//save操作, Hibernate根据id生成策略, 为u生成id
//然后存入数据库, 与Hibernate产生关联, 变为持久态
session.save(u);//持久态

注: 只有Hibernate的主键生成策略变为assigned才能手动设置id, 否则报错

  • **瞬时(无id, 无关联)=>游离(有id, 无关联) **
    修改id即可
//设置Hibernate主键生成策略为assigned
User u = new User();
u.setId(1);
  • 持久(有id, 有关联)=>瞬时(无id, 无关联)
    修改id与关联状态即可
1.直接通过session的get查询操作获取持久化对象关闭session切断关联, 再通过setId(null)修改id就变为瞬时态
User u = (User) session.get(User.class, 1); //持久态
session.close();
u.setId(null); //瞬时态2.get查询到的对象, 使用evict方法切断user与session的联系,再设置id为null变为瞬时态
User u = session.get(User.class ,1);//持久态
session.evict(u);
u.setId(null); //瞬时态
  • 持久(有id, 有关联)=>游离(有id, 无关联)
    获得持久对象, 然后切断持久对象与session的联系
User u =(User) session.get(User.class, 1);
session.close();
//或者 session.evict(u);
  • 游离(有id, 无关联)=>瞬时(无id, 无关联)
    设置游离对象id为null
User u = (User) session.get(User.class, 1);
session.evict(u);
u.setId(null);
  • 游离(有id, 无关联)=>持久(有id, 有关联)
    重新将游离对象写入数据库, 使得与session产生联系
User u = (User) session.get(User.class, 1);
session.evict(u);
session.update(u); //写入数据库,变为持久态

注: 持久态的时候不允许修改持久化对象的id: u.setId(2), 修改直接报错, 对持久化对象直接进行修改:u.setName(“C”), 那么Hibernate将会执行update操作, 将修改的持久化对象的数据同步到数据库

一级缓存

一级缓存(session缓存), 用于存储持久化对象 , 内部储存结构是一个Map, 当需要使用持久化对象的时候, Hibernate会优先从缓存中获取, 当session关闭, 一级缓存销毁.

  • 快照
    快照: 缓存的复制品, 存放在session中
    快照主要用来与缓存中数据进行比较, 判断缓存中数据是否发生变化
缓存中数据发生变化:Hibernate就会执行update, insert操作
缓存中数据没有发生变化:说明数据库中数据与缓存中数据相同,Hibernate就会避免主动执行SQL的update, insert, 减少资源浪费
  • 缓存执行过程:
查询(select):session首次执行查询, 发送select到数据库, 将查询的结果封装放入session缓存,当再次执行查询操作, Hibernate会先去Session缓存中查找,没找到, 再去数据库中查找.
更新(update):使用get/load执行查询操作后, 持久化对象放入Session缓存中,然后对持久化对象update, 事务提交的时候将执行如下操作:先将Session缓存中修改后的对象与快照中数据进行比较,二者数据不相同的时候执行update, 数据更新到数据库中二者数据相同, 那么不执行任何操作.
插入(insert):使用快照中数据与缓存中数据进行比较, 根据比较结果判断是否继续执行insert操作
  • 缓存刷新时机:
//查询表中所有数据, 查询后的结果会覆盖缓存中数据
List<User> list = session.createQuery("from User").list();//执行flush, 刷新缓存中数据,
session.flush();
  • 缓存导致的问题:
    每次获取持久化对象, Hibernate优先去缓存中查找, 一定程度上提高了SQL的执行效率.
    缓存的存在, 出现的一个问题就是: 如果Hibernate获得持久化对象后, 数据库中数据又出现了修改, 当再次对该持久化对象进行操作的时候, Hibernate会优先从缓存中获得持久化对象, 导致数据库与Hibernate中数据不一致. 当出现这种问题的时候, 建议JDBC操作

注: 避免将相同的对象放入缓存中, 谨记缓存是一个Map, 看如下操作:

User u1 = session.get(User.class, 1);//缓存中放入u1
session.evict(u1);//变为游离态, 缓存中不存在
User u2 = session.get(User.class, 1);//获得id=1的持久化对象,缓存中存在
session.update(u1);//u1重新变为持久态, 缓存中存在
//u1, u2同时存在缓存中, 将会报错

session缓存常用API

  • evict(Object o): 将指定对象从session缓存中移除
  • clear(): 清除session缓存中所有的对象
  • refresh(Object o):
    强制刷新指定对象, 使持久化对象数据与数据库中数据一致, 一定程度上避免session缓存产生的数据不一致问题;
    对o对象重新执行SQL
  • flush():
    对比快照与缓存中的数据, 确保数据一致, 然后将缓存中数据提交到数据库, 类似于commit, 数据不一致的时候刷新缓存中的数据

对象的操作

  • save操作细节:
    当执行save的时候, 对象会从瞬时态=>持久态, 事务提交后将持久化对象存入数据库中
执行save操作, 先根据映射文件中的主键生成策略生成主键.
Hibernate将生成的主键赋值给瞬时态对象, 若该对象有id, 那么就会覆盖原有的id
最后执行insert, 将瞬时态对象变为持久化对象
  • persist操作:
    persist操作与save一样, 他们二者的区别在: persist会检查对象主键, save不会检查对象主键
    例如:
//u.setId(9); //save操作的时候Hibernate会自动进行主键生成,设置id无效
//persist会检查Bean的id
//发现与Hibernate主键生成策略不符,
//报org.hibernate.PersistentObjectException: detached entity passed to persist: com.demo.User异常
///将主键生成策略改为assigned或不设置主键,将不报错u.setName("O");u.setPassword("66");//session.save(u);session.persist(u);
  • update操作细节:
    游离对象=>持久对象, 对持久对象属性修改后, 使用save, 执行的是update, 而非insert
    在映射文件的class标签中设置select-before-update=“true”, 那么执行update就会执行如下操作:
User u = new User();
u.setId(1); u.setName("P"); u.setPassword("pp");
session.update(u);
//当执行这样的操作的时候, 先执行select操作, 然后比较查询结果
//与查询结果一直, 那么就不执行update
  • saveOrUpdate:
    该方法就是save与update的结合, session.saveOrUpdate(u); 如果u存在id, 那么执行select, 然后再执行update, 没有id, 执行insert

HQL, SQL, Criteria与缓存的联系

下面通过例子说明:

体现一个问题: HQL都会执行select操作,将获取的list与缓存中的数据进行比较//如果相同, 每次获取缓存中的封装对象/*List<User> list1 = session.createQuery("from User").list();List<User> list2 = session.createQuery("from User").list();List<User> list3 = session.createQuery("from User").list();for(User u:list1){System.out.println(u);}System.out.println(list1.hashCode()+"--"+list2.hashCode()+"--"+list3.hashCode());*///原生的SQL操作与HQL一致/*List<User> list1 = session.createSQLQuery("select * from t_User").addEntity(User.class).list();List<User> list2 = session.createSQLQuery("select * from t_User").addEntity(User.class).list();List<User> list3 = session.createSQLQuery("select * from t_User").addEntity(User.class).list();for(User u:list1){System.out.println(u);}System.out.println(list1.hashCode()+"--"+list2.hashCode()+"--"+list3.hashCode());*///criteria操作同上/*List<User> list1 = session.createCriteria(User.class).list();List<User> list2 = session.createCriteria(User.class).list();List<User> list3 = session.createCriteria(User.class).list();for(User u:list1){System.out.println(u);}System.out.println(list1.hashCode()+"--"+list2.hashCode()+"--"+list3.hashCode());*/

通过程序的运行观察执行的SQL语句, 以及list对象的hashCode, 发现每次执行批量查询(HQL, SQL, Criteria)都会select * from t_user, 然后将查询的结果集与Session缓存中的数据进行比较.
说明:Hibernate把第一次执行的结果集放入缓存区, 在后面的查询中, 尽管Hibernate发送了SQL语句, 但是使用的数据依旧是缓存中的数据, 这个时候使用get操作的时候, 获取的数据也是从缓存区中得到

多表设计

表中存在的三种关系: 多对多, 一对多, 一对一

  • 数据库描述上述关系:
    在数据库中所有的关系都需要通过外键进行约束.

  • Bean对象描述上述的关系:

一对多:客户与订单
class Customer{//使用set描述客户订单列表private Set<Order> orderSet;
}
class Order{//多个商品订单属于一个客户private Customer customer;
}多对多: 学生与课程
class Student{private Set<Course> courseSet;
}
class Course{private Set<Student> StudentSet;
}一对一: 学生与学生证
class Student{private StudentCard sc;
}
class StudentCard{private Student s;
}

Hibernate的一对多关系实现

一对多操作的时候, 维护一个对象的时候会自动维护另一方的关系; 例如 Customer referenced Order, 当删除Order的时候,Hibernate会先update商品表中所有的外键为null, 然后再执行删除订单操作, 我们就不用显式修改商品表中的外键, 维护商品与订单之间的关系

测试类:

//消费者:
public class Customer {private Integer cid;private String cname;private Set<Order> orderSet = new HashSet<Order>();//get/set方法就不写了
}
//订单
public class Order {private Integer oid;private String price;private Customer customer;//get/set方法就不写了
}
  • Customer.hbm.xml与Order.hbm.xml编写
Customer.hbm.xml
<hibernate-mapping><class name="com.test.Customer" table="t_customer"><id><generator class="native"></generator></id><property name="cname"></property><!--配置一对多关系标签--><set name="ordertest" cascade="save-update"><key column="customer_id"></key><one-to-many class="com.test.Order"/></set></class>
</hibernate-mapping>Order.hbm.xml
<hibernate-mapping><class name="com.test.Order" table="t_order"><id name="oid"><generator class="native"></generator></id><property name="price"></property><many-to-one name="customer" class="com.test.Customer" column="customer_id"></many-to-one></class>
</hibernate-mapping>

在Customer.hbm.xml中:

set标签用于确定容器(用于装备Order)name属性: 确定对象属性名cascade属性: 设置Customer与Order的级联操作inverse属性: 将关系的维护翻转给对方, 默认值false(我维护这个关系)key标签确定Customer主键名one-to-many标签确定从表Order

cascade详细级联操作: 级联操作就是, 当A与B绑定好关系后, 就比如Customer的set已经存储了B, 当A执行save的时候, B也会自动执行save操作, 少写session.save(B)的代码, 同样的也可以执行级联删除, 当A删除了, B也跟着自动删除
注: 级联操作并不会维护关系

cascade的取值如下:
save-update:级联保存与修改A保存,同时保存B在程序中修改A中的B, 对应到数据库中B将会级联修改
delete:删除A,同时删除B,AB都不存在删除过程中, 如果A在维护关系,那么A还会去处理外键,对外键设置为null,然后执行删除.如果设置了inverse为true,A不去维护关系,A删除,B就删除,A不去update外键,减少了SQL操作
delete-orphan:孤儿删除,解除关系,同时将B删除,A存在的。接触B与A的关系, 将B从A的集合内移除, B此时没有引用对象, 就自动delete
如果需要配置多项,使用逗号分隔。<set cascade="save-update,delete">all : save-update 和 delete 整合
all-delete-orphan : 三个整合

此处注明: 千万不要A设置了级联删除,然后B也设置了级联删除

当删除B对象的时候, 由于级联删除, B会select所有A, 然后删除A, 但是A又触发级联删除, A会select所有的B, 最终删除所有的B, 以及所有的A, 就因为删除了一个B导致了如此严重的问题, 这个一定要避免!!!

在Order.hbm.xml中:

many-to-one标签中name属性: 确定属性名称class属性: 确定参照的类column属性: 确定Order表参照Customer表的外建名

往数据库中保存Customer与Order:

Customer c = new Customer();
c.setName("tom");
Order o1 = new Order();
o1.setName("o1");
Order o2 = new Order();
o2.setName("o2");
//往c中添加Order信息, 维护关系
c.getOrderSet().add(o1);//Customer去维护, 执行update
c.getOrderSet().add(o2);//执行update
//往Order对象中添加Customer, 维护关系
o1.setCustomer(c);//Order去维护, 在insert中修改cid的值
o2.setCustomer(c);
//保存到数据库
session.save(c);
session.save(o1);
session.save(o2);

执行上面的代码, Hibernate执行3次insert, 2次update, 需要注意的是在c, o1, o2 insert的过程中, 就已经在维护关系(对Order表的cid外键进行设置), 但是后面又对Order表执行了2次update, 产生的问题就是重复
所以通过上面的代码也可以看出, 当维护关系的时候只需要维护一方, 另一方的关系就能得到维护

同理, 执行delete操作: session.delete©; 执行这条语句的时候, Hibernate会先将o1, o2的cid设置为null, 然后再对c进行delete, 从Customer的角度维护关系, 但是Customer不去维护关系的时候, 就需要遍历Customer的orderSet, 将所有的Order对象setCustomer(null)主动切断与Customer的关系, 设置所有Order的外键为null

总结: 设置一对多关系下, 可以只让一方维护关系, 另一方不维护, 放弃维护关系的对象就是–非外键所在的对象, 就比如上面的操作, 让Order 维护关系, Customer不去维护关系, 这种一方去维护关系也可以使用set标签中的inverse属性, 使得关系的维护交给对方

未完待续~~

上面有错, 还请指出, 如果认为我写的还不错, 还请点个赞, 多多支持一下, O(∩_∩)O~~

Hibernate_2_Hibernate中对象状态及转化_一级缓存_Session详解_HQL/SQL/Criteria_一对多关系_级联操作相关推荐

  1. 《深入理解mybatis原理》 MyBatis的一级缓存实现详解 及使用注意事项

    MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓存机制上.MyBatis提供了一级缓存.二级缓存 这两个缓存机制,能够很好地处理和维护缓存,以提高系统的性能.本 ...

  2. 京东java多级缓存_多级缓存设计详解 | 给数据库减负,刻不容缓!

    来这里找志同道合的小伙伴! 作 者 简 介 王梓晨 自古兵家多谋,<谋攻篇>,"故上兵伐谋,其次伐交,其次伐兵,其下攻城.攻城之法,为不得已",可见攻城之计有很多种,而 ...

  3. MyBatis的一级缓存实现详解

    MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓存机制上.MyBatis提供了一级缓存.二级缓存 这两个缓存机制,能够很好地处理和维护缓存,以提高系统的性能.本 ...

  4. python中怎么计数_浅谈python中统计计数的几种方法和Counter详解

    1) 使用字典dict() 循环遍历出一个可迭代对象中的元素,如果字典没有该元素,那么就让该元素作为字典的键,并将该键赋值为1,如果存在就将该元素对应的值加1. lists = ['a','a','b ...

  5. python反向缩进_在Pycharm中对代码进行注释和缩进的方法详解

    在Pycharm中对代码进行注释和缩进的方法详解 一.注释 1. #单行注释 2. """ 多行注释 """ 3. pycharm多行注释快 ...

  6. python实现单例模式的几种方式_基于Python中单例模式的几种实现方式及优化详解...

    单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场. ...

  7. 电容的q值计算公式_在设计电路中电容容量大小、耐压等级选取详解 (转)

    原文链接:在设计电路中电容容量大小.耐压等级选取详解 作者:张飞 电容的计算 我们对电容的计算,目的是要知道,我们在电路中需要一个多大的电容.为什么要需要 这么个电容?它的电压要多高?它的容量要多大? ...

  8. python 读取图片转换为一维向量_对Python中一维向量和一维向量转置相乘的方法详解...

    对Python中一维向量和一维向量转置相乘的方法详解 在Python中有时会碰到需要一个一维列向量(n*1)与另一个一维列向量(n*1)的转置(1*n)相乘,得到一个n*n的矩阵的情况.但是在pyth ...

  9. python读二进制格点雷达基数据_对numpy中二进制格式的数据存储与读取方法详解...

    使用save可以实现对numpy数据的磁盘存储,存储的方式是二进制.查看使用说明,说明专门提到了是未经压缩的二进制形式.存储后的数据可以进行加载或者读取,通过使用load方法. In [81]:np. ...

最新文章

  1. htop 和 bashtop 的一些不足
  2. mongodb学习参考博文
  3. 在JUnit中处理异常的3种方法。 选择哪一个?
  4. 服务机器人占领智能安防哪些领域?
  5. redis源码剖析(1):基础数据结构SDS
  6. c#数据库事务锁类型
  7. android studio报错:ClassLoader referenced unknown path: /data/app/xxxx-1/lib/arm64
  8. 数据可视化——tableau 数据报表样例(报表模板)二
  9. 中国省份区域json
  10. U盘容量变小后修复的方法
  11. UI设计师如何通过兼职月入过万?
  12. 字体测试打分软件哪个好,准确率奇高的看图识字体网站-在线认字体
  13. arduino教程-13. 蓝牙
  14. win10 系统更新(window update)
  15. 硬件ecc和软件ecc
  16. APP二维码下载 自动跳转
  17. 【04741】2022年10月高等教育自学考试-计算机网络原理
  18. 在C++控制台程序中播放欢乐颂
  19. win7蓝屏_win7蓝屏如何修复
  20. 【2018】输出奇偶数之和

热门文章

  1. 多线程数据下载(akshare)
  2. 同步本地远程分支 git remote prune origin
  3. 常用数据结构的一部分类
  4. 冒泡排序与快速排序(java实现)
  5. CentOS下添加Root权限用户‘超级用户’方法(xxx is not in the sudoers file.This incident will be reported.的解决方法)
  6. 遵义大数据中心项目工程概况_市委书记张新文到曹州云都大数据中心等项目现场调研建设情况...
  7. Java笔记03-Constructor Override
  8. Css基本语法及页面引用
  9. java读取gpx文件,从Leaflet导出GPX文件
  10. canny算子的理论分析