Hibernate——缓存

Hibernate的缓存

缓存无论对于应用服务器还是对于数据库服务器来说,都是一个福音,缓存可以减轻数据库的压力,提高查询效率。下面就具体的来介绍一下Hibernate 中的缓存吧。

缓存研究的内容

  • 缓存的生命周期
  • 把一个对象放入到缓存中
  • 把一个对象从缓存中提取出来
  • 把一个对象从缓存中清除
  • 把一些对象从缓存中清除
  • 把缓存中的数据同步到数据库中
  • 把数据库中的数同步到缓存中

数据缓存

数据缓存一般是一个Map 的集合。

对象缓存

1. 对象缓存是一个集合。而不是一个Map
2. 每个对象都是一个唯一的标识符
3. 在Hibernate中,该标识符为主键

一级缓存

说明

  1. 一级缓存是session缓存
  2. 当session开启的时候,一级缓存起作用,当session关闭的时候,一级缓存销毁了
  3. session 的缓存存放的是私有数据

操作

将对象放入一级缓存

get方法
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* 测试 Session.get()方式是否把对象放入到一级缓存中
*/
@Test
public void testGetPerson(){
Session session = HibernateUtils.sessionFactory.openSession();
// 第一次从数据库中查询该记录, 发出 SQL语句
Person person =(Person) session.get(Person.class, 1L);
// 从数据库中查询相同的记录,不发出SQL语句
Person person2 = (Person) session.get(Person.class, 1L);
//SessionFactoryImpl
session.close();
}

说明: Session.get 方法把一个对象放入到一级缓存中

下图为上述代码执行后,hibernate执行的SQL语句,从中可以看出,只是对数据库执行了一次的查询操作。
如果查询的是同一个对象,那么就进行一次查询,而不是多次查询

statictis方法:统计方法
1
2
3
4
5
6
7
8
@Test
public void testStatistics(){
Session session = HibernateUtils.sessionFactory.openSession();
Person person = (Person) session.get(Person.class, 1L);
session.get(Person.class, 2L);
System.out.println("session.getStatistics().getEntityCount(): " + session.getStatistics().getEntityCount());
session.close();
}

说明: 上面的代码中,session的一级缓存中存放了两个对象

下图中通过统计方法可以知道此时的一级缓存中是有两个对象(从数据库中查询出两个)。

这个是一张统计的图片

save方法
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testSave(){
Session session = HibernateUtils.sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Person person = new Person();
person.setName("张小萌");
person.setDescription("萌萌哒");
// 将对象放入一级缓存中
session.save(person);
System.out.println("session.getStatistics().getEntityCount():" + session.getStatistics().getEntityCount() );
transaction.commit();
session.close();
}

说明: session.save() 方法把对象放入到一级缓存中了

把一个对象从一级缓存中清除

evict方法
1
2
3
4
5
6
7
8
9
10
11
@Test
public void testEvict(){
Session session = HibernateUtils.sessionFactory.openSession();
Person person = (Person) session.get(Person.class, 3L);
/**
* Evict()方法将person对象从一级缓存中清除
*/
session.evict(person);
System.out.println("session.getStatistics().getEntityCount():" + session.getStatistics().getEntityCount() );
session.close();
}

上述代码执行的时候,hibernate输出如下的SQL语句:

evict方法

说明,session调用了evict()方发法之后,一级缓存中的对象就被清除了。

clear方法
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testClear(){
Session session = HibernateUtils.sessionFactory.openSession();
Person person = (Person) session.get(Person.class, 2L);
session.get(Person.class, 3L);
// 输出 2
System.out.println("session.getStatistics().getEntityCount():" + session.getStatistics().getEntityCount());
session.clear();
// 使用clear()之后将缓存中的对象所有清除掉。
System.out.println("session.getStatistics().getEntityCount():" + session.getStatistics().getEntityCount());
session.close();
}

很显然,当执行session.clear()方法的时候,一级缓存中对象所有都清除掉了。

close方法

当执行session.close方法的时候,一级缓存的生命更周期结束了。

把数据库的数据同步到缓存中

refresh方法
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testRefresh(){
Session session = HibernateUtils.sessionFactory.openSession();
//先从数据库中查找数据
Person person = (Person) session.get(Person.class, 1L);
person.setName("张小溪");
System.out.println(person.getName());
//发出SQL语句
session.refresh(person);
//将出输出数据库中的原有姓名一致,
System.out.println(person.getName());
session.close();
}

把缓存中的数据同步到数据库中

flush方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void tesFlush(){
Session session =HibernateUtils.sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Person person = new Person();
person.setName("李小天");
// 持久化
Person person2 = (Person) session.get(Person.class, 1L);
person2.setName("hello");
// 持久化
Person person3 = (Person)session.get(Person.class, 2L);
// 临时状态转化为持久化状态
session.save(person);
session.flush();
transaction.commit();
session.close();
}

说明:
当执行flush() 方法的时候,Hibernate会检查一级缓存中所有的持久化转态的对象,如果该持久化状态的对象没有标识符的值,则会发出insert语句,如果该持久化状态的对象有标识符的值,则会对照副本,看是否和副本一致,如果一致,则什么都不做,如果不一致,则发出update语句

执行上述代码的时候hibernate执行如下的SQL语句:
hibernate执行flush

总结

一级缓存提供了一个临时存放对象的一个内存结构,当Hibernate对对象进行操作的时候,仅仅改变的是对象的属性,改变的是一级缓存中的对象的属性,在seeesion.flush之前的代码可以任意写,因为这个时候,并没有和数据库交互。当执行session.flush的时候,Hibernate会检查一级缓存中的对象的情况,发出insert或者update语句。

Session的产生方式

需求

转账的需求:后台有一张表:account表(aid, account,money), 操作将account为1的账号转移100元到account2中。
实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private void reduceMoney(String account){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Account account2 = (Account)session.createQuery("from Account where account='"+account+"'")
.uniqueResult();
account2.setMoney(account2.getMoney()-100);
transaction.commit();
session.close();
}
private void addMoney(String account){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Account account2 = (Account)session.createQuery("from Account where account='"+account+"'")
.uniqueResult();
account2.setMoney(account2.getMoney()+100);
transaction.commit();
session.close();
}
@Test
public void testTrform(){
AccountDao accountDao = new AccountDao();
accountDao.addMoney("1");
int a = 1/0;
accountDao.reduceMoney("2");
}

说明: 这样的写法是不对的,因为在这两个方法中开启了两个session,如果在程序的执行过程中遇到了异常,就有可能出现转账的错误。所以这样的写法是满足不了这种情况的。

sessionFaactory.openSession

每一次执行openSession 的时候会打开一个session ,效率比较低。

sessionFactory.getCurrentSession

原理

1. 从当前线程中(ThreadLocal)中,把session提取出来
2. 如果当前线程中没有session(第一步失败提取失败),则调用openSession方法创建一个session
3. 接着第二步,把session放入到threadlocal中。
4. 再执行第一步的步骤。

优点

不管有几个类完全松耦合,如果这几个类要用到同一个session,在运行的时候,如果这几个类在同一个线程下运行,利用threadlocal就能够保证是同一个session。

步骤

在Hibernate.cfg.xml文件中
1
<property name="current_session_cntext_class">thread</property>
在客户端
1
2
3
4
Session session = sessionFactory.getCurrentSession();
Transacction transaction = session.getCurrentSession();
Account account = (Account)session.get(Account.class, 1L);
transaction.comit();

说明:
1. CRUD操作必须在事务的环境下运行(所以,上述的代码中,必须有session.beginTransaction())。
2. 事务提交,session自动关闭(如果手动再session.close()则会报出“session已经关闭的错误”)。
3. 这种方式强制把事务和session绑定在一起了。

坚持原创技术分享,您的支持将鼓励我继续创作!