说明
在 SpringBoot2.x 之后, 原来使用的 Jedis 被替换成了 lettuce
jedis: 采用直连, 多个线程操作的话, 是不安全的, 如果想要避免不安全, 使用 jedis pool 连接池 它更像BIO
lettuce: 采用netty 实例可以多个线程中进行共享, 不存在线程不安全的情况, 可以减少线程数据 它更像NIO
maven依赖
<dependencies>
<!--mybatis 依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--redis lettuce-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<!--<version>2.1.10.RELEASE</version>-->
<!--<version>1.7.5.RELEASE</version>-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</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>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>redis.clients</groupId>
<artifactId>jedis</artifactId>
<!--<version>3.0.1</version>-->
</dependency>
</dependencies>
application.yml
server:
port: 8080
spring:
datasource:
name: test
url: jdbc:mysql://47.104.248.219:3306/redis?serverTimezone=UTC
username: root
password: 12345678.Lhx
driver-class-name: com.mysql.jdbc.Driver
main:
allow-bean-definition-overriding: true
### Redis 相关配置
redis:
host: 47.104.248.219
port: 6379
timeout: 10000
database: 0
# lettuce:
# pool:
# max-active: 8
# max-wait: -1
# max-idle: 8
# min-idle: 0
# pool:
# max-active: 8
# max-wait: -1
# max-idle: 8
# min-idle: 0
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#开启MyBatis的二级缓存
cache-enabled: true
mapper-locations: classpath:mapping/*Mapper.xml,classpath:mapping/extend/*Mapper.xml #扫描mapper.xml
# type-aliases-package: com.lvhx.pojo
redis.properties
redis.hostName=47.104.248.219
redis.port=6379
#redis.password=613613
redis.timeout=10000
redis.maxIdle=300
redis.maxTotal=1000
redis.maxWaitMillis=1000
redis.minEvictableIdleTimeMillis=300000
redis.numTestsPerEvictionRun=1024
redis.timeBetweenEvictionRunsMillis=30000
redis.testOnBorrow=true
redis.testWhileIdle=true
redis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--
说明: 在 SpringBoot2.x 之后, 原来使用的 Jedis 被替换成了 lettuce
jedis: 采用直连, 多个线程操作的话, 是不安全的, 如果想要避免不安全, 使用 jedis pool 连接池 它更像BIO
lettuce: 采用netty 实例可以多个线程中进行共享, 不存在线程不安全的情况, 可以减少线程数据 它更像NIO
-->
<!-- 1. 引入properties配置文件 -->
<context:property-placeholder location="classpath:redis.properties" />
<!-- 2. redis连接池配置-->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!--最大空闲数-->
<property name="maxIdle" value="${redis.maxIdle}"/>
<!--连接池的最大数据库连接数 -->
<property name="maxTotal" value="${redis.maxTotal}"/>
<!--最大建立连接等待时间-->
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
<!--逐出连接的最小空闲时间 默认1800000毫秒(30分钟)-->
<property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
<!--每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3-->
<property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
<!--逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1-->
<property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
<!--是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个-->
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
<!--在空闲时检查有效性, 默认false -->
<property name="testWhileIdle" value="${redis.testWhileIdle}"/>
</bean>
<!-- 3. redis连接工厂 -->
<!--<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">-->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory" destroy-method="destroy">
<!--<property name="poolConfig" ref="poolConfig"/>-->
<!--IP地址 -->
<property name="hostName" value="${redis.hostName}"/>
<!--端口号 -->
<property name="port" value="${redis.port}"/>
<!--如果Redis设置有密码 -->
<!--<property name="password" value="${redis.password}"/>-->
<!--客户端超时时间单位是毫秒 -->
<property name="timeout" value="${redis.timeout}"/>
</bean>
<!-- 4. redis操作模板,使用该对象可以操作redis -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<!--如果不配置Serializer,那么存储的时候缺省(默认)使用String,如果用User类型存储,那么会提示错误User can't cast to String!! -->
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<!--<property name="valueSerializer">-->
<!--<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>-->
<!--</property>-->
<!--<property name="hashKeySerializer">-->
<!--<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>-->
<!--</property>-->
<!--<property name="hashValueSerializer">-->
<!--<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>-->
<!--</property>-->
<!--开启事务 -->
<property name="enableTransactionSupport" value="true"/>
</bean>
<!-- 5.使用中间类解决RedisCache.RedisTemplate的静态注入,从而使MyBatis实现第三方缓存 -->
<bean id="redisCacheTransfer" class="com.lvhx.util.RedisCacheTransfer">
<property name="redisTemplate" ref="redisTemplate"/>
</bean>
</beans>
start-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--<context:property-placeholder ignore-unresolvable="true" location="classpath:jdbc.properties,classpath:redis.properties" />-->
<context:property-placeholder ignore-unresolvable="true" location="classpath:redis.properties" />
<import resource="redis.xml"></import>
</beans>
启动类
@SpringBootApplication
//此处扫描的是接口
@MapperScan("com.lvhx.dao")
@ComponentScan(basePackages = {"com.lvhx.*"})
@ImportResource("classpath:/start-config.xml")
@EnableCaching
@EnableAutoConfiguration
public class SpringbootMybatisApplication {
// private final Logger logger = LoggerFactory.getLogger(SpringbootMybatisApplication.class);
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisApplication.class, args);
System.out.println("*****服务启动成功*****");
}
}
ApplicationContextHolder
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
*/
public void setApplicationContext(ApplicationContext applicationContext) {
ApplicationContextHolder.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");
}
}
}
MybatisRedisCache
public class MybatisRedisCache implements Cache {
private static final Logger log = LoggerFactory.getLogger(MybatisRedisCache.class);
private String id;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static RedisTemplate redisTemplate;
//private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
public MybatisRedisCache() {}
public MybatisRedisCache(String id) {
System.out.println("===缓存ID = "+id);
this.id = id;
}
public static void setRedisTemplate(RedisTemplate redisTemplate) {
MybatisRedisCache.redisTemplate = redisTemplate;
}
private RedisTemplate<Object, Object> getRedisTemplate(){
return ApplicationContextHolder.getBean("redisTemplate");
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
RedisTemplate redisTemplate = getRedisTemplate();
try {
redisTemplate.boundHashOps(getId()).put(key, value);
log.info("[结果放入到缓存中: " + key + "=" + value+" ]");
}catch (Exception e){
e.printStackTrace();
log.error("[结果放入缓存失败: " + key + "=" + value+" ]");
}
}
@Override
public Object getObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
try {
Object value = redisTemplate.boundHashOps(getId()).get(key);
log.info("[从缓存中获取了: " + key + "=" + value+" ]");
return value;
} catch (Exception e) {
e.printStackTrace();
log.info("[从缓存获取失败: " + key + " ]");
}
return null;
}
@Override
public Object removeObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
Object value = redisTemplate.boundHashOps(getId()).delete(key);
log.info("[从缓存删除了: " + key + "=" + value+" ]");
return value;
}
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete(getId());
log.info("清空缓存!!!");
}
@Override
public int getSize() {
RedisTemplate redisTemplate = getRedisTemplate();
Long size = redisTemplate.boundHashOps(getId()).size();
return size == null ? 0 : size.intValue();
}
/**
* 清理所有缓存
*/
public void clearAll(){
RedisTemplate redisTemplate = getRedisTemplate();
Set keys = redisTemplate.keys("*");
if (!ObjectUtils.isEmpty(keys)){
redisTemplate.delete(keys);
}
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
}
RedisCacheTransfer
public class RedisCacheTransfer {
@Autowired
public void setRedisTemplate(RedisTemplate redisTemplate) {
MybatisRedisCache.setRedisTemplate(redisTemplate);
}
}
RedisConfig
@Configuration
public class RedisConfig {
@Autowired
private LettuceConnectionFactory connectionFactory;
// @Autowired
// private JedisConnectionFactory connectionFactory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
initDomainRedisTemplate(redisTemplate, connectionFactory);
return redisTemplate;
}
/**
* 设置数据存入 redis 的序列化方式
*
* @param template
* @param factory
*/
private void initDomainRedisTemplate(RedisTemplate<String, Object> template, LettuceConnectionFactory factory) {
// 定义 key 的序列化方式为 string
// 需要注意这里Key使用了 StringRedisSerializer,那么Key只能是String类型的,不能为Long,Integer,否则会报错抛异常。
StringRedisSerializer redisSerializer = new StringRedisSerializer();
template.setKeySerializer(redisSerializer);
// 定义 value 的序列化方式为 json
@SuppressWarnings({"rawtypes", "unchecked"})
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash结构的key和value序列化方式
template.setHashKeySerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setEnableTransactionSupport(true);
template.setConnectionFactory(factory);
}
}
OrderController
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 查找所有order
* @return
*/
@RequestMapping(value = {"/findAll"}, produces = {"application/json;charset=UTF-8"}, method = RequestMethod.GET)
public List<Order> findAll() {
List<Order> list = orderService.findAllOreder();
return list;
}
/**
* 测试插入数据
*/
@RequestMapping(value = {"/testInsert"})
public void testInsert() {
Order order = new Order();
order.setId(String.valueOf(System.currentTimeMillis()));
order.setName(String.valueOf(System.currentTimeMillis()));
order.setMessageId(String.valueOf(System.currentTimeMillis()));
orderService.testInsert(order);
}
/**
* 清理所有缓存
*/
@RequestMapping(value = {"/clearCache"})
public void clearCache() {
orderService.clearCache();
}
}
OrderService
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private OrderExtednMapper orderExtednMapper;
public List<Order> findAllOreder(){
List<Order> allInfo = orderExtednMapper.getAllInfo();
return allInfo;
}
public void testInsert(Order order){
orderMapper.insertSelective(order);
}
public void clearCache(){
MybatisRedisCache mybatisRedisCache = new MybatisRedisCache();
mybatisRedisCache.clearAll();
}
}
OrderMapper.xml
<?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.lvhx.dao.OrderMapper" >
<!-- 开启基于redis的二级缓存 -->
<cache type="com.lvhx.util.MybatisRedisCache"/>
</mapper>
gitee地址
git@gitee.com:lvhongxu/springboot-redis.git