二級緩存是多個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