SpringBoot30 整合Mybatis-Plus、整合Redis、利用Ehcache实现二级缓存、利用SpringCache和Redis作为缓存
1 环境说明
JDK: 1.8
MAVEN: 3.
SpringBoot: 2.0.4
2 SpringBoot集成Mybatis-Plus
2.1 创建SpringBoot
利用IDEA创建SpringBoot项目,引入web mysql mybatis-plus lombok devtools依赖
技巧01:SpringBoot没有mybatis的启动依赖,需要到maven仓库查询
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.1</version> </dependency>
<?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> <groupId>com.example</groupId> <artifactId>spring_mybatisplus_redis</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring_mybatisplus_redis</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </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-data-redis</artifactId>--> <!--</dependency>--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.2 配置数据库连接
在 application.yml中配置数据库的连接信息
2.3 创建实体类
根据数据表创建对应的实体类
/* Navicat MySQL Data Transfer Source Server : mysql5.4 Source Server Version : 50540 Source Host : localhost:3306 Source Database : testdemo Target Server Type : MYSQL Target Server Version : 50540 File Encoding : 65001 Date: 2018-09-16 22:55:16 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `student` -- ---------------------------- DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(50) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `age` int(10) NOT NULL, `address` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of student -- ---------------------------- INSERT INTO `student` VALUES ('1', 'wys', '24', '重庆'); INSERT INTO `student` VALUES ('2', '王杨帅', '24', '重庆'); INSERT INTO `student` VALUES ('3', '王杨帅', '25', '渝足'); INSERT INTO `student` VALUES ('4', '王杨帅', '25', '重庆'); INSERT INTO `student` VALUES ('5', '杨玉林', '0', '大足'); INSERT INTO `student` VALUES ('6', '杨玉林', '22', '渝足'); INSERT INTO `student` VALUES ('7', '杨玉林', '20', '重庆'); INSERT INTO `student` VALUES ('8', '杨玉林', '24', '大足'); INSERT INTO `student` VALUES ('9', 'wys', '33', '渝足'); INSERT INTO `student` VALUES ('10', '杨玉林', '24', '重庆'); INSERT INTO `student` VALUES ('11', '杨玉林', '32', '渝足'); INSERT INTO `student` VALUES ('12', '杨玉林', '24', '大足'); INSERT INTO `student` VALUES ('13', '杨玉林', '6', '重庆'); INSERT INTO `student` VALUES ('14', '三少', '24', '渝足'); INSERT INTO `student` VALUES ('15', '杨玉林', '4', '大足'); INSERT INTO `student` VALUES ('16', '杨玉林', '24', '渝足'); INSERT INTO `student` VALUES ('17', 'wys', '12', '重庆'); INSERT INTO `student` VALUES ('18', '杨玉林', '0', '渝足'); INSERT INTO `student` VALUES ('19', '杨玉林', '24', null);
2.4 创建持久层接口
技巧01:由于使用的是mybatis-plus,所以持久层接口只要继承了BaseMapper就可以拥有简单的CRUD功能,这一点跟SpringData JPA 很相似
2.5 启动类配置
在启动类上添加@MapperScan注解来扫描持久层接口,只有添加了这个注解才可以依赖注入持久层接口
2.6 测试
》依赖注入持久层接口
》调用mybatis-plus默认提供的方法进行CRUD操作
3 SpringBoot集成Mybatis-Plus进阶
说明:在第2节中是利用mybatis-plus提供的方法进行CRUD操作,其实mybatis-plus是对mybatis的封装,它同样可以向mybatis那样利用xml映射文件来实现数据库操作
3.1 创建mybatis配置文件
<?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> <!-- 打印SQL语句到控制台 --> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> </configuration>
3.2 创建映射文件
技巧01:在resources目录下创建一个xml文件用来存放映射文件
技巧02:在映射接口中声明一个方法
package com.example.spring_mybatisplus_redis.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.spring_mybatisplus_redis.pojo.dataobject.Student; /** * @author 王杨帅 * @create 2018-09-16 22:31 * @desc **/ public interface StudentMapper extends BaseMapper<Student> { Student getById(Long id); }
<?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.example.spring_mybatisplus_redis.mapper.StudentMapper"> <select id="getById" resultType="Student"> SELECT * FROM student WHERE id = #{id} </select> </mapper>
3.3 配置文件
在 application.yml 中配置mybatis配置文件和映射文件位置
3.4 测试效果
4 MyBatis-Plus开启二级缓存
技巧01:mybatis的以及缓存默认是开启的,二级缓存默认是关闭的
技巧02:一级缓存是SqlSession级别,二级缓存是SqlSessionFactory级别
技巧03:从二级换粗中获取到的数据都是缓存数据的副本,从一级缓存中获取到的数据是缓存数据的引用
4.1 Mybatis默认的二级缓存
技巧01:直接在映射文件中添加 <cache /> 即可开启二级缓存
技巧02:mybatis默认的二级缓存是利用Map实现的
4.1.1 序列化实体类
使用二级缓存时必须对实体类进行序列化
4.1.2 映射文件修改
在映射文件中添加 <cache />
4.1.3 利用映射器进行数据操作
技巧01:映射器 = 映射接口 + 映射文件
说明:本案例比较简单,直接在controller类中依赖注入映射器接口,实际开发中应该在service中进行的
4.1.4 测试效果
启动应用后,访问多次 /test/{id}
技巧01:第一次访问时会从数据库中读取数据,后面的就会从二级缓存中读取数据
技巧02:delete、update、insert操作都会清空二级缓存,前提是这三种操作对应的标签上的 flushCache属性值为true(默认值就是true)
4.2 利用EhCache实现二级缓存
4.2.1 引入ehcache依赖
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency>
4.2.2 配置EhCache
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <diskStore path="java.io.tmpdir/Tmp_EhCache" /> <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0" timeToLiveSeconds="3600" memoryStoreEvictionPolicy="LRU" /> <cache name="content" maxEntriesLocalHeap="200" timeToLiveSeconds="3600"> </cache> </ehcache>
4.2.3 修改映射文件
4.2.4 测试效果
启动应用多次访问 /student/{id}
4.3 利用Spring Cache实现缓存
4.3.1 新建一个SpringBoot项目
引入web springcache依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
4.3.2 开启SpringCache依赖
在启动类上利用@EnableCaching来引入SpringCache的默认配置
4.3.3 如何使用
在需要使用缓存的方法上添加相关注解就可以啦,例如
4.4.4 测试效果
启动应用,多次访问 /test/test01 时,只打印了一次日志,因为从第二次开始就会到SpringCache中去寻找对应的缓存数据
说明:SpringCache一般用于service层中的方法的,这里只是为了达到测试效果就直接在controller层中进行了测试
4.4 利用Redis实现缓存
注意使用Redis实现缓存时需要用到SpringCache相关技术,因为SrpingCache支持多种缓存实现
SpringBoot项目集成好SpringCache和Redis后,只需要在application.yml文件中配置 spring.cache.type=redis 就可以啦
技巧01:具体使用时在方法上添加@Cacheable等类似注解就可以啦
spring: datasource: url: jdbc:mysql://127.0.0.1/javaarchitect?characterEncoding=utf-8&useSSL=false username: root password: 182838 thymeleaf: cache: false redis: host: 127.0.0.1 port: 6379 cache: type: redis jpa: properties: hibernate: format_sql: true show_sql: true mybatis-plus: type-aliases-package: com.example.homework.pojo config-location: classpath:/mybatis-config.xml mapper-locations: classpath:xml/*Mapper.xml
5 SpringBoot 集成 Spring Cache
请参见第4.3小节
6 SpringBoot集成Redis
6.1 安装redis服务器
6.2 创建SpringBoot项目
引入web redis lombok devtools依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
6.3 配置redis服务信息
spring.redis.host=127.0.0.1
spring.redis.port=6379
6.4 重写配置RedisTemplate对应Bean的配置
主要是为了设置序列化方式
package com.example.spring_redis02.configs; 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 java.net.UnknownHostException; @Configuration public class RedisConfiguration { /** * 重写RedisTemplate的Bean * @param redisConnectionFactory * @return * @throws UnknownHostException */ @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); jackson2JsonRedisSerializer.setObjectMapper(new ObjectMapper()); template.setKeySerializer(jackson2JsonRedisSerializer); template.setValueSerializer(jackson2JsonRedisSerializer); return template; } }
6.5 编写RedisTemplate工具类
技巧01:这个工具类只是为了简化操作而言的,没有也没关系
package com.example.spring_redis02.utils; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @Component public class RedisUtil { @Autowired private RedisTemplate redisTemplate; /** * 指定缓存失效时间 * * @param key 键 * @param time 时间(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } //============================String============================= /** * 普通缓存获取 * * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 递增 * * @param key 键 * @param by 要增加几(大于0) * @return */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * * @param key 键 * @param by 要减少几(小于0) * @return */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } //================================Map================================= /** * HashGet * * @param key 键 不能为null * @param item 项 不能为null * @return 值 */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * * @param key 键 * @return 对应的多个键值 */ public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashSet 并设置时间 * * @param key 键 * @param map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除hash表中的值 * * @param key 键 不能为null * @param item 项 可以使多个 不能为null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } /** * 判断hash表中是否有该项的值 * * @param key 键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } /** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * * @param key 键 * @param item 项 * @param by 要增加几(大于0) * @return */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * * @param key 键 * @param item 项 * @param by 要减少记(小于0) * @return */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } //============================set============================= /** * 根据key获取Set中的所有值 * * @param key 键 * @return */ public Set<Object> sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根据value从一个set中查询,是否存在 * * @param key 键 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将数据放入set缓存 * * @param key 键 * @param values 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 将set数据放入缓存 * * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) expire(key, time); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 获取set缓存的长度 * * @param key 键 * @return */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 移除值为value的 * * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } //===============================list================================= /** * 获取list缓存的内容 * * @param key 键 * @param start 开始 * @param end 结束 0 到 -1代表所有值 * @return */ public List<Object> lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取list缓存的长度 * * @param key 键 * @return */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 通过索引 获取list中的值 * * @param key 键 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 * @return */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据索引修改list中的某条数据 * * @param key 键 * @param index 索引 * @param value 值 * @return */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 移除N个值为value * * @param key 键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */ public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } } }
6.6 测试效果