-
一级缓存:
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的。
-
一级缓存的工作原理:
用户发起查询请求,查找某条数据,sqlSession先去缓存中查找,是否有该数据,如果有,读取
如果没有,从数据库中查询,并将查询到的数据放入一级缓存区域,供下次查找使用。
但sqlSession执行commit,即增删改操作时会清空缓存。这么做的目的是避免脏读。
-
实际开发中,MyBatis通常和Spring进行整合开发。Spring将事务放到Service中管理,对于每一个service中的sqlsession是不同的,这是通过mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer创建sqlsession自动注入到service中的。 每次查询之后都要进行关闭sqlSession,关闭之后数据被清空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。
-
二级缓存
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
-
UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。
-
Spring和MyBatis整合时, 每次查询之后都要进行关闭sqlSession,关闭之后数据被清空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。那么如果开启二级缓存,关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。
-
总结:
- 对于查询多commit少且用户对查询结果实时性要求不高,此时采用mybatis二级缓存技术降低数据库访问量,提高访问速度。
- 但不能滥用二级缓存,二级缓存也有很多弊端,从MyBatis默认二级缓存是关闭的就可以看出来。
- 二级缓存是建立在同一个namespace下的,如果对表的操作查询可能有多个namespace,那么得到的数据就是错误的。
-
想要使用二级缓存时需要想好两个问题:
- 对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读。
- 对关联表的查询,关联的所有表的操作都必须在同一个namespace。
-
下面是具体的代码实现过程
-
新建一个SpringBoot项目,并导入相关的依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.5</version> <scope>provided</scope> </dependency> </dependencies>
-
在resources下添加mybatis-generator的配置文件generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!--本机数据库驱动jar包存放目录--> <classPathEntry location="C:\Users\Administrator\.m2\repository\mysql\mysql-connector-java\5.1.42\mysql-connector-java-5.1.42.jar"/> <context id="DB2Tables" targetRuntime="MyBatis3"> <commentGenerator> <property name="suppressDate" value="true"/> <property name="suppressAllComments" value="true"/> </commentGenerator> <!--数据库驱动,数据库地址及表名,账号,密码--> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/test" userId="root" password="root"> </jdbcConnection> <javaTypeResolver> <property name="forceBigDecimals" value="false"/> </javaTypeResolver> <!--生成Model类的包名及存放位置--> <javaModelGenerator targetPackage="com.kangswx.springbootmybatisredis.domain" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> <property name="trimStrings" value="true"/> </javaModelGenerator> <!--生成映射文件的包名及存放位置--> <sqlMapGenerator targetPackage="com.kangswx.springbootmybatisredis.mapper.mapping" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> </sqlMapGenerator> <!--生成Dao类的包名及存放位置--> <javaClientGenerator type="XMLMAPPER" targetPackage="com.kangswx.springbootmybatisredis.mapper" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <!--生成对应表及类名,domainObjectName是设置实体类的名字的--> <table tableName="t_user" domainObjectName="User" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table> </context> </generatorConfiguration>
-
在pom.xml中添加mybatis-generator插件
<plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configuration> <configurationFile>src/main/resources/generatorConfig.xml</configurationFile> <verbose>true</verbose> <overwrite>true</overwrite> </configuration> <executions> <execution> <id>Generate MyBatis Artifacts</id> <goals> <goal>generate</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.2</version> </dependency> </dependencies> </plugin>
-
添加Mybatis的配置文件config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!--配置命名规则--> <setting name="mapUnderscoreToCamelCase" value="true" /> </settings> <typeAliases> <typeAlias alias="Integer" type="java.lang.Integer" /> <typeAlias alias="Long" type="java.lang.Long" /> <typeAlias alias="HashMap" type="java.util.HashMap" /> <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" /> <typeAlias alias="ArrayList" type="java.util.ArrayList" /> <typeAlias alias="LinkedList" type="java.util.LinkedList" /> </typeAliases> </configuration>
-
修改resources\application.properties为application.yml,并添加下面的配置项
server: port: 8081 spring: datasource: url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource redis: host: localhost port: 6379 database: 5 jedis: pool: max-active: 8 max-wait: -1ms max-idle: 500 mybatis: config-location: classpath:mybatis/config.xml mapper-locations: classpath*:mybatis/mapper/**/*.xml #打印Mybatis日志 logging: level: com.kangswx.springbootmybatisredis.mapper: debug
-
利用mybatis-generator一键生成mybatis需要的bean、接口和配置文件
-
不会使用mybatis-generator可以参考 SpringBoot整合Mybatis和Mybatis-generator实现代码自动生成
-
新建t_user用作测试
DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, `birthday` datetime DEFAULT NULL, `comment` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
-
建立获取spring容器获取bean工具类,通过Spring Aware(容器感知)来获取到ApplicationContext,然后根据ApplicationContext获取容器中的Bean,因为RedisTemplate的bean不能用@Autowired注解注入
package com.kangswx.springbootmybatisredis.util; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class ApplicationContextHolder implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextHolder.applicationContext = applicationContext; } /** * 获取存储在静态变量中的ApplicationContext * * @return */ public static ApplicationContext getApplicationContext() { checkApplicationContext(); return applicationContext; } /** * 通过bean的名称从ApplicationContext中取得bean,自动转型为所赋值对象的类型 * @param name * @param <T> * @return */ public static <T> T getBean(String name){ checkApplicationContext(); return (T)applicationContext.getBean(name); } /** * 通过bean的类型从ApplicationContext中取得bean,自动转型为所赋值对象的类型 * @param clazz * @param <T> * @return */ public static <T> T getBean(Class<T> clazz){ checkApplicationContext(); return (T)applicationContext.getBeansOfType(clazz); } /** * 清除applicationContext静态变量. */ public static void cleanApplicationContext(){ applicationContext = null; } public static void checkApplicationContext(){ if(null == applicationContext){ throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder"); } } }
-
实现Mybatis缓存接口
package com.kangswx.springbootmybatisredis.util; import org.apache.ibatis.cache.Cache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class MybatisRedisCache implements Cache { private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class); private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private String id; private RedisTemplate redisTemplate; private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间 public MybatisRedisCache() { } public MybatisRedisCache(String id) { if(null == id){ throw new IllegalArgumentException("Cache instances require an ID"); } this.id = id; } public String getId() { return this.id; } @Override public void putObject(Object key, Object value) { RedisTemplate redisTemplate = getRedisTemplate(); ValueOperations opsForValue = redisTemplate.opsForValue(); opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES); } @Override public Object getObject(Object key) { RedisTemplate redisTemplate = getRedisTemplate(); ValueOperations opsForValue = redisTemplate.opsForValue(); return opsForValue.get(key); } @Override public Object removeObject(Object key) { RedisTemplate redisTemplate = getRedisTemplate(); redisTemplate.delete(key); return null; } /** * Clears this cache instance */ @Override public void clear() { RedisTemplate redisTemplate = getRedisTemplate(); redisTemplate.execute((RedisCallback) connection -> { connection.flushDb(); return null; }); } @Override public int getSize() { return 0; } @Override public ReadWriteLock getReadWriteLock() { return readWriteLock; } public RedisTemplate getRedisTemplate() { if(null == redisTemplate){ redisTemplate = ApplicationContextHolder.getBean("redisTemplate"); } return redisTemplate; } }
-
在mapper.xml映射文件中开启缓存(第4行):
<?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.kangswx.springbootmybatisredis.mapper.UserMapper" > <cache type="com.kangswx.springbootmybatisredis.util.MybatisRedisCache"/> <resultMap id="BaseResultMap" type="com.kangswx.springbootmybatisredis.domain.User" > <id column="id" property="id" jdbcType="INTEGER" /> <result column="username" property="username" jdbcType="VARCHAR" /> <result column="password" property="password" jdbcType="VARCHAR" /> <result column="age" property="age" jdbcType="INTEGER" /> <result column="birthday" property="birthday" jdbcType="TIMESTAMP" /> <result column="comment" property="comment" jdbcType="VARCHAR" /> </resultMap> <sql id="Base_Column_List" > id, username, password, age, birthday, comment </sql> <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" > select <include refid="Base_Column_List" /> from t_user where id = #{id,jdbcType=INTEGER} </select> <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" > delete from t_user where id = #{id,jdbcType=INTEGER} </delete> <insert id="insert" parameterType="com.kangswx.springbootmybatisredis.domain.User" > insert into t_user (id, username, password, age, birthday, comment ) values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{birthday,jdbcType=TIMESTAMP}, #{comment,jdbcType=VARCHAR} ) </insert> <insert id="insertSelective" parameterType="com.kangswx.springbootmybatisredis.domain.User" > insert into t_user <trim prefix="(" suffix=")" suffixOverrides="," > <if test="id != null" > id, </if> <if test="username != null" > username, </if> <if test="password != null" > password, </if> <if test="age != null" > age, </if> <if test="birthday != null" > birthday, </if> <if test="comment != null" > comment, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides="," > <if test="id != null" > #{id,jdbcType=INTEGER}, </if> <if test="username != null" > #{username,jdbcType=VARCHAR}, </if> <if test="password != null" > #{password,jdbcType=VARCHAR}, </if> <if test="age != null" > #{age,jdbcType=INTEGER}, </if> <if test="birthday != null" > #{birthday,jdbcType=TIMESTAMP}, </if> <if test="comment != null" > #{comment,jdbcType=VARCHAR}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="com.kangswx.springbootmybatisredis.domain.User" > update t_user <set > <if test="username != null" > username = #{username,jdbcType=VARCHAR}, </if> <if test="password != null" > password = #{password,jdbcType=VARCHAR}, </if> <if test="age != null" > age = #{age,jdbcType=INTEGER}, </if> <if test="birthday != null" > birthday = #{birthday,jdbcType=TIMESTAMP}, </if> <if test="comment != null" > comment = #{comment,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="com.kangswx.springbootmybatisredis.domain.User" > update t_user set username = #{username,jdbcType=VARCHAR}, password = #{password,jdbcType=VARCHAR}, age = #{age,jdbcType=INTEGER}, birthday = #{birthday,jdbcType=TIMESTAMP}, comment = #{comment,jdbcType=VARCHAR} where id = #{id,jdbcType=INTEGER} </update> </mapper>
-
mapper映射接口类要加上@mapper注解,将其添加入ioc容器:
package com.kangswx.springbootmybatisredis.mapper; import com.kangswx.springbootmybatisredis.domain.User; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; @Repository //消除idea中@Autowired时报错的问题 @Mapper public interface UserMapper { int deleteByPrimaryKey(Integer id); int insert(User record); int insertSelective(User record); User selectByPrimaryKey(Integer id); int updateByPrimaryKeySelective(User record); int updateByPrimaryKey(User record); }
-
编写UserService接口
package com.kangswx.springbootmybatisredis.service; import com.kangswx.springbootmybatisredis.domain.User; public interface UserService { User getByid(Integer id); int updateUser(User user); int deleteUserById(Integer id); int addUser(User user); }
-
编写Service实现类
package com.kangswx.springbootmybatisredis.service.impl; import com.kangswx.springbootmybatisredis.domain.User; import com.kangswx.springbootmybatisredis.mapper.UserMapper; import com.kangswx.springbootmybatisredis.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public User getByid(Integer id) { return userMapper.selectByPrimaryKey(id); } @Override public int updateUser(User user) { return userMapper.updateByPrimaryKeySelective(user); } @Override public int deleteUserById(Integer id) { return userMapper.deleteByPrimaryKey(id); } @Override public int addUser(User user) { return userMapper.insert(user); } }
-
编写UserController类(Restfull风格)
package com.kangswx.springbootmybatisredis.controller; import com.kangswx.springbootmybatisredis.domain.User; import com.kangswx.springbootmybatisredis.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/user") public class UserController { @Autowired private UserService userService; /** * 查询接口 * @param id * @return */ @GetMapping() public Object getById(int id){ return userService.getByid(id); } /** * 修改接口 * @param user * @return */ @PutMapping() public Object updateUserById(@RequestBody User user){ return userService.updateUser(user); } /** * 添加接口 * @param user * @return */ @PostMapping() public Object addUser(@RequestBody User user){ return userService.addUser(user); } /** * 删除接口 * @param id * @return */ @DeleteMapping() public Object deleteUser(int id){ return userService.deleteUserById(id); } }
-
通过postman进行相关的测试
-
第一次查询的时候,发现打印出了查询SQL,并且Redis中插入了缓存的数据,如下图
-
第二次查询相同的数据的时候,发现没有打印出查询的SQL,并出现下面的日志
-
查询结果如下图
-
当我们修改MySQL中的数据,再次查询上面的数据的时候,发现查询出来的数据与MySQL的数据不一致,如下图
-
至此,SpringBoot下Redis作为Mybatis的二级缓存的作用正常发挥了。
SpringBoot下Redis作为Mybatis的二级缓存
最新推荐文章于 2022-01-03 22:37:35 发布