1 二级缓存
1.1 概念
1.1.1 缓存
- 缓存:计算机领域中非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序读写硬盘(永久性数据存储源)的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷。缓存的物理介质通常是内存。
1.1.2 二级缓存
- Hibernate提供缓存机制:一级缓存、二级缓存。
- 一级缓存:Session级别的缓存,在一次请求中共享数据。
- 二级缓存:SessionFactory级别的缓存,整个应用程序共享一个会话工厂,共享一个二级缓存。
- SessionFactory缓存分为两部分:
- 内置缓存:使用一个Map,用于存放配置信息,预定义HQL语句等,提供给Hibernate框架自己使用,对外是只读的。不能写入。
- 外置缓存:使用另一个Map,用于存放用户自定义数据。默认不开启。外置缓存Hibernate只提供规范(接口),需要第三方来实现。
1.1.3 二级缓存的结构
1.1.4 并发访问策略
- transactional:事务型。
- 仅在受管理的缓存中适用。
- 提供Repeateable Read事务隔离级别。
- 适用经常被读,很少修改的数据。
- 可以防止脏读和不可重复读的并发问题。
- 缓存支持事务,发生异常的时候,缓存也能够回滚。
- read-write:读写型。
- 提供Read Committed事务隔离级别。
- 在非集群的环境中适用。
- 使用经常被读,很少修改的数据。
- 可以防止脏读。
- 更新缓存的时候会锁定缓存中的数据。
- nonstrict-read-write:非严格读写型。
- 适用极少被修改,偶尔允许脏读的数据(两个事务同时修改数据的情况很少见)。
- 不保证缓存和数据库中数据的一致性。
- 为缓存数据设置很短的过期时间,从而尽量避免脏读。
- 不锁定缓存中的数据。
- read-only:只读型。
- 适用从来不会被修改的数据。
- 在此模式下,如果对数据进行更新操作,会有异常。
- 事务隔离级别低,并发性能高。
- 在集群环境中也能完美运作。
1.1.5 应用场景
- 适合放入二级缓存中的数据:
- 很少被修改。
- 不是很重要的数据,允许出现偶尔的并发问题。
- 不适合放入二级缓存中的数据:
- 经常被修改。
- 财务数据,绝对不允许出现并发问题、
- 和其他应用数据共享的数据。
1.1.6 二级缓存的提供商
- Hibernate中只定义了二级缓存的接口,实现需要自己选择提供商。
- EHCache:可作为进程(单机)范围内的缓存,存放数据的物理介质可以是内存或者硬盘,对Hibernate的查询缓存提供了支持。支持集群。
- OpenSymphony:可作为进程范围内的缓存,存放数据的物理介质可以是内存或者硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。
- SwarmCache:可作为集群范围内的缓存,但是不支持Hibernate的查询缓存。
- JBossCache:可作为集群范围内的缓存,支持Hibernate的查询缓存。
1.2 整合
1.2.1 ehcache的坐标
<dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> <version>3.8.1</version> </dependency>
1.2.2 配置hibernate.cfg.xml
<!--配置二级缓存--> <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.SingletonEhcacheRegionFactory</property>
- 完整hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!--配置数据库连接参数--> <property name="connection.url"> <![CDATA[ jdbc:mysql://localhost:3306/hibernate?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC ]]> </property> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="connection.username">root</property> <property name="connection.password">123456</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</property> <!--是否显示SQL--> <property name="show_sql">true</property> <!--是否格式化SQL--> <property name="format_sql">true</property> <!--是否自动提交事务--> <property name="connection.autocommit">true</property> <property name="hbm2ddl.auto">update</property> <property name="hibernate.id.new_generator_mappings">false</property> <!--配置c3p0--> <property name="hibernate.connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider </property> <property name="hibernate.c3p0.max_size">2</property> <property name="hibernate.c3p0.min_size">2</property> <property name="hibernate.c3p0.timeout">5000</property> <property name="hibernate.c3p0.max_statements">100</property> <property name="hibernate.c3p0.acquire_increment">2</property> <!--配置二级缓存--> <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.SingletonEhcacheRegionFactory</property> <!--设置隔离级别--> <property name="hibernate.connection.isolation">4</property> <!--配置JavaBean和表的映射关系--> <mapping class="com.sunxiaping.domain.Customer"/> <mapping class="com.sunxiaping.domain.Order"/> </session-factory> </hibernate-configuration>
1.2.3 配置类和集合缓存
- Customer.java
package com.sunxiaping.domain; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import javax.persistence.*; import java.io.Serializable; import java.util.HashSet; import java.util.Set; /** * 客户 */ @Setter @Getter @NoArgsConstructor @Entity @Table(name = "`customer`") @Cacheable @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) public class Customer implements Serializable { /** * 主键 */ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; /** * 客户的名称 */ private String name; /** * 一个客户有许多订单 * 一方放弃外键的维护权 */ @OneToMany(mappedBy = "customer", cascade = {CascadeType.ALL}, fetch = FetchType.LAZY) @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) private Set<Order> orders = new HashSet<>(); }
- Order.java
package com.sunxiaping.domain; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.CacheConcurrencyStrategy; import javax.persistence.*; import java.io.Serializable; import java.math.BigDecimal; /** * 订单 */ @Setter @Getter @NoArgsConstructor @Entity @Table(name = "`order`") @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_ONLY) public class Order implements Serializable { /** * 主键 */ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; /** * 订单名称 */ private String name; /** * 价格 */ private BigDecimal price; /** * 一个订单属于一个客户,多方维护外键 */ @ManyToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER) @JoinColumn(name = "cid") @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) private Customer customer; }
1.2.4 ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <!-- 设置硬盘目录 --> <diskStore path="D:\\temp"/> <!-- maxElementsInMemory 缓存最大个数 --> <!-- eternal 是否永久有效,如果该值设置为true,则timeout将失效 --> <!-- timeToIdleSeconds 失效前的闲置时间 --> <!-- timeToLiveSeconds 失效前的存活时间 --> <!-- diskSpoolBufferSizeMB 缓存区的大小 默认30M --> <!-- maxElementsOnDisk 硬盘最大缓存个数 --> <!-- overflowToDisk 超出缓存最大个数时写入硬盘 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /> </ehcache>
1.3 测试
1.3.1 证明二级缓存的存在
- 示例:
package com.sunxiaping.test; import com.sunxiaping.domain.Customer; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.junit.Test; public class HibernateTest { @Test public void test() { Configuration configuration = new Configuration().configure(); SessionFactory sessionFactory = configuration.buildSessionFactory(); Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); //执行查询后,放在一级缓存和二级缓存中 Customer c1 = session.get(Customer.class, 1); System.out.println(c1); session.clear(); //如果二级缓存中有数据,将从二级缓存中取数据 Customer c2 = session.get(Customer.class, 1); System.out.println(c2); transaction.commit(); session.close(); sessionFactory.close(); } }
- 总结:类级别缓存,只缓存数据,而一级缓存缓存的是对象本身。
1.3.2 证明集合级别的缓存
- 示例:
package com.sunxiaping.test; import com.sunxiaping.domain.Customer; import com.sunxiaping.domain.Order; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.junit.Test; import java.util.Set; public class HibernateTest { @Test public void test() { Configuration configuration = new Configuration().configure(); SessionFactory sessionFactory = configuration.buildSessionFactory(); Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); //执行查询后,放在一级缓存和二级缓存中 Customer c1 = session.get(Customer.class, 1); System.out.println(c1); Set<Order> orders = c1.getOrders(); System.out.println(orders); session.clear(); //如果二级缓存中有数据,将从二级缓存中取数据 Customer c2 = session.get(Customer.class, 1); System.out.println(c2); //默认二级缓存里面缓存的是Order的id,然后根据ID从类级别缓存中查找 Set<Order> orders1 = c2.getOrders(); System.out.println(orders1); transaction.commit(); session.close(); sessionFactory.close(); } }
- 总结:集合缓存只缓存了关联对象的OID的值,如果需要,则从类缓存中寻找,如果类缓存没有,则从数据库中查找。
1.3.3 查询缓存
- 查询缓存默认不使用,需要手动开启。
- 查询缓存:将HQL语句和查询结果进行绑定。通过HQL相同语句可以缓存内容。
- 默认情况下Query只将查询结果放在一级缓存和二级缓存中,但是却不从一级和二级缓存中获取。
- 查询缓存可以让Query从二级缓存中获取。
- 示例:
package com.sunxiaping.test; import com.sunxiaping.domain.Customer; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.query.Query; import org.junit.Test; import java.util.List; public class HibernateTest { @Test public void test() { Configuration configuration = new Configuration().configure(); SessionFactory sessionFactory = configuration.buildSessionFactory(); Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); Query query = session.createQuery(" FROM Customer "); query.setCacheable(true);//设置允许缓存 List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } Customer customer1 = session.get(Customer.class, 1); System.out.println(customer1); transaction.commit(); session.close(); sessionFactory.close(); } }
1.3.4 时间戳缓存
- 任何操作都在时间戳中记录操作时间。
- 示例:
package com.sunxiaping.test; import com.sunxiaping.domain.Customer; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.query.Query; import org.junit.Test; public class HibernateTest { @Test public void test() { Configuration configuration = new Configuration().configure(); SessionFactory sessionFactory = configuration.buildSessionFactory(); Session session1 = sessionFactory.openSession(); Transaction transaction = session1.beginTransaction(); /** * 先修改用户信息 */ Query query = session1.createQuery(" update Customer set name =:name where id =:id"); query.setParameter("name", "李四"); query.setParameter("id", 1); query.executeUpdate(); transaction.commit(); session1.close(); Session session2 = sessionFactory.openSession(); Transaction transaction2 = session2.beginTransaction(); Customer customer = session2.get(Customer.class, 1); System.out.println(customer); transaction2.commit(); session2.close(); sessionFactory.close(); } }