读者注意:接上篇,请先阅读上篇。
一 环境
基础环境:jdk1.7+spring3.2.9+mybatis3.2.2+redis3.2.8+mysql5.6
引入额外的依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.4.1</version>
</dependency>
maven会自动添加上如下依赖包:
spring-data-redis-1.3.1.RELEASE.jar
jedis-2.4.1.jar
commons-pool2-2.0.jar
附加工具及验证工具:
centos6.5 64位 + SecureCRT6.7 + RedisDesktopManager0.8.8
二 服务器架构
三 关键代码
=======================redis参数配置=====================================
#*****************jedis连接参数设置*********************
#redis服务器ip
redis.ip=169.254.130.122
#redis服务器端口号
redis.port=6379
#redis访问密码
redis.passWord=test123
#与服务器建立连接的超时时间
redis.timeout=3000
#************************jedis池参数设置*******************
#jedis的最大活跃连接数
jedis.pool.maxActive=50
#jedis最大空闲连接数
jedis.pool.maxIdle=10
#jedis池没有连接对象返回时,等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。
#如果超过等待时间,则直接抛出JedisConnectionException
jedis.pool.maxWait=1500
#从池中获取连接的时候,是否进行有效检查
jedis.pool.testOnBorrow=true
#归还连接的时候,是否进行有效检查
jedis.pool.testOnReturn=true
<?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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-3.2.xsd">
<import resource="classpath:configs/spring/applicationContext-*.xml"/>
<bean id="config" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:configs/druidConfig.properties</value>
<value>classpath:configs/redis.properties</value>
</list>
</property>
</bean>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${driverClassName}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<property name="filters" value="${filters}" />
<property name="initialSize" value="${initialSize}" />
<property name="maxActive" value="${maxActive}" />
<property name="minIdle" value="${minIdle}" />
<property name="maxWait" value="${maxWait}" />
<property name="validationQuery" value="${validationQuery}" />
<property name="testWhileIdle" value="${testWhileIdle}" />
<property name="testOnBorrow" value="${testOnBorrow}" />
<property name="testOnReturn" value="${testOnReturn}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${maxPoolPreparedStatementPerConnectionSize}" />
<property name="removeAbandoned" value="${removeAbandoned}" />
<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
</bean>
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="druidDataSource"></property>
<property name="configLocation">
<value>classpath:configs/mybatis-config.xml</value>
</property>
<property name="mapperLocations">
<list>
<value>classpath:com/wx/entitys/*.xml</value>
</list>
</property>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sessionFactory"></constructor-arg>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<tx:advice id="txAdvise" transaction-manager="txManager">
<tx:attributes>
<tx:method name="find*" read-only="true"/>
<tx:method name="search*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="do*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* com.wx.service.*.*(..))" id="myCut"/>
<aop:advisor advice-ref="txAdvise" pointcut-ref="myCut"/>
</aop:config>
<!-- 配置redis 单机版 start-->
<!-- redis数据源 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大空闲数 -->
<property name="maxIdle" value="${jedis.pool.maxIdle}" />
<!-- 最大活跃数 -->
<property name="maxTotal" value="${jedis.pool.maxActive}" />
<!-- 最大等待时间 -->
<property name="maxWaitMillis" value="${jedis.pool.maxWait}" />
<!-- 返回连接时,检测连接是否成功 -->
<property name="testOnBorrow" value="${jedis.pool.testOnBorrow}" />
</bean>
<!-- Spring-redis连接池管理工厂 -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<!-- IP地址 -->
<property name="hostName" value="${redis.ip}" />
<!-- 端口号 -->
<property name="port" value="${redis.port}" />
<!-- 密码 -->
<property name="password" value="${redis.passWord}"/>
<!-- 超时时间 -->
<property name="timeout" value="${redis.timeout}" />
<property name="poolConfig" ref="poolConfig" />
</bean>
<!-- 使用中间类解决RedisCache.jedisConnectionFactory的静态注入,从而使MyBatis实现第三方缓存 -->
<bean id="redisCacheTransfer" class="com.wx.utils.RedisCacheTransfer">
<property name="jedisConnectionFactory" ref="jedisConnectionFactory"/>
</bean>
<!-- 单机版Redis集成 End -->
</beans>
Mybatis为了方便我们扩展缓存定义了一个Cache接口,看看ehcache-mybatis的源码就明白了。我们要使用自己的cache同样的实现Cache接口即可。
package com.wx.utils;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
public class RedisCache implements Cache {
private static JedisConnectionFactory jedisConnectionFactory;
private final String id;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public RedisCache(final String id){
if (id == null) {
throw new IllegalArgumentException("cache instances require an ID");
}
this.id = id;
}
/**
* 缓存的清除策略,只要有增删改操作,那么就会自动调用它 ,如果不需要,请注释掉
*/
public void clear() {
RedisConnection connection = null;
try {
connection=jedisConnectionFactory.getConnection();
connection.flushDb();
connection.flushAll();
System.out.println("缓存被清空了....");
} catch (Exception e) {
e.printStackTrace();
}finally{
if (connection != null) {
connection.close();
}
}
}
@Override
public String getId() {
return this.id;
}
@Override
public Object getObject(Object key) {
System.out.println("--------------------------------get_key==>"+key+"<==");
Object result = null;
//JedisConnection connection = null;
RedisConnection connection = null;
try {
connection = jedisConnectionFactory.getConnection();
RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
result = serializer.deserialize(connection.get(serializer.serialize(key)));
} catch (Exception e) {
e.printStackTrace();
}finally{
if (connection != null) {
connection.close();
}
}
return result;
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
@Override
public int getSize() {
int result = 0;
//JedisConnection connection = null;
RedisConnection connection = null;
try {
connection = jedisConnectionFactory.getConnection();
result = Integer.valueOf(connection.dbSize().toString());
} catch (Exception e) {
e.printStackTrace();
}finally{
if (connection != null) {
connection.close();
}
}
return result;
}
@Override
public void putObject(Object key, Object value) {
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>put_key===>"+key+"<===");
//JedisConnection connection = null;
RedisConnection connection = null;
try {
connection = jedisConnectionFactory.getConnection();
RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
System.out.println("**"+serializer.serialize(key));
connection.set(serializer.serialize(key), serializer.serialize(value));
} catch (Exception e) {
e.printStackTrace();
}finally{
if (connection != null) {
connection.close();
}
}
}
@Override
public Object removeObject(Object key) {
//JedisConnection connection = null;
RedisConnection connection=null;
Object result = null;
try {
System.out.println("===>"+key+"一个缓存被删除了");
connection = jedisConnectionFactory.getConnection();
RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
result = connection.expireAt(serializer.serialize(key), 0);
} catch (Exception e) {
e.printStackTrace();
}finally{
if (connection != null) {
connection.close();
}
}
return result;
}
public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
RedisCache.jedisConnectionFactory = jedisConnectionFactory;
}
}
========================
package com.wx.utils;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
//该类用于JedisConnectionFactory对象的注入
public class RedisCacheTransfer {
public void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
RedisCache.setJedisConnectionFactory(jedisConnectionFactory);
}
}
===============================
在看ehcache-mybatis的源码 它真正使用cache的方式是通过集成org.apache.ibatis.cache.decorators.LoggingCache 这个类实现的,所以我们也继承package com.wx.utils;
import org.apache.ibatis.cache.decorators.LoggingCache;
public class LoggingRedisCache extends LoggingCache{
public LoggingRedisCache(String id) {
super(new RedisCache(id));
}
}
在mapper.xml中添加如下cache标签
<!-- 支持缓存配置 -->
<cache type="com.wx.utils.LoggingRedisCache" />
在mybatis核心配置文件中启用缓存功能
<!-- 配置mybatis的缓存,延迟加载等相关属性 -->
<settings>
<!-- 是否开启全局缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 查询时,关闭关联对象即时加载以提高性能 -->
<setting name="lazyLoadingEnabled" value="false"/>
<!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指 定),不会加载关联表的所有字段,以提高性能 -->
<setting name="aggressiveLazyLoading" value="true"/>
<!-- 配置默认的执行器。SIMPLE 执行器没有什么特别之处。REUSE 执行器重用预处理语句。BATCH 执行器重用语句和批量更新 -->
<setting name="defaultExecutorType" value="REUSE" />
<!-- 对于未知的SQL查询,允许返回不同的结果集以达到通用的效果 -->
<setting name="multipleResultSetsEnabled" value="true"/>
</settings>
四 验证
应用程序运行前,缓存服务器里面没有任何缓存,如图
进行登录操作,并进入综合查询页码
这中间会经历4个sql语句
select * from users where userName=? and passWord=?
SELECT s.stuId,s.stuName,s.gender,s.age,s.address,d.departName FROM student s inner join department d on s.deptidd=d.deptid where 1=1
ORDER BY AGE DESC
LIMIT ?,?
SELECT count(*) FROM student s inner join department d on s.deptidd=d.deptid where 1=1
select departName from department
然后我们可以看到缓存服务器上就会有对应的4个键值对
直接从linux服务器上看
从运行结果上看,先从缓存取,没取到从数据库取,然后存入缓存
从redis图形管理界面也可以看出
往后翻,发现一个sql一个key
再次运行同样的sql,发现只从缓存取数据了,而没必要从数据库取数据了
证明独立缓存集成配置成功!!!
这里有个问题,如果进行增删改操作,会默认删除所有的缓存,而不是按照mapper的命名空间刷新缓存,如果有读者解决了这个问题请留言告诉我,感激不尽!!!
该缓存解决方案并不能实现对缓存的精确控制!
比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,
但是要求用户每次都能查询最新的商品信息,此时就无法实现当一个商品变化时
只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,
当一个商品信息变化会将所有商品信息的缓存数据全部清空。
解决此类问题需要在业务层根据需求对数据有针对性缓存。
请读者继续阅读我的后面的博文,会提供相应解决方案!!!