其实就是我们前面讲的Unit Of Work的必然结果。除非StatelessSession,否则就自然存在。
已经加载到entityManager或session(以下简称context)里面的entity,再次获取的时候不需要查询数据库。
先获取一个id=x的Student(所有关联对象都被设置成eager load)
em.find(Student.class, 5);然后,log演示:
-Resolved object in session cache: [Student#5]
//不会hit DB,因为已经load em.find(Student.class, 5); //不会hit DB,因为已经load em.find(Bed.class, 3); //会hit DB,因为当前em中没有id=4的Bed em.find(Bed.class, 4); //id=5的学生的scoresId为9和10 em.find(Score.class, 9); //不会hit em.find(Score.class, 11); //会hit
但是,如果我们使用criteria查询呢?
//再次使用criteria用name查询Teacher CriteriaQuery<Teacher> queryOnTeacher = builder.createQuery(Teacher.class); Root<Teacher> rootOnTeacher = queryOnTeacher.from(Teacher.class); //fg正是id=5的Student的老师 queryOnTeacher.where(builder.equal(rootOnTeacher.get(Teacher_.name), "fg")); //不会hit DB em.createQuery(queryOnTeacher).getFirstResult();结果就是SELECT * FROM Teacher WHERE name = "fg"的SQL语句依然会执行,但是
对比name="xy"演示,没有:
Initializing object from ResultSet: [Teacher#4]
等log
@想一想@:为什么?因为:
一级缓存的“范围”是基于context的,也就是说,不同的context之间无法共享entity(演示);
EntityManager em2 = entityManagerFactory.createEntityManager(); //因为是“新”的em2,所以一级缓存无法生效 em2.find(Student.class, 5);
但二级缓存就可以(在不同的context)全局共享。
二级缓存需要显式开启,指定cache provider,我们这里用Ehcache:
PS:其他还有官方文档推荐的Infinispan,已经非常流行的分布式缓存redis等
首先maven下载包:
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.8</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-ehcache</artifactId> <version>5.1.17.Final</version> </dependency>
然后在persistence.xml中指定:
<!-- 开启二级缓存,默认false --> <property name="use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory" />还需要用@Cacheable标记哪些entity需要被缓存,用@Cache指定具体的缓存方式:
@Cacheable @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) public class Student
Hibernate默认的策略是不会缓存任何entity,除非显式声明。该策略可以通过javax.persistence.sharedCache.mode修改(略)
为了演示方便,我们设置格式化的缓存log:
<property name="hibernate.cache.use_structured_entries" value="true"/>log演示:
-Converting second-level cache entry [CacheEntry(Student)[atai,17,true,[Ljava.lang.Object;@22fcaf0b,null,我是阿泰,泰山的泰,^_^,2011-05-01,MONDAY,5,5,null]] into entity : [Student#5]
如前所示,我们就Student设置了缓存,再次根据Id获取某Student时,不会SELECT该Student,但还是会SELECT该Student关联的Bed,为什么呢?
其一,Bed上面没有没有@Cacheable!但即使加了@Cacheable:还是不行。
再@想一想@:为什么?因为:
单个entity:dehydrated(脱水),只保留基本的数据(排除方法等)信息,如上所示。或者,用图表示
Hibernate默认不缓存entity的关联集合。
如果要缓存的话,在需要缓存的集合上添加@Cache注解:
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY) @ManyToMany(mappedBy = "students", fetch = FetchType.EAGER) private Set<Teacher> teachers = new HashSet<>();
entity的集合中只缓存每个元素的id,每个具体的元素按单个entity脱水(dehydrated)后缓存:
这是org.hibernate.annotations包下的注解(@Cacheable是javax.persistence下的),它包含一个
主要的作用是指定缓存并发策略(CacheConcurrencyStrategy),即当多个context同时访问某缓存entity并对其进行读写操作时,如何处理可能产生的冲突。其原理和我们之前学习的事务隔离级别(复习)非常类似。
PS:Hibernate通过大量精妙的设计来保证缓存数据和数据库数据的一致性,但是如果第三方在Hibernate之外修改数据库,Hibernate无从监控。
可选的策略包括:(具体实现依赖于provider,此处仅简单粗略说明)
|
说明 |
性能 |
数据完整性 |
READ_ONLY:只读 |
一旦被缓存,就不能被修改。
|
高 |
抛异常 |
NONSTRICT_READ_WRITE:非严格模式的读写 |
修改就加独占锁,直到更改完成 |
较高 |
低 |
READ_WRITE:可读可写 |
修改只加写锁 |
一般 |
一般 |
TRANSACTIONAL:事务级 |
对可能涉及到的数据都加独占锁 |
低 |
高 |
演示代码:
Student atai = em.find(Student.class, 5); atai.setAge(20); //transaction.commit(); EntityManager em2 = entityManagerFactory.createEntityManager(); Student atai2 = em2.find(Student.class, 5);
region和缓存设置相关,比如:缓存过期时间,最多缓存多少条数据,缓存满了之后如何处理……,这些配置都可以写在ehCache的配置文件(ehcache.xml)里,一个region一个设置。
<cache name="nh.17bang.Student" <cache name="normal"
@Cache(region = "normal" public class StudentelCache以后还会使用,我们后面详解讲。
include指示是否缓存lazy-load的元素。默认是要缓存的,如果不想缓存:
@Cache(include = "non-lazy", usage = CacheConcurrencyStrategy.READ_ONLY)
在二级缓存开启的基础上显式开启
<property name="hibernate.cache.use_query_cache" value="true" />然后一样要在查询上设置
em.createQuery(queryOnTeacher) //设置在createQuery()之后 .setHint("org.hibernate.cacheable", "true");
使用字符串"org.hibernate.cacheable"和"true"是非常难受的一种写法……
建议专门建一个类,存储相关的字符串常量:
public class QueryCacheStrings { public final static String CACHABLE = "org.hibernate.cacheable"; public final static String TRUE = "true";
log演示:
key: sql: select teacher0_.id as id1_3_, teacher0_.name as name2_3_, teacher0_.age as age3_3_
Caching query results in region: org.hibernate.cache.internal.StandardQueryCache; timestamp=6698896483094528注意这个时间戳(timestamp)
没有开启二级缓存,查询缓存也不会启动。
但是,查询缓存的结果直接独立存储,不是像集合那样只存元素id。@想一想@:为什么?
因为查询的结果不一定就是entity的集合啊!可以是multiselect之类的投影呀……
Hibernate仍然会保证缓存数据的正确性(和数据库一致),为了做到这一点,Hibernate需要
所以,如果查询所涉及的任何一张表的更改,都会导致缓存失效;如果表的更改较为频繁,使用查询缓存是得不偿失的。
Hibernate自带了一个开发用(即不能用于生成环境)连接池。log演示:
-HHH10001002: Using Hibernate built-in connection pool (not for production use!)
maven下载依赖:
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-hikaricp</artifactId> <version>5.1.17.Final</version> </dependency>这会直接下载两个jar包:
hikaricp本身
然后在persistence.xml中配置:
<property name="hibernate.connection.provider_class" value="org.hibernate.hikaricp.internal.HikariCPConnectionProvider" />
<property name="hibernate.hikari.poolName" value="hh.17bang" /> <property name="hibernate.hikari.minimumIdle" value="2"></property> <property name="hibernate.hikari.maximumPoolSize" value="5"></property>
演示:
-HHH000130: Instantiating explicit connection provider: org.hibernate.hikaricp.internal.HikariCPConnectionProvider
多快好省!前端后端,线上线下,名师精讲
更多了解 加: