文章目录
一、Redis的Java客户端:Jedis
1、jedis介绍
Redis不仅是使用命令来操作,现在基本上主流的语言都有客户端支持,比如java、C、C#、C++、php、Node.js、Go等。
在官方网站里列一些Java的客户端,有Jedis、Redisson、Jredis、JDBC-Redis、等其中官方推荐使用Jedis和Redisson。 在企业中用的最多的就是Jedis,下面我们就重点学习下Jedis。
Jedis同样也是托管在github上,地址:https://github.com/xetorthio/jedis
2、简单案例
前提:redis的服务器端开启,且虚拟机关闭防火墙。
注意:Jedis相当于mysql的connection对象,使用结束后需要close。
添加jar包:
MAVEN依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
单实例连接(Jedis)
@Test
public void testJedis() {
//创建一个Jedis的连接
Jedis jedis = new Jedis("127.0.0.1", 6379);
//密码认证 如果设置了密码,就需要进行认证
//jedis.auth("123");
//执行redis命令,返回值是String类型,跟窗口执行命令的返回信息一致,如 :成功后返回 OK
jedis.set("mytest", "hello world, this is jedis client!");
//从redis中取值
String result = jedis.get("mytest");
//打印结果
System.out.println(result);
//关闭连接
jedis.close();
}
连接池连接(JedisPool)
@Test
public void testJedisPool() {
//创建一连接池对象
JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);
//从连接池中获得连接
Jedis jedis = jedisPool.getResource();
//密码认证 如果设置了密码,就需要进行认证
jedis.auth("123");
String result = jedis.get("mytest");
System.out.println(result);
//关闭连接
jedis.close();
//关闭连接池
jedisPool.close();
}
jedis工具类
package net.bioknow.cdl.sync_project_cfg.util;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import org.apache.commons.lang.StringUtils;
import net.bioknow.mvc.tools.WebPath;
import net.bioknow.webutil.tools.Log;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.exceptions.JedisConnectionException;
public class RedisUtil {
private RedisUtil() {
}
private static JedisPool pool;
private static final int MAX_RETRIES = 3;
private static final ThreadLocal<Integer> RETRIES_HOLDER = ThreadLocal.withInitial(() -> 0);
static {
initJedisPool();
}
/* 初始化池子 */
protected static void initJedisPool() {
synchronized (RedisUtil.class) {
InputStream in = null;
FileInputStream filein = null;
try {
String rootPath = WebPath.getRootPath();
File file = new File(rootPath, "WEB-INF/config/cdl_redis.properties");// 读取配置文件
String host = "127.0.0.1";
int port = 6379;
String auth = "";
int database = 0;
int timeout = 100000;
int maxTotal = 50;
int maxWaitMillis = 10000;
if (file.exists()) {// 读取配置文件存在
filein = new FileInputStream(file);
in = new BufferedInputStream(filein);
ResourceBundle rb = new PropertyResourceBundle(in);
host = rb.getString("redis.host");
port = Integer.parseInt(rb.getString("redis.port"));
database = Integer.parseInt(rb.getString("redis.database"));
timeout = Integer.parseInt(rb.getString("redis.timeout"));
auth = rb.getString("redis.password");
maxTotal = Integer.parseInt(rb.getString("redis.maxTotal"));
maxWaitMillis = Integer.parseInt(rb.getString("redis.maxWaitMillis"));
}
pool = buildJedisPool(host, port, auth, database, timeout, maxTotal, maxWaitMillis);
} catch (Exception e) {
Log.error("", e);
} finally {
try {
if (in != null) {
in.close();
}
if (filein != null) {
filein.close();
}
} catch (Exception e2) {
Log.info("", e2);
}
}
}
}
/* 创建池子 */
private static JedisPool buildJedisPool(String host, int port, String auth, int database, int timeout, int maxtotal, int maxwaitmillis) {
if ("".equals(auth)) auth = null;
// Jedis非线程安全,JedisPool线程安全
boolean conn = testJedisProp(host, port, auth);
if (!conn) return null;
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(8);// 最大空闲连接数, 默认8个 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例。
jedisPoolConfig.setMaxTotal(maxtotal);// 最大连接数, 默认8个
jedisPoolConfig.setMaxWaitMillis(maxwaitmillis);// 表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;
jedisPoolConfig.setTestOnBorrow(true);// 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
return new JedisPool(jedisPoolConfig, host, port, timeout, auth, database);
}
/* 测试redis连接 */
private static boolean testJedisProp(String host, int port, String auth) {
Jedis jedis = new Jedis(host, port);
if (StringUtils.isNotEmpty(auth)) jedis.auth(auth);
String ping = jedis.ping();
if (!ping.equals("PONG")) {
Log.info("redis库连接失败");
jedis.close();
jedis = null;
return false;
}
return true;
}
public static void execute(RedisRunnable runnable) throws Exception {
execute(runnable, true);
}
public static <R> R submit(RedisCallable<R> callable) throws Exception {
return submit(callable, true);
}
public static void execute(RedisRunnable runnable, boolean retry) throws Exception {
if (pool == null) initJedisPool();
if (pool == null) throw new IllegalAccessException("redis 连接失败!");
try (Jedis jedis = pool.getResource()) {
runnable.run(jedis);
} catch (JedisConnectionException e) {
if (retry && incrRetries() < MAX_RETRIES) {
execute(runnable, true);
} else {
throw e;
}
}
}
public static <R> R submit(RedisCallable<R> callable, boolean retry) throws Exception {
if (pool == null) initJedisPool();
if (pool == null) throw new IllegalAccessException("redis 连接失败!");
try (Jedis jedis = pool.getResource()) {
return callable.call(jedis);
} catch (JedisConnectionException e) {
if (retry && incrRetries() < MAX_RETRIES) {
return submit(callable, true);
} else {
throw e;
}
}
}
private static int incrRetries() {
synchronized (Thread.currentThread().getName()) {// 保证原子性
int i = RETRIES_HOLDER.get() + 1;
RETRIES_HOLDER.set(i);
return i;
}
}
public static void closePool(){
if(pool == null) return;
pool.close();
}
// 关闭 jedis
public static void closeJedis(Jedis jedis) {
try {
if (jedis != null) {
jedis.close();
}
} catch (Exception e) {
closeBrokenResource(jedis);
}
}
private static void closeBrokenResource(Jedis jedis) {
try {
if (jedis == null) return;
pool.returnBrokenResource(jedis);
} catch (Exception e) {
destroyJedis(jedis);
}
}
/**
* 在 Jedis Pool 以外强行销毁 Jedis
*/
private static void destroyJedis(Jedis jedis) {
if (jedis != null) {
try {
jedis.quit();
} catch (Exception e) {
Log.error(">>> RedisUtil-jedis.quit() : " + e);
}
try {
jedis.disconnect();
} catch (Exception e) {
Log.error(">>> RedisUtil-jedis.disconnect() : " + e);
}
}
}
public interface RedisRunnable {
void run(Jedis jedis);
}
public interface RedisCallable<R> {
R call(Jedis jedis);
}
public static void main(String[] args) throws Exception {
RedisUtil.execute(jedis -> jedis.set("testkey", "1111"));
String testkey = RedisUtil.submit(jedis -> jedis.get("testkey"));
System.out.println(testkey);
RedisUtil.execute(jedis -> {
Long testkey1 = jedis.del("testkey");
System.out.println(testkey1);
});
// 实现发布订阅抽象类, 监听接收到的消息
JedisPubSub jedisPubSub = new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
// subscribe模式,收到的 发布信息
System.out.println(String.format("on message channel=%s message=%s", channel, message));
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
// subscribe模式,收到的 订阅信息
System.out.println(String.format("on subscribe channel=%s subscribedChannels=%s", channel, subscribedChannels));
}
@Override
public void onUnsubscribe(String channel, int subscribedChannels) {
// subscribe模式,收到的 取消订阅信息
System.out.println(String.format("on un subscribe channel=%s subscribedChannels=%s", channel, subscribedChannels));
}
@Override
public void onPMessage(String pattern, String channel, String message) {
// psubscribe模式, 收到的 发布信息
System.out.println(String.format("on p message channel=%s message=%s, pattern=%s", channel, message, pattern));
}
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
// psubscribe模式, 收到的 订阅信息
System.out.println(String.format("on p subscribe pattern=%s subscribedChannels=%s", pattern, subscribedChannels));
}
@Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
// psubscribe模式, 收到的 取消订阅信息
System.out.println(String.format("on p un subscribe pattern=%s subscribedChannels=%s", pattern, subscribedChannels));
}
@Override
public void onPong(String pattern) {
// subscribe模式 或 psubscribe模式, 收到的ping信息。
System.out.println(String.format("on pong pattern=%s ", pattern));
}
};
// 匹配模式 订阅my-test-*通道; 会阻塞线程,所以新起一个线程
new Thread(()->{
try {
RedisUtil.execute(jedis -> {
jedis.psubscribe(jedisPubSub, "my-test-*");//onPSubscribe 会收到消息
});
} catch (Exception e) {
e.printStackTrace();
}
}).start();
Thread.sleep(1000);
// my-test-channel1 通道 发布消息
RedisUtil.execute(jedis -> {
for (int i = 0; i < 10; i++) {
//Long publish = jedis.publish("my-test-channel", "msg" + i);
Long publish = jedis.publish("my-test-channel1", "channel1-" + i);
System.out.println(publish);
}
});
// my-test-channel2 通道 发布消息
RedisUtil.execute(jedis -> {
for (int i = 0; i < 10; i++) {
//Long publish = jedis.publish("my-test-channel", "msg" + i);
Long publish = jedis.publish("my-test-channel2", "channel2-" + i);
System.out.println(publish);
}
});
// 注意,想直接调用jedisPubSub必须先指定jedis,在第一次用jedis订阅的时候会指定jedis client
jedisPubSub.ping();// onPong 会收到消息
jedisPubSub.subscribe("aaa"); // onSubscribe 会收到消息
jedisPubSub.punsubscribe("my-test-*");// onPUnsubscribe 会收到消息
jedisPubSub.unsubscribe("aaa");// onUnsubscribe 会收到消息
}
}
发布订阅实现
https://www.cnblogs.com/excellencesy/p/11696580.html
https://blog.51cto.com/BADAOLIUMANGQZ/6059793
订阅:
package com.badao.redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import java.text.SimpleDateFormat;
import java.util.Date;
public class RedisSubscriber extends JedisPubSub {
@Override
public void onMessage(String channel, String message) {
SimpleDateFormat df = new SimpleDateFormat("yyyy MM dd HH:mm:ss");
System.out.println("订阅者:订阅频道["+channel+"],收到消息["+message+"],时间:"+df.format(new Date()));
}
public static void main(String[] args) {
System.out.println("启动订阅者");
//创建Jedis
Jedis jedis = new Jedis("192.168.40.133", 6379);
//创建订阅者
RedisSubscriber redisSubscriber = new RedisSubscriber();
//订阅频道
jedis.subscribe(redisSubscriber,"badaodechengxvyuan");
}
}
发布:
package com.badao.redis;
import redis.clients.jedis.Jedis;
public class Publisher {
public static void main(String[] args) {
System.out.println("开始发布......");
//创建Jedis
Jedis jedis = new Jedis("192.168.40.133", 6379);
//发布消息
jedis.publish("badaodechengxvyuan", "紧急通知:...");
System.out.println("消息发送完毕......");
}
}
3、案例改进(整合Spring,让Spring容器管理JedisPool对象)
3.1、导入Spring和Jedis的Jar包依赖
<properties>
<spring.version>4.1.3.RELEASE</spring.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
3.2、在spring的配置文件中配置JedisPool对象
<bean class="redis.clients.jedis.JedisPool">
<constructor-arg name="host" value="192.168.232.10"></constructor-arg>
<constructor-arg name="port" value="6379"></constructor-arg>
</bean>
3.3、测试代码
public static void main(String[] args) {
//1.获取容器对象
ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext-redis.xml");
//2.从容器对象中获取bean对象
JedisPool jedisPool = context.getBean(JedisPool.class);
//3.从JedisPool对象中获取Jedis连接
Jedis jedis = jedisPool.getResource();
//4.使用Jedis连接和redis服务器做交互
String result = jedis.get("key6");
System.out.println(result);
//5.关闭Jedis连接
jedis.close();
}
二、SSM整合redis(简单整合)
1、导入ssm和Jedis的Jar包依赖
2、配置ssm的配置文件及spring整合Jedis配置文件
spring整合Jedis配置文件。
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="1" />
<!-- 最大连接数 -->
<property name="maxTotal" value="5" />
<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
<property name="blockWhenExhausted" value="true" />
<!-- 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1 -->
<property name="maxWaitMillis" value="30000" />
<!-- 在获取连接的时候检查有效性 -->
<property name="testOnBorrow" value="true" />
</bean>
<bean class="redis.clients.jedis.JedisPool" destroy-method="close">
<constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg>
<constructor-arg name="host" value="192.168.232.10"></constructor-arg>
<constructor-arg name="port" value="6379"></constructor-arg>
</bean>
在spring的配置文件中导入整合redis的配置文件:
<import resource="applicationContext-redis.xml"></import>
3、创建序列化工具类
我们向redis里可以用string数据类型来存储任意对象,以byte[]
的格式。我们要缓存List,所以要将list序列化成字节数组。
package com.ssm.util;
import java.io.*;
public class SerializableUtils {
//序列化(Object序列化成byte数组)
public static byte[] serializable(Object object){
//1.字节数组输出流
ByteArrayOutputStream baos= new ByteArrayOutputStream();
try {
//2.对象输出流
ObjectOutputStream oos = new ObjectOutputStream(baos);
//3.通过对象输出流将对象数据写入到字节数组输出流里
oos.writeObject(object);
//4.从字节数组输出流读取object对象数据即可
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//反序列化(byte数组转Object)
public static Object unSerializable(byte[] bytes){
//小优化
if(bytes == null){
return null;
}
//1.字节数组输入流
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
try {
//2.对象输入流
ObjectInputStream ois= new ObjectInputStream(bais);
//3.对象输入流里面读取数据
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
4、注意:需要序列化的pojo要实现可序列化接口,Serializable
5、在service层添加缓存
思路:在读取的时候先去redis缓存中查找,判断是否存在该key,
如果存在,证明缓存过,直接在redis中拿到结果;
如果不存在,表示没有缓存过,或者数据更新了,就去数据库中查,并将结果保存到redis中,最后返回结果。
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Autowired
private JedisPool jedisPool;
@Override
public List<User> getUsers() {
List<User> list = null;
Jedis jedis = jedisPool.getResource();
byte[] key = "users".getBytes();
System.out.println("Users集合的key为:"+key);
// 1.判断redis中存不存在该key
Boolean isExists = jedis.exists(key);
if (isExists){
// 存在key,就直接在缓存中拿
byte[] bytes = jedis.get(key);
System.out.println("从缓存中读key:"+key+",value:"+bytes);
// 反序列化成list
list = (List<User>) SerializableUtils.unSerializable(bytes);
}else{
// 缓存中不存在,从数据库中查询,并把结果放到缓存中。
list = userMapper.selectUserList();
jedis.set(key,SerializableUtils.serializable(list));
System.out.println("缓存中不存在,向缓存中写入,key:"+key+",value:"+SerializableUtils.serializable(list));
}
// 很重要,要关闭jedis
jedis.close();
return list;
}
}
三、使用Redis做MyBatis的二级缓存,在Dao层实现内容缓存(不重要)
实际开发中很少用。
1、在mybatis的全局配置文件中开启二级缓存
<settings>
<!-- 开启全局缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
2、在mapper映射文件中加一个cache标签,指定type属性:Redis(自定义Redis作为MyBatis的二级缓存)
2.1、mapper文件添加cache标签:
<!-- 使用自定义的redis缓存 -->
<cache type="com.ssm.cache.RedisCache"></cache>
<select id="selectUserList2" resultType="com.ssm.pojo.User">
select * from user
</select>
2.2、创建序列化工具类(跟上边的SSM整合redis的工具类一样)
2.3、自定义redis缓存对象(RedisCache)
创建Redis的缓存对象,实现mybatis的cache接口,并实现它的方法。
package com.ssm.cache;
import com.ssm.util.SerializableUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class RedisCache implements Cache {
private static JedisPool jedisPool=null;
private String cacheId=null;
static {
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext-redis.xml");
jedisPool = context.getBean(JedisPool.class);
}
public RedisCache(String cacheId){
this.cacheId=cacheId;
}
@Override
public String getId() {
return this.cacheId;
}
@Override
public void putObject(Object key, Object value) {
Jedis jedis = jedisPool.getResource();
jedis.set(SerializableUtils.serializable(key),SerializableUtils.serializable(value));
jedis.close();
}
@Override
public Object getObject(Object key) {
Jedis jedis = jedisPool.getResource();
byte[] bytes = jedis.get(SerializableUtils.serializable(key));
Object o = SerializableUtils.unSerializable(bytes);
jedis.close();
return o;
}
@Override
public Object removeObject(Object key) {
Jedis jedis = jedisPool.getResource();
byte[] bytes = jedis.get(SerializableUtils.serializable(key));
Object o = SerializableUtils.unSerializable(bytes);
jedis.del(SerializableUtils.serializable(key));
jedis.close();
return o;
}
@Override
public void clear() {
Jedis jedis = jedisPool.getResource();
jedis.flushDB();
jedis.close();
}
@Override
public int getSize() {
Jedis jedis = jedisPool.getResource();
Long dbSize = jedis.dbSize();
jedis.close();
return dbSize.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
ReadWriteLock readWriteLock= new ReentrantReadWriteLock();
return readWriteLock;
}
}
3、注意:需要缓存的pojo需要实现可序列化接口
4、service层等其他代码,正常写
如:service方法
@Override
public List<User> getUsers2() {
return userMapper.selectUserList2();
}
5、结果
访问controller层。发现redis里多了个key
四、在service使用注解的方式使用Redis做缓存
1、导入Jar包依赖:jedis、springdata-jedis的依赖包
<!-- redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- Spring redis缓存。基于注解的redis缓存 -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.6.0.RELEASE</version>
</dependency>
2、配置spring整合redis的配置文件
1. 添加cache名称空间
2. 开启基于注解的缓存支持
3. JedisPoolConfig对象
4. JedisConnectoryFactory
5. RedisTempate
引用connectionFactory
key序列化
value序列化
6. RedisCacheManager
配置文件:
<?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:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- 开启基于注解的缓存支持 -->
<cache:annotation-driven/>
<!-- Jedis连接池配置,相当于DataSource配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="1" />
<!-- 最大连接数 -->
<property name="maxTotal" value="5" />
<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
<property name="blockWhenExhausted" value="true" />
<!-- 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1 -->
<property name="maxWaitMillis" value="30000" />
<!-- 在获取连接的时候检查有效性 -->
<property name="testOnBorrow" value="true" />
</bean>
<!-- 配置JedisConnectionFactory -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="192.168.37.14"/>
<property name="port" value="6379"/>
<property name="database" value="0"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
</bean>
<!-- 配置RedisTemplate,spring实现key和value的序列化 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
</property>
</bean>
<!-- 配置RedisCacheManager -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg index="0" ref="redisTemplate"/>
<property name="defaultExpiration" value="30000"/>
</bean>
</beans>
3、Service层使用注解实现内容缓存
@CacheEable()注解:(添加缓存)
@Cacheable可以标记在一个方法上,也可以标记在一个类上。
当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。
对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而 不 需要再次执行该方法。
Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略。
注意:当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。@Cacheable可以指定三个属性,value、key和condition。
参数 | 解释 | example |
---|---|---|
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如:@Cacheable(value=”mycache”) ,@Cacheable(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | @Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
缓存的key的声明方式:
// key 是指传入时的参数
@Cacheable(value="users", key="#id")
public User find(Integer id) {
return null;
}
// 表示第一个参数
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
return null;
}
// 表示User中的id值
@Cacheable(value="users", key="#user.id")
public User find(User user) {
return null;
}
// 表示第一个参数里的id属性值
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
return null;
}
除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可
以用来生成key。通过该root对象我们可以获取到以下信息。
属性名称 | 描述 | 示例 |
---|---|---|
methodName | 当前方法名 | #root.methodName |
method | 当前方法 | #root.method.name |
target | 当前被调用的对象 | #root.target |
targetClass | 当前被调用的对象的class | #root.targetClass |
args | 当前方法参数组成的数组 | #root.args[0] |
caches | 当前被调用的方法使用的Cache | #root.caches[0].name |
当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。如:
// key值为: user中的name属性的值。 #root.cache[1].name 表示xxx的名字
@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
return null;
}
@CacheEvict()注解:(删除缓存)
@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。
@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。
-
value表示清除操作是发生在哪些Cache上的(对应Cache的名称);
-
key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;
-
condition表示清除操作发生的条件。
-
allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。
// 删除缓存名为users的缓存中的所有元素 @CacheEvict(value="users", allEntries=true) public void delete(Integer id) { System.out.println("delete user by id: " + id); }
-
beforeInvocation是boolean类型,清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素
// 在执行方法前,删除缓存名为users的缓存中的元素 @CacheEvict(value="users", beforeInvocation=true) public void delete(Integer id) { System.out.println("delete user by id: " + id); }
参考:https://blog.youkuaiyun.com/dreamhai/article/details/80642010
service方法
@Cacheable(key = "'users_'+#pageNumber+'_'+#pageSize",value = "getUsers")
public List<User> getUsers3(Integer pageNumber,Integer pageSize) {
return userMapper.selectUserList3();
}
@CacheEvict(value = {"getUsers"},allEntries = true)
public void insert(){
}
// 把缓存名为getUsers的getUser的里面的缓存元素都清除,在执行这个方法前
@CacheEvict(value = {"getUsers","getUser"},allEntries = true,beforeInvocation = true)
public void update(){
}
@CacheEvict(value = {"getUsers","getUser"},allEntries = true,beforeInvocation = true)
public void delete(){
}
分别访问:
http://localhost:8080/toList3?pageNumber=1&pageSize=10
http://localhost:8080/toList3?pageNumber=2&pageSize=10
http://localhost:8080/toList3?pageNumber=3&pageSize=10
redis缓存的结果如下:
缓存名会把这个方法的所有缓存的key放到一起。
如果删除缓存的时候开启了allEntries = true
,那么会把这个getUsers~keys
里所有的缓存都删除,包括getUsers~keys
这条数据
五、Redis实现Tomcat集群session统一管理
1、问题引入
问题: 在tomcat集群中,用户的session不一定放在哪个tomcat中,导致如果请求其他tomcat都要重新登录。
解决方法1: 使用redis同一管理session
解决方法2: nginx计算同一个请求ip的tomcat(nginx的ip hash),使同一个ip请求同一个tomcat
解决方法3: 多个tomcat间通过网络,共享session信息。(一般不用)
2、配置Redis实现Tomcat集群session统一管理
2.1、环境搭建(准备多个tomcat)
在linux中复制一份tomcat,然后修改tomcat的server.xml中的端口号,否则会端口冲突,只能打开一个tomcat。
1. cp -r tomcat7 tomcat7-1
2. cd tomcat7-1
vi conf/server.xml
8005--->8006
8080--->8081
8009--->8010
3. vi /etc/profile
将原来tomcat环境变量配置注释掉,然后重启虚拟机
注意:tomcat的端口号的作用:传送门!
2.2、向tomcat中导包(tomcat7和tomcat8的不同)
链接: https://pan.baidu.com/s/1f4s-UbuTiRKNNqEMKhRuBA
提取码: cmg6
tomcat7:
tomcat8:
2.3、修改context.xml
context.xml文件:
<?xml version='1.0' encoding='utf-8'?>
<!-- The contents of this file will be loaded for each web application -->
<Context>
<!-- Default set of monitored resources -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
host="localhost"
port="6379"
password="123123"
database="0"
maxInactiveInterval="60"
/>
</Context>
host:用来做session管理的redis的主机地址。
port:redis端口号
password:redis的密码
database:redis的库的索引
maxInactiveInterval:最大不活动时间间隔
如果需要 修改redis密码:
vi /usr/local/redis/bin/redis.conf redis配置文件中的requirepass用来指定密码
requirepass 密码
2.4、修改webapps/ROOT/index.jsp
就用ROOT/index.jsp来做session测试,为了区分不同的tomcat的index,所以要修改每个tomcat的index.jsp。还有,一定一定要把这个index的session打开,因为默认tomcat把它给禁用了,你就是配置出天来,他也没反应。
-
修改
<%@ page session="false" %>
将false改为true -
在body标签中添加:
<h1>No1:tomcat</h1> // 第二个tomcat上改为No2:tomcat
<h2><%=session.getId()%></h2>
2.5、测试
开启redis
启动两个tomcat
分别访问两个tomcat
查看redis: