二级缓存是多个SqlSession共享的,其做用域是mapper的同一个namespace,不一样的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将再也不从数据库查询,从而提升查询效率。Mybatis默认没有开启二级缓存须要在setting全局参数中配置开启二级缓存。java
下面是使用Redis来做为Mybatis二级缓存的实例:node
Redis的安装使用的是Docker,Docker的简介mysql
在application.properties文件中配置Redis,Mybatis,开启Mybatis二级缓存等:git
server.port=80 # 数据源配置 spring.datasource.url=jdbc:mysql://localhost:3306/ssb_test spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.username=root spring.datasource.password=root #链接池配置 #spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource #mybatis #entity扫描的包名 mybatis.type-aliases-package=com.xiaolyuh.domain.model #Mapper.xml所在的位置 mybatis.mapper-locations=classpath*:/mybaits/*Mapper.xml #开启MyBatis的二级缓存 mybatis.configuration.cache-enabled=true #pagehelper pagehelper.helperDialect=mysql pagehelper.reasonable=true pagehelper.supportMethodsArguments=true pagehelper.params=count=countSql #日志配置 logging.level.com.xiaolyuh=debug logging.level.org.springframework.web=debug logging.level.org.springframework.transaction=debug logging.level.org.mybatis=debug #redis #database name spring.redis.database=0 #server host spring.redis.host=192.168.195.128 #server password spring.redis.password= #connection port spring.redis.port=6378 #spring.redis.pool.max-idle=8 # pool settings ... #spring.redis.pool.min-idle=0 #spring.redis.pool.max-active=8 #spring.redis.pool.max-wait=-1 #spring.redis.sentinel.master= # name of Redis server #spring.redis.sentinel.nodes= # comma-separated list of host:port pairs debug=false
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>spring-boot-student-mybatis-redis</artifactId> <packaging>jar</packaging> <name>spring-boot-student-mybatis-redis</name> <description>Demo Mybatis Redis for Spring Boot</description> <parent> <groupId>com.xiaolyuh</groupId> <artifactId>spring-boot-student</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath /> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <!--pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.31</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.0.0</version> </dependency> </dependencies> </project>
package com.xiaolyuh.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { /** * 重写Redis序列化方式,使用Json方式: * 当咱们的数据存储到Redis的时候,咱们的键(key)和值(value)都是经过Spring提供的Serializer序列化到数据库的。RedisTemplate默认使用的是JdkSerializationRedisSerializer,StringRedisTemplate默认使用的是StringRedisSerializer。 * Spring Data JPA为咱们提供了下面的Serializer: * GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。 * 在此咱们将本身配置RedisTemplate并定义Serializer。 * @param redisConnectionFactory * @return */ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 设置值(value)的序列化采用Jackson2JsonRedisSerializer。 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // 设置键(key)的序列化采用StringRedisSerializer。 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
经过Spring Aware(容器感知)来获取到ApplicationContext,而后根据ApplicationContext获取容器中的Bean。github
package com.xiaolyuh.holder; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任什么时候候中取出ApplicaitonContext. */ @Component public class SpringContextHolder implements ApplicationContextAware { private static ApplicationContext applicationContext; /** * 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量. */ public void setApplicationContext(ApplicationContext applicationContext) { SpringContextHolder.applicationContext = applicationContext; // NOSONAR } /** * 取得存储在静态变量中的ApplicationContext. */ public static ApplicationContext getApplicationContext() { checkApplicationContext(); return applicationContext; } /** * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型. */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) { checkApplicationContext(); return (T) applicationContext.getBean(name); } /** * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型. */ @SuppressWarnings("unchecked") public static <T> T getBean(Class<T> clazz) { checkApplicationContext(); return (T) applicationContext.getBeansOfType(clazz); } /** * 清除applicationContext静态变量. */ public static void cleanApplicationContext() { applicationContext = null; } private static void checkApplicationContext() { if (applicationContext == null) { throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder"); } } }
自定义缓存须要实现Mybatis的Cache接口,我这里将使用Redis来做为缓存的容器。web
package com.xiaolyuh.cache; import com.xiaolyuh.holder.SpringContextHolder; import org.apache.ibatis.cache.Cache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.CollectionUtils; import java.util.Collections; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 使用Redis来作Mybatis的二级缓存 * 实现Mybatis的Cache接口 */ public class MybatisRedisCache implements Cache { private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class); // 读写锁 private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true); private RedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean("redisTemplate"); private String id; public MybatisRedisCache(final String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } logger.info("Redis Cache id " + id); this.id = id; } @Override public String getId() { return this.id; } @Override public void putObject(Object key, Object value) { if (value != null) { // 向Redis中添加数据,有效时间是2天 redisTemplate.opsForValue().set(key.toString(), value, 2, TimeUnit.DAYS); } } @Override public Object getObject(Object key) { try { if (key != null) { Object obj = redisTemplate.opsForValue().get(key.toString()); return obj; } } catch (Exception e) { logger.error("redis "); } return null; } @Override public Object removeObject(Object key) { try { if (key != null) { redisTemplate.delete(key.toString()); } } catch (Exception e) { } return null; } @Override public void clear() { logger.debug("清空缓存"); try { Set<String> keys = redisTemplate.keys("*:" + this.id + "*"); if (!CollectionUtils.isEmpty(keys)) { redisTemplate.delete(keys); } } catch (Exception e) { } } @Override public int getSize() { Long size = (Long) redisTemplate.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { return connection.dbSize(); } }); return size.intValue(); } @Override public ReadWriteLock getReadWriteLock() { return this.readWriteLock; } }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.xiaolyuh.domain.mapper.PersonMapper"> <cache type="com.xiaolyuh.cache.MybatisRedisCache"> <property name="eviction" value="LRU" /> <property name="flushInterval" value="6000000" /> <property name="size" value="1024" /> <property name="readOnly" value="false" /> </cache> <resultMap id="BaseResultMap" type="com.xiaolyuh.domain.model.Person"> <!-- WARNING - @mbggenerated This element is automatically generated by MyBatis Generator, do not modify. --> <id column="id" property="id" jdbcType="BIGINT"/> <result column="name" property="name" jdbcType="VARCHAR"/> <result column="age" property="age" jdbcType="INTEGER"/> <result column="address" property="address" jdbcType="VARCHAR"/> </resultMap> <sql id="Base_Column_List"> <!-- WARNING - @mbggenerated This element is automatically generated by MyBatis Generator, do not modify. --> id, name, age, address </sql> <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long"> <!-- WARNING - @mbggenerated This element is automatically generated by MyBatis Generator, do not modify. --> select <include refid="Base_Column_List"/> from person where id = #{id,jdbcType=BIGINT} </select> <delete id="deleteByPrimaryKey" parameterType="java.lang.Long"> <!-- WARNING - @mbggenerated This element is automatically generated by MyBatis Generator, do not modify. --> delete from person where id = #{id,jdbcType=BIGINT} </delete> <insert id="insert" parameterType="com.xiaolyuh.domain.model.Person"> <!-- WARNING - @mbggenerated This element is automatically generated by MyBatis Generator, do not modify. --> <selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> insert into person (name, age, address ) values (#{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{address,jdbcType=VARCHAR} ) </insert> <insert id="insertSelective" parameterType="com.xiaolyuh.domain.model.Person"> <!-- WARNING - @mbggenerated This element is automatically generated by MyBatis Generator, do not modify. --> <selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> insert into person <trim prefix="(" suffix=")" suffixOverrides=","> <if test="name != null"> name, </if> <if test="age != null"> age, </if> <if test="address != null"> address, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="name != null"> #{name,jdbcType=VARCHAR}, </if> <if test="age != null"> #{age,jdbcType=INTEGER}, </if> <if test="address != null"> #{address,jdbcType=VARCHAR}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="com.xiaolyuh.domain.model.Person"> <!-- WARNING - @mbggenerated This element is automatically generated by MyBatis Generator, do not modify. --> update person <set> <if test="name != null"> name = #{name,jdbcType=VARCHAR}, </if> <if test="age != null"> age = #{age,jdbcType=INTEGER}, </if> <if test="address != null"> address = #{address,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=BIGINT} </update> <update id="updateByPrimaryKey" parameterType="com.xiaolyuh.domain.model.Person"> <!-- WARNING - @mbggenerated This element is automatically generated by MyBatis Generator, do not modify. --> update person set name = #{name,jdbcType=VARCHAR}, age = #{age,jdbcType=INTEGER}, address = #{address,jdbcType=VARCHAR} where id = #{id,jdbcType=BIGINT} </update> <!-- 对这个语句useCache="true"默认是true,能够不写 --> <select id="findAll" resultMap="BaseResultMap" useCache="true"> select <include refid="Base_Column_List"/> from person </select> <!-- 对这个语句禁用二级缓存 --> <select id="findByPage" resultMap="BaseResultMap" useCache="false"> select <include refid="Base_Column_List"/> from person </select> </mapper>
package com.xiaolyuh.domain.mapper; import com.github.pagehelper.Page; import com.xiaolyuh.domain.model.Person; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper//声明成mybatis Dao层的Bean,也能够在配置类上使用@MapperScan("com.xiaolyuh.domain.mapper")注解声明 public interface PersonMapper { int deleteByPrimaryKey(Long id); int insert(Person record); int insertSelective(Person record); Person selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(Person record); int updateByPrimaryKey(Person record); /** * 获取全部数据 * @return */ List<Person> findAll(); /** * 分页查询数据 * @return */ Page<Person> findByPage(); }
package com.xiaolyuh.domain.model; import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = 1L; private Long id; /** * 名称 */ private String name; /** * 年龄 */ private Integer age; /** * 地址 */ private String address; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Person{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + '}'; } }
package com.xiaolyuh.service; import com.github.pagehelper.Page; import com.xiaolyuh.domain.model.Person; import java.util.List; /** * Created by yuhao.wang on 2017/6/19. */ public interface PersonService { List<Person> findAll(); /** * 分页查询 * @param pageNo 页号 * @param pageSize 每页显示记录数 * @return */ Page<Person> findByPage(int pageNo, int pageSize); void insert(Person person); }
package com.xiaolyuh.service.impl; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.xiaolyuh.domain.mapper.PersonMapper; import com.xiaolyuh.domain.model.Person; import com.xiaolyuh.service.PersonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; /** * Created by yuhao.wang on 2017/6/19. */ @Service @Transactional(readOnly = true) public class PersonServiceImpl implements PersonService { @Autowired private PersonMapper personMapper; @Override public List<Person> findAll() { return personMapper.findAll(); } @Override public Page<Person> findByPage(int pageNo, int pageSize) { PageHelper.startPage(pageNo, pageSize); return personMapper.findByPage(); } @Override @Transactional public void insert(Person person) { personMapper.insert(person); } }
package com.xiaolyuh; import com.alibaba.fastjson.JSON; import com.github.pagehelper.Page; import com.xiaolyuh.domain.model.Person; import com.xiaolyuh.holder.SpringContextHolder; import com.xiaolyuh.service.PersonService; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.test.context.junit4.SpringRunner; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @RunWith(SpringRunner.class) @SpringBootTest public class PersonMapperTests { private Logger logger = LoggerFactory.getLogger(PersonMapperTests.class); @Autowired private PersonService personService; @Autowired private RedisTemplate<String, Object> redisTemplate; Person person = null; @Before public void testInsert() { person = new Person(); person.setName("测试"); person.setAddress("address"); person.setAge(10); personService.insert(person); Assert.assertNotNull(person.getId()); logger.debug(JSON.toJSONString(person)); } @Test public void testFindAll() { List<Person> persons = personService.findAll(); Assert.assertNotNull(persons); logger.debug(JSON.toJSONString(persons)); } @Test public void testFindByPage() { Page<Person> persons = personService.findByPage(1, 2); Assert.assertNotNull(persons); logger.debug(persons.toString()); logger.debug(JSON.toJSONString(persons)); } // 测试mybatis缓存 @Test public void testCache() { long begin = System.currentTimeMillis(); List<Person> persons = personService.findAll(); long ing = System.currentTimeMillis(); personService.findAll(); long end = System.currentTimeMillis(); logger.debug("第一次请求时间:" + (ing - begin) + "ms"); logger.debug("第二次请求时间:" + (end - ing) + "ms"); Assert.assertNotNull(persons); logger.debug(JSON.toJSONString(persons)); } // 测试Redis存储和获取一个List @Test public void testRedisCacheSetList() { List<Person> persons = new ArrayList<>(); persons.add(person); persons.add(person); persons.add(person); redisTemplate.opsForValue().set(person.getId() + "", persons, 2, TimeUnit.MINUTES); persons = (List<Person>) redisTemplate.opsForValue().get(person.getId() + ""); System.out.println(JSON.toJSONString(persons)); } // 测试Redis存储和获取一个Object @Test public void testRedisCacheSetObject() { redisTemplate.opsForValue().set(person.getId() + "", person, 2, TimeUnit.MINUTES); Object p = redisTemplate.opsForValue().get(person.getId() + ""); if (p instanceof Person) { Person person1 = (Person) p; System.out.println(JSON.toJSONString(person1)); } } // 测试 经过Spring Aware获取Spring容器中的额Bean @Test public void testApplicationContextAware() { RedisTemplate redisTemplate = SpringContextHolder.getBean("redisTemplate"); System.out.println(redisTemplate); } }
2017-06-29 15:22:22.351 DEBUG 12976 --- [ main] com.xiaolyuh.domain.mapper.PersonMapper : Cache Hit Ratio [com.xiaolyuh.domain.mapper.PersonMapper]: 0.5 2017-06-29 15:22:22.351 DEBUG 12976 --- [ main] org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b2a4332] 2017-06-29 15:22:22.351 DEBUG 12976 --- [ main] org.mybatis.spring.SqlSessionUtils : Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b2a4332] 2017-06-29 15:22:22.351 DEBUG 12976 --- [ main] org.mybatis.spring.SqlSessionUtils : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b2a4332] 2017-06-29 15:22:22.351 DEBUG 12976 --- [ main] org.mybatis.spring.SqlSessionUtils : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b2a4332] 2017-06-29 15:22:22.353 DEBUG 12976 --- [ main] com.xiaolyuh.PersonMapperTests : 第一次请求时间:92ms 2017-06-29 15:22:22.354 DEBUG 12976 --- [ main] com.xiaolyuh.PersonMapperTests : 第二次请求时间:68ms
https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releasesredis
spring-boot-student-mybatis-redis工程spring