目录
一、SpringBoot集成Mybatis-Plues
二、Mybatis-Plues 使用 redis 作为二级缓存
项目结构:
所需要的所有依赖坐标:
4.0.0
org.example
SpringBoot_MybatisPlues
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.3.4.RELEASE
1.8
org.springframework.boot
spring-boot-starter-web
2.3.4.RELEASE
org.springframework.boot
spring-boot-starter
com.baomidou
mybatis-plus-boot-starter
3.4.0
mysql
mysql-connector-java
org.projectlombok
lombok
1.18.12
provided
junit
junit
test
cn.hutool
hutool-all
5.3.7
org.springframework.boot
spring-boot-starter-data-redis
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.2
${basedir}/src/main/resources/generator/generatorConfig.xml
true
true
1、导入如上 mybatis-plus 依赖
2、在 application.yml 配置文件中加入 Mybatis-plues 的配置信息
mybatis-plus:
# 加入此属性,可以再映射文件的 resultType 中直接写类名
type-aliases-package: com.wanshi.spring.pojo
# 遇到复杂sql语句时,可使用自定义语句,映射文件位置信息
mapper-locations: classpath:/mappers/**.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # mybatis-plus日志
cache-enabled: true #开启二级缓存
3、编写Mybatis-Plues的配置类 MybatisPlusConfig
package com.wanshi.spring.config;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class MybatisPlusConfig {
//配置乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
//paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
4、建立一个实体类:User
package com.wanshi.spring.pojo;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("t_user") //此类对应的表名
public class User implements Serializable { //序列化
@TableId //设置主键
private int noid;
// @TableField("name") //表名与属性名不一致时,使用此注释来修改
private String username;
private double balance;
}
5、编写数据处理层:UserMapper(其实什么都不做,就继承个其他类)
package com.wanshi.spring.mappers;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wanshi.spring.config.cache.MybatisPlusRedisCache;
import com.wanshi.spring.pojo.User;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;
@Mapper
@CacheNamespace(implementation= MybatisPlusRedisCache.class,eviction=MybatisPlusRedisCache.class)
public interface UserMapper extends BaseMapper {}
6、编写数据访问层:UserService
package com.wanshi.spring.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wanshi.spring.mappers.UserIoMoneyMapper;
import com.wanshi.spring.mappers.UserMapper;
import com.wanshi.spring.pojo.ResultBean;
import com.wanshi.spring.pojo.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Service
@Transactional //设置全局事务
public class UserService {
@Resource
private UserMapper userMapper;
@Resource
private UserIoMoneyMapper userIoMoneyMapper;
//新增人员
public ResultBean insert(User user) {
try {
return ResultBean.pass(0, userMapper.insert(user), null);
} catch (Exception e) {
return ResultBean.fail(200, e.getMessage());
}
}
//修改人员
public ResultBean edit(User user) {
try {
return ResultBean.pass(0, userMapper.updateById(user), null);
} catch (Exception e) {
return ResultBean.fail(200, e.getMessage());
}
}
//删除人员
public ResultBean delete(User user) {
try {
//删除用户的所对应的明细
HashMap map = new HashMap<>();
map.put("user_id", user.getNoid());
userIoMoneyMapper.deleteByMap(map);
return ResultBean.pass(0, userMapper.deleteById(6), null);
} catch (Exception e) {
// 捕获异常后不会回滚,设置手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return ResultBean.fail(200, e.getMessage());
}
}
//获取人员分页信息
public ResultBean page(Map map) {
try {
//分页多条件参数查询参数设置
LambdaQueryWrapper userLambdaQueryWrapper = Wrappers.lambdaQuery();
if (map.get("userName") != null) {
userLambdaQueryWrapper.like(User::getUsername, map.get("userName"));
}
return ResultBean.pass(0,
//当前页和每页条数
userMapper.selectPage(new Page<>(Long.valueOf(("" + map.get("pageNumb"))), Long.valueOf(("" + map.get("pageSize")))), userLambdaQueryWrapper),
null);
} catch (Exception e) {
return ResultBean.fail(200, e.getMessage());
}
}
}
可以看到,Mybatis-Plues 的sql语句都不需要自己动手写了,甚至分页也都有方法来实现
具体sql操作请参考:CRUD 接口 | MyBatis-Plus (baomidou.com)
redis是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的Key-Value数据库
需求:已安装 redis
1、导入如上的 hutool工具包、redis 依赖
2、在 application.yml 中配置 redis 信息
spring:
#redis配置
redis:
host: 127.0.0.1
port: 6379
timeout: 5000 #设置超时时间,单位:毫秒
3、在启动类加入注解:
@EnableCaching //开启缓存
4、配置 redis 的配置类 RedisConfig
package com.wanshi.spring.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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;
import java.net.UnknownHostException;
/**
* redis配置类
*/
@Configuration
public class RedisConfig {
/**redis配置模板,可直接copy使用**/
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory)
throws UnknownHostException {
//为了开发的方便,一般直接使用
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(factory);
//json的序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
5、分别设置两个工具类 ApplicationContextUtil,用于在自定义缓存里手动获取bean。RedisUtil,将 redis 相关的命令封装为一个类
ApplicationContextUtil:
package com.wanshi.spring.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @ClassName : ApplicationContextUtil
* @Description : 手动获取bean的工具类
*/
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
/**
*
* @param beanName
* @return
*/
public static Object getBean(String beanName) {
return context.getBean(beanName);
}
}
package com.wanshi.spring.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
*
* Redis工具类,一般企业开发常用的一个工具类,不会去用原生的redis配置类
*
*/
@Component("redisUtil")
public final class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
public Object execute(RedisCallback action){
Object execute = redisTemplate.execute(action);
return execute;
}
/**
* 删除Collection集合中的key
* @param keys
* @return
*/
public Long del(Collection keys){
Long delete = redisTemplate.delete(keys);
return delete;
}
/**
* 模糊查询key
* @param str 模糊查询的条件
* @return
*/
public Set keys(String str){
Set keys = redisTemplate.keys(str);
return keys;
}
/**
* 消息订阅与发布:发布
* @param channel
* @param message
*/
public void convertAndSend(String channel, Object message){
redisTemplate.convertAndSend(channel,message);
}
/**
* 如果不存在且保存的时间大于0即可创建成功,否则创建失败
* @param key 键
* @param value 值
* @param time 缓存的时间
* @return
*/
public boolean setNx(String key,Object value,long time){
try {
if (time > 0) {
return redisTemplate.opsForValue().setIfAbsent(key,value,time,TimeUnit.SECONDS);
} else {
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 如果不存在才创建成功,否则就创建失败
* @param key
* @param value
* @return
*/
public boolean setNx(String key,Object value){
try {
return redisTemplate.opsForValue().setIfAbsent(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
// =============================common============================
/**
* 26
* 指定缓存失效时间
* 27
*
* @param key 键
* 28
* @param time 时间(秒)
* 29
* @return 30
*/
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;
}
}
/**
* 44
* 根据key 获取过期时间
* 45
*
* @param key 键 不能为null
* 46
* @return 时间(秒) 返回0代表为永久有效
* 47
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 53
* 判断key是否存在
* 54
*
* @param key 键
* 55
* @return true 存在 false不存在
* 56
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 67
* 删除缓存
* 68
*
* @param key 可以传一个值 或多个
* 69
*/
@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=============================
/**
* 83
* 普通缓存获取
* 84
*
* @param key 键
* 85
* @return 值
* 86
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 92
* 普通缓存放入
* 93
*
* @param key 键
* 94
* @param value 值
* 95
* @return true成功 false失败
* 96
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 109
* 普通缓存放入并设置时间
* 110
*
* @param key 键
* 111
* @param value 值
* 112
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* 113
* @return true成功 false 失败
* 114
*/
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;
}
}
/**
* 130
* 递增
* 131
*
* @param key 键
* 132
* @param delta 要增加几(大于0)
* 133
* @return 134
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 143
* 递减
* 144
*
* @param key 键
* 145
* @param delta 要减少几(小于0)
* 146
* @return 147
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* 157
* HashGet
* 158
*
* @param key 键 不能为null
* 159
* @param item 项 不能为null
* 160
* @return 值
* 161
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 167
* 获取hashKey对应的所有键值
* 168
*
* @param key 键
* 169
* @return 对应的多个键值
* 170
*/
public Map
6、设置自定义缓存类 MybatisPluesRedisCache,用于缓存的存储
package com.wanshi.spring.config.cache;
import com.wanshi.spring.utils.ApplicationContextUtil;
import com.wanshi.spring.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.util.DigestUtils;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @ClassName : MybatisRedisCache
* @Description : mybatis-plus结合redis自定义缓存管理,注意:该缓存模板并不完善,这里没有设置缓存时间,
* 实际上应该根据应用场景设置过期时间等
*/
@Slf4j
public class MybatisPlusRedisCache implements Cache {
/**
* 注意,这里无法通过@Autowired等注解的方式注入bean,只能手动获取
*/
private RedisUtil redisUtil;
/**
* 手动获取bean
*
* @return
*/
private void getRedisUtil() {
redisUtil = (RedisUtil) ApplicationContextUtil.getBean("redisUtil");
}
// 读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private String id;
public MybatisPlusRedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
log.info("存入缓存");
if (redisUtil == null) {
getRedisUtil();//获取bean
}
try {
//将key加密后存入
redisUtil.hset(this.id.toString(),this.MD5Encrypt(key),value);
} catch (Exception e) {
log.error("存入缓存失败!");
e.printStackTrace();
}
}
@Override
public Object getObject(Object key) {
log.info("获取缓存");
if (redisUtil == null) {
getRedisUtil();//获取bean
}
try {
if (key != null) {
return redisUtil.hget(this.id.toString(),this.MD5Encrypt(key));
}
} catch (Exception e) {
log.error("获取缓存失败!");
e.printStackTrace();
}
return null;
}
@Override
public Object removeObject(Object key) {
log.info("删除缓存");
if (redisUtil == null) {
getRedisUtil();//获取bean
}
try {
if (key != null) {
redisUtil.del(this.MD5Encrypt(key));
}
} catch (Exception e) {
log.error("删除缓存失败!");
e.printStackTrace();
}
return null;
}
@Override
public void clear() {
log.info("清空缓存");
if (redisUtil == null) {
getRedisUtil();//获取bean
}
try {
redisUtil.del(this.id.toString());
} catch (Exception e) {
log.error("清空缓存失败!");
e.printStackTrace();
}
}
@Override
public int getSize() {
if (redisUtil == null) {
getRedisUtil();//获取bean
}
Long size = (Long)redisUtil.execute((RedisCallback) RedisServerCommands::dbSize);
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
/**
* MD5加密存储key,以节约内存
*/
private String MD5Encrypt(Object key){
String s = DigestUtils.md5DigestAsHex(key.toString().getBytes());
return s;
}
}
7、启动 redis 服务器,并且使用Postman 发送请求,会将请求到的数据放入 redis 缓存中,下次读取时会更快,这在数据量大的情况下很明显
效果
第一次请求:
第二次请求:
可以发现,从第一次的 1137ms 缩至第二次的 14ms,提速非常明显
可以连接 redis 查看保存的缓存:
可见已经将查询的数据缓存了
启动 redis 命令:redis-server.exe redis.windows.conf
连接 redis 命令:redis-cli.exe -h 127.0.0.1 -p 6379 -a ''
启动窗口退出操作:CTRL + C
注:当手动向数据库添加数据时不会更新缓存,执行项目中的 CRUD 操作时会刷新缓存
项目资源: https://pan.baidu.com/s/10WpoNNGoyCro-XjsyLRnag?pwd=af6c