打赏

Hibernate(3)——实例总结Hibernate对象的状态和ThreadLoacl封闭的session

俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及的知识点总结如下:

  • Hibernate的内部执行过程(CRUD)
  • 对象的状态及其转换图和例子
  • 使用JUnit测试
  • 使用getCurrentSession代替openSession
  • ThreadLoacl对象
  • 享元模式
  • session.update(obj),为保证执行更新,推荐使用session.flush()刷新缓存;

 

  1和2大概总结了Hibernate运行的技术原理,那么现在总结一下它自身的编写过程:如下:

  其中本质上主要就是使用了dom4j解析配置文件+反射技术来支撑了整个框架的运行。当然如果是注解的话,还有注解技术。而其中世界级的设计思想和编程技巧,又是另一个方面的技术内容了。

 

  Hibernate是如何识别持久化类的?

  在Hibernate的hibernate.cfg.xml配置文件中引入了实体关系的映射文件Xxx.hbm.xml,而在映射文件中指明了持久化类是哪些,所以Hibernate通过它识别持久化类,Hibernate容器——》hibernate.cfg.xml——》 *.hbm.xml——》class元素的name属性加在持久化类,通过这种方式识别持久化类。

 

  内部执行过程

  Hibernate的CRUD方法代码

 1 /**
 2  * UserDao
 3  *
 4  * @author Wang Yishuai.
 5  * @date 2016/2/2 0002.
 6  * @Copyright(c) 2016 Wang Yishuai,USTC,SSE.
 7  */
 8 public class UserDao {
 9     final Logger LOG = LoggerFactory.getLogger(UserDao.class);
10 
11     private SessionFactory sessionFactory;
12 
13     private UserDao() {
14         // 通过new一个Configuration实例,然后用该实例去调用configure返回一个配置实例
15         Configuration configuration = new Configuration().configure();
16         // 通过 配置实例的buildSessionFactory方法 生成一个 sessionFactory 对象
17         // buildSessionFactory方法会默认的去寻找配置文件hibernate.cfg.xml并解析xml文件
18         // 解析完毕生成sessionFactory,负责连接数据库
19         this.sessionFactory = configuration.buildSessionFactory();
20     }
21 
22     public static UserDao newInstance() {
23         return new UserDao();
24     }
25 
26     public void save(User user) {
27         // 通过 sessionFactory 获得一个数据库连接 session,可以操作数据库
28         Session session = sessionFactory.openSession();
29         // 把操作封装到数据库的事务,则需要开启一个事务
30         Transaction transaction = session.beginTransaction();
31 
32         // 一般把对实体类和数据库的操作,放到try-catch-finally块
33         try {
34             // 把user对象插入到数据库
35             session.save(user);
36             // 提交操作事务
37             transaction.commit();
38             LOG.info("transaction.commit(); ok");
39         } catch (Exception e) {
40             // 提交事务失败,必须要回滚
41             transaction.rollback();
42             // 打印日志
43             LOG.error("save user error......", e);
44         } finally {
45             // 不能丢这一步,要释放资源
46             session.close();
47             LOG.info("session.close(); ok");
48         }
49     }
50 
51     public void retriveAll() {
52         Session session = sessionFactory.openSession();
53         List<User> userList = session.createQuery("from User").list();
54         session.close();
55 
56         for (User user : userList) {
57             LOG.info("username = {}", user.getUsername());
58         }
59     }
60 
61     public void delete(User user) {
62         Session session = sessionFactory.openSession();
63         Transaction transaction = session.beginTransaction();
64 
65         user = (User) session.get(user.getClass(), user.getUserId());
66         session.delete(user);
67         transaction.commit();
68         session.close();
69     }
70 
71     public void update(User user) {
72         Session session = sessionFactory.openSession();
73         Transaction transaction = session.beginTransaction();
74         user = (User) session.get(user.getClass(), user.getUserId());
75         user.setUsername("dadad");
76         session.update(user);
77         transaction.commit();
78         session.close();
79     }
80 }
View Code

  2 中我们知道hibernate通过读取配置文件和依靠反射去拼接对应的SQL语句,比如当hibernate执行session.get(xxx.class, xL)这个代码的时候,在hibernate内部会拼接成一个SQL语句:

select
user0_.userId as userId0_0_,
user0_.username as username0_0_,
user0_.password as password0_0_
from
user user0_
where
user0_.userId=?

要生成该SQL语句,必须找到数据库对应的表,以及表中的字段,表中的主键。又因为session.get方法的第一个参数为持久化类的class形式,去sessionFactory中查找该class对应的映射文件,找到该映射文件以后,映射文件中的class元素的name

属性的值就是对应的持久化类,class元素的table属性就是对应的表。这样找到的。

 

  Hibernate对象的三种状态

  • Transient 瞬时状态:数据库中没有数据与之一一对应,id没有纳入session的管理,没有持久化标识(相当于主键),随时都有可能被垃圾回收。
  • Persist 持久化状态数据库中有数据与之一一对应,id纳入了session的管理。特点:属性与数据的改变,与数据库中保持一致
  • Detached 托管状态/游离状态没有纳入session的管理,但数据在数据库中存在。

下面是测试代码:使用的JUnit4做单元测试

 1 package test.java;
 2 
 3 import dashuai.dao.UserDao;
 4 import dashuai.vo.User;
 5 import org.junit.After;
 6 import org.junit.Before;
 7 import org.slf4j.Logger;
 8 import org.slf4j.LoggerFactory;
 9 
10 /**
11  * Test
12  *
13  * @author Wang Yishuai.
14  * @date 2016/3/10 0010.
15  * @Copyright(c) 2016 Wang Yishuai,USTC,SSE.
16  */
17 public class Test {
18     private static final Logger LOG = LoggerFactory.getLogger(Test.class);
19 
20     private UserDao userDao;
21 
22     @Before
23     public void init() {
24         LOG.info("init");
25         this.userDao = UserDao.newInstance();
26     }
27 
28     @After
29     public void clear() {
30         LOG.info("clear");
31     }
32 
33     @org.junit.Test
34     public void testHibernate1() {
35         User user = new User();
36         user.setUsername("niubi");
37         user.setPassword("1");
38         // 此时的user对象是瞬时态的
39 
40         userDao.save(user);
41         // save到数据库之后,user变为持久态
42 
43         // 最后在save方法里,最终执行 session.close();使得user变为游离态
44         user.setUsername("liuxiang");// 数据库中的数据不会变
45     }
46 }
View Code

  小结:

  1. 瞬时状态的对象没有和hibernate发生交互,转换为持久态对象和hibernate容器发生交互,之后一直到事务提交,当session关闭之后

    ,对象脱离hibernate管理了,变为游离态。

  2. 瞬时状态:

    1.new出来的对象,但没有进行session.save();

    2.持久化对象调用delete()方法,持久态对象也会变成瞬时对象;

  3. 持久化对象:

    1.在数据库中通过get(),load(),find()查询出来的对象数据;

    2.瞬时的对象调用save();方法

    3.游离态的对象调用update()方法;

  4. 托管/游离态对象:

    1.手动构建游离态对象;

    2.持久化对象调用evict()(evict方法可以把一个对象从hibernate容器中去除掉),clear()(session.clear方法清空hibernate内部的所有对象),close()方法,可变为游离对象;

  如图:

 

 

  使用getCurrentSession创建session

  为什么不推荐使用openSession方法,看一个例子:

  银行的转账操作:一个数据库的表account,如下:

现在把100块从一个账户转到另外一个账户,代码如下:

    public void delete(String account) {
        Account acc = (Account) session.createQuery("from Account where account = '" + account + "'").uniqueResult();
        Transaction transaction = session.beginTransaction();
        acc.setMoney(acc.getMoney() - 100);
        transaction.commit();
        session.close();
    }

    public void addMoney(String account) {
        Account acc = (Account) session.createQuery("from Account where account = '" + account + "'").uniqueResult();
        Transaction transaction = session.beginTransaction();
        acc.setMoney(acc.getMoney() + 100);
        transaction.commit();
        session.close();
    }
View Code

测试

    @org.junit.Test
    public void testAccount() {
        // 账户1
        Account account1 = new Account();
        account1.setAccount("1");
        account1.setAid(1);
        account1.setMoney(100.0);

        // 账户2
        Account account2 = new Account();
        account2.setAccount("2");
        account2.setAid(2);
        account2.setMoney(200.0);

//        accountDao.save(account1);
//        accountDao.save(account2);
//
        // 把2的账户的钱转100到1账户
        // 先增加1账户100元
        accountDao.addMoney("1");
        // 删除2账户100元
        accountDao.delete("2");
    }
View Code

这是转账之前的表,转账之后如下:

账户2的钱没有少100,这肯定不对。而且JUnit还报错了:org.hibernate.SessionException: Session is closed!,说明当执行sessionFactory.openSession的时候,会创建一个新的session,再执行事务提交的时候,肯定是一个新的事务提交了,说明上面的代码中addMoney中的事务和delete的事务不是同一个事务,但是根据需求分析,这两个方法必须在同一个事务中,如果在同一个事务中则必须在同一个session中!也就是说openSession每次都会创建一个新的session,这样非常耗费内存,且有安全隐患。

  

  使用getCurrentSession

  先检查当前线程中是否有session,如果当前线程中有session,则把session提取出来,直接使用,如果当前线程中没有session,才用openSession方法创建session,然后把新创建的session放入到threadlocal中,当再次得到session的时候就是从当前线程中获取了。其实这非常类似享元设计模式的思想:

减小内存的占用问题——享元模式和单例模式的对比分析

因为数据库的crud操作必须在事务的条件下运行,而使用getCurrentSession创建session,当事务提交的时候,session自动关闭, 这种做法相当于把session和事务绑定在一起了。

 

  再补充一下ThreadLoacl类的作用

  《Java并发编程实践》上面说到:ThreadLoacl是一种线程封闭的实现策略。把变量封闭在线程中,使之变得安全。ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

 

  getCurrentSession用法

  在hibernate.cfg.xml文件中增加配置:

 

  说明session从当前线程中获取。代码中使用无需关闭session事务,非常方便,推荐使用。

this.session = sessionFactory.getCurrentSession();

  下面比较两者的区别:

  1 getCurrentSession创建的session会绑定到当前线程,而openSession不会。

  2 getCurrentSession创建的线程会在事务回滚或事物提交后自动关闭,而openSession必须手动关闭

    这里getCurrentSession使用本地事务(本地事务:jdbc)时 要在配置文件里进行如下设置:

<property name="hibernate.current_session_context_class">thread</property>
  如果使用的是全局事务(jta事务),如下配置:
<property name="hibernate.current_session_context_class">jta</property>

  3.getCurrentSession () 使用当前的session,openSession() 重新建立一个新的session 

 

  使用getCurrentSession的优点

  1)非常适合web程序,并发管理十分容易,session由线程产生,且能够保证一个线程总是只有一个session。
  2)资源回收变得轻松。session将在commit()或rollback()后自动释放,无需再手动关闭事务。
  3)事务管理十分直观,一般来说在业务层或service层的方法前后用三个语句包住:
this.session = sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
transaction.commit();
View Code

就可以让该方法的原子性得到保证。

  4)由于3)的方式应用十分普遍,用spring AOP 对 service 层进行事务控制就更简单了,上面三行代码甚至都不必写。
 
注意:永远不在DAO的方法内做开启session、打开事务、提交事务、释放session这些事,一般来说这不是什么好习惯。一般交给Spring AOP 容器去做事务的管理。

 

  能否不使用事务保存对象

  Hibernate3.3为了提倡大家使用事务,把默认的setAutoCommit设为false,所以,不使用事务也可以实现对象保存,只是Hibenate并不推荐这么做。

 

欢迎关注

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

 

posted @ 2016-03-10 23:13  dashuai的博客  阅读(826)  评论(0编辑  收藏  举报
Flag Counter欢迎关注微信公众号