• SSM+redis整合(mybatis整合redis做二级缓存)


      

      SSM:是Spring+Struts+Mybatis ,另外还使用了PageHelper

      

    前言:

      这里主要是利用redis去做mybatis的二级缓存,mybaits映射文件中所有的select都会刷新已有缓存,如果不存在就会新建缓存,所有的insert,update操作都会更新缓存。(这里需要明白对于注解写的SQL语句不会操作缓存,我的增加方法是注解写的就没有清空缓存,后来改为XML中写就清空缓存了,这个问题没有解决?)

      redis的好处也显而易见,可以使系统的数据访问性能更高。本节只是展示了整合方法和效果,后面会补齐redis集群、负载均衡和session共享的文章。

    开始整合工作:

      0.目录结构:


    UserAction .java
    package cn.qlq.Action;
    
    import java.sql.SQLException;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.apache.struts2.ServletActionContext;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Scope;
    import org.springframework.stereotype.Controller;
    
    import com.github.pagehelper.PageHelper;
    import com.opensymphony.xwork2.ActionSupport;
    
    import cn.qlq.bean.User;
    import cn.qlq.service.UserService;
    
    @Controller
    @Scope("prototype")
    @SuppressWarnings("all")
    public class UserAction extends ActionSupport {
        private Map<String, Object> response;
        @Autowired
        private UserService userService;
        private int id;
        private String name;
        public String add() {
            try {
                userService.addUser(id, name);
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return "add";
        }
    
        public String delete() {
            return "delete";
        }
    
        public String update() {
            return "update";
        }
    
        public String find() throws Exception {
            User user = userService.findUserById(1);
            System.out.println(user);
            HttpServletRequest request = ServletActionContext.getRequest();
            request.setAttribute("user", user);
            return "find";
        }
    
        public String findPage() throws Exception {
            response = new HashMap();
            // 第三个参数代表排序方式
            PageHelper.startPage(2, 2, "id desc");
            List<User> users = userService.findUsersByPage();
            response.put("users", users);
            return "success";
        }
    
        public Map getResponse() {
            return response;
        }
    
        public void setResponse(Map response) {
            this.response = response;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }

      1.后台启动Redis-server,用redis-cli.exe能连接上则证明开启成功。我是服务注册的Redis。(参考:http://www.cnblogs.com/qlqwjy/p/8554215.html)

      2.开始整合redis缓存:

    • 首先在pom.xml中增加需要的redis jar包
            <!-- jedis依赖 -->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>2.7.1</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-redis</artifactId>
                <version>1.6.2.RELEASE</version>
            </dependency>
    • pom.xml写好后,还需要新增两个配置文件:redis.properties
    redis.host=127.0.0.1
    redis.port=6379
    redis.pass=123456
    redis.maxIdle=200
    redis.maxActive=1024
    redis.maxWait=10000
    redis.testOnBorrow=true
    • 其中字段也都很好理解,再加入配置文件:applicationContext-redis.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd ">
    
    <!-- 连接池基本参数配置,类似数据库连接池 -->
        <context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true"/>
    
         <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
             <property name="maxTotal" value="${redis.maxActive}"/>
             <property name="maxIdle" value="${redis.maxIdle}" />
             <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
         </bean>
         
         <!-- 连接池配置,类似数据库连接池 -->
         <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
             <property name="hostName" value="${redis.host}"></property>
             <property name="port" value="${redis.port}"></property>
             <!-- <property name="password" value="${redis.pass}"></property> -->
             <property name="poolConfig"  ref="poolConfig"></property> 
         </bean>
         
          <bean id="redisCacheTransfer" class="cn.qlq.jedis.RedisCacheTransfer">
             <property name="jedisConnectionFactory" ref="jedisConnectionFactory" />
         </bean>
         
    </beans>

      注意,刚开始我的context标签没有加ignore-unresolvable,报了个错误,解决办法参考:http://www.cnblogs.com/qlqwjy/p/8556017.html

     配置文件写好后,就开始java代码的编写:

    JedisClusterFactory.java

    package cn.qlq.jedis;
    
    
    import java.util.HashSet;
    import java.util.Properties;
    import java.util.Set;
    import java.util.regex.Pattern;
    
    import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
    import org.springframework.beans.factory.FactoryBean;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.core.io.Resource;
    
    import redis.clients.jedis.HostAndPort;
    import redis.clients.jedis.JedisCluster;
    
    public class JedisClusterFactory implements FactoryBean<JedisCluster>, InitializingBean {
    
        private Resource addressConfig;
        private String addressKeyPrefix;
    
        private JedisCluster jedisCluster;
        private Integer timeout;
        private Integer maxRedirections;
        private GenericObjectPoolConfig genericObjectPoolConfig;
    
        private Pattern p = Pattern.compile("^.+[:]\d{1,5}\s*$");
    
        public JedisCluster getObject() throws Exception {
            return jedisCluster;
        }
    
        public Class<? extends JedisCluster> getObjectType() {
            return (this.jedisCluster != null ? this.jedisCluster.getClass() : JedisCluster.class);
        }
    
        public boolean isSingleton() {
            return true;
        }
    
        private Set<HostAndPort> parseHostAndPort() throws Exception {
            try {
                Properties prop = new Properties();
                prop.load(this.addressConfig.getInputStream());
    
                Set<HostAndPort> haps = new HashSet<HostAndPort>();
                for (Object key : prop.keySet()) {
    
                    if (!((String) key).startsWith(addressKeyPrefix)) {
                        continue;
                    }
    
                    String val = (String) prop.get(key);
    
                    boolean isIpPort = p.matcher(val).matches();
    
                    if (!isIpPort) {
                        throw new IllegalArgumentException("ip 或 port 不合法");
                    }
                    String[] ipAndPort = val.split(":");
    
                    HostAndPort hap = new HostAndPort(ipAndPort[0], Integer.parseInt(ipAndPort[1]));
                    haps.add(hap);
                }
    
                return haps;
            } catch (IllegalArgumentException ex) {
                throw ex;
            } catch (Exception ex) {
                throw new Exception("解析 jedis 配置文件失败", ex);
            }
        }
    
        public void afterPropertiesSet() throws Exception {
            Set<HostAndPort> haps = this.parseHostAndPort();
    
            jedisCluster = new JedisCluster(haps, timeout, maxRedirections, genericObjectPoolConfig);
    
        }
    
        public void setAddressConfig(Resource addressConfig) {
            this.addressConfig = addressConfig;
        }
    
        public void setTimeout(int timeout) {
            this.timeout = timeout;
        }
    
        public void setMaxRedirections(int maxRedirections) {
            this.maxRedirections = maxRedirections;
        }
    
        public void setAddressKeyPrefix(String addressKeyPrefix) {
            this.addressKeyPrefix = addressKeyPrefix;
        }
    
        public void setGenericObjectPoolConfig(GenericObjectPoolConfig genericObjectPoolConfig) {
            this.genericObjectPoolConfig = genericObjectPoolConfig;
        }
    
    }

    RedisCache.java

    package cn.qlq.jedis;
    
    
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    import org.apache.ibatis.cache.Cache;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.data.redis.connection.jedis.JedisConnection;
    import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
    import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    
    import redis.clients.jedis.exceptions.JedisConnectionException;
    
    public class RedisCache implements Cache {
        private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
    
        private static JedisConnectionFactory jedisConnectionFactory;
    
        private final String id;
    
        private final ReadWriteLock rwl = new ReentrantReadWriteLock();
        
    
        public RedisCache(final String id) {
            if (id == null) {
                throw new IllegalArgumentException("Cache instances require an ID");
            }
            logger.debug("MybatisRedisCache:id=" + id);
            this.id = id;
        }
    
        /**
         * 清空所有缓存
         */
        public void clear() {
            rwl.readLock().lock();
            JedisConnection connection = null;
            try {
                connection = jedisConnectionFactory.getConnection();
                connection.flushDb();
                connection.flushAll();
                logger.debug("清除缓存.......");
            } catch (JedisConnectionException e) {
                e.printStackTrace();
            } finally {
                if (connection != null) {
                    connection.close();
                }
                rwl.readLock().unlock();
            }
        }
    
        public String getId() {
            return this.id;
        }
    
        /**
         * 获取缓存总数量
         */
        public int getSize() {
            int result = 0;
            JedisConnection connection = null;
            try {
                connection = jedisConnectionFactory.getConnection();
                result = Integer.valueOf(connection.dbSize().toString());
                logger.info("添加mybaits二级缓存数量:" + result);
            } catch (JedisConnectionException e) {
                e.printStackTrace();
            } finally {
                if (connection != null) {
                    connection.close();
                }
            }
            return result;
        }
    
        public void putObject(Object key, Object value) {
            rwl.writeLock().lock();
    
            JedisConnection connection = null;
            try {
                connection = jedisConnectionFactory.getConnection();
                RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
                connection.set(SerializeUtil.serialize(key), SerializeUtil.serialize(value));
                logger.info("添加mybaits二级缓存key=" + key + ",value=" + value);
            } catch (JedisConnectionException e) {
                e.printStackTrace();
            } finally {
                if (connection != null) {
                    connection.close();
                }
                rwl.writeLock().unlock();
            }
        }
    
        public Object getObject(Object key) {
            // 先从缓存中去取数据,先加上读锁
            rwl.readLock().lock();
            Object result = null;
            JedisConnection connection = null;
            try {
                connection = jedisConnectionFactory.getConnection();
                RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
                result = serializer.deserialize(connection.get(serializer.serialize(key)));
                logger.info("命中mybaits二级缓存,value=" + result);
    
            } catch (JedisConnectionException e) {
                e.printStackTrace();
            } finally {
                if (connection != null) {
                    connection.close();
                }
                rwl.readLock().unlock();
            }
            return result;
        }
    
        public Object removeObject(Object key) {
            rwl.writeLock().lock();
    
            JedisConnection connection = null;
            Object result = null;
            try {
                connection = jedisConnectionFactory.getConnection();
                RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
                result = connection.expire(serializer.serialize(key), 0);
            } catch (JedisConnectionException e) {
                e.printStackTrace();
            } finally {
                if (connection != null) {
                    connection.close();
                }
                rwl.writeLock().unlock();
            }
            return result;
        }
    
        public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
            RedisCache.jedisConnectionFactory = jedisConnectionFactory;
        }
    
        public ReadWriteLock getReadWriteLock() {
            // TODO Auto-generated method stub
            return rwl;
        }
    
    }

    RedisCacheTransfer.java

    package cn.qlq.jedis;
    
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
    
    /**
     * 静态注入中间类
     */
    public class RedisCacheTransfer {
         @Autowired
            public void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
                RedisCache.setJedisConnectionFactory(jedisConnectionFactory);
            }
    
    }

    SerializeUtil.java

    package cn.qlq.jedis;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    /**
     * 
     * @author qlq
     *
     */
    public class SerializeUtil {
        /**
         * 序列化
         */
        public static byte[] serialize(Object object) {
            ObjectOutputStream oos = null;
            ByteArrayOutputStream baos = null;
            try {
                // 序列化
                baos = new ByteArrayOutputStream();
                oos = new ObjectOutputStream(baos);
                oos.writeObject(object);
                byte[] bytes = baos.toByteArray();
                return bytes;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 反序列化
         */
        public static Object unserialize(byte[] bytes) {
            if (bytes != null) {
                ByteArrayInputStream bais = null;
                try {
                    // 反序列化
                    bais = new ByteArrayInputStream(bytes);
                    ObjectInputStream ois = new ObjectInputStream(bais);
                    return ois.readObject();
                } catch (Exception e) {
    
                }
            }
            return null;
        }
    }

      到此,修改就算完成,开始开启二级缓存

     

    3.开启二级缓存:

    • 需要缓存的对象实现序列化接口

    •  Mybatis的全局配置文件开启二级缓存(SqlMapConfig.xml)
    <?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>
            <setting name="cacheEnabled" value="true"/>
        </settings>
        
        
        <!--  只需要定义个别名,这个应该有-->
         <typeAliases >
             <package name="cn.qlq.bean"/>
         </typeAliases>       
    
    </configuration>
    • Mapper.xml中开启二级缓存并设置缓存类(UserMapper.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">
    
    <!-- namespace命名空间,作用就是对sql进行分类化管理,理解sql隔离 注意:使用mapper代理方法开发,namespace有特殊重要的作用 -->
    <mapper namespace="cn.qlq.mapper.UserMapper">
        <!-- 使用Redis二级缓存 -->
        <cache type="cn.qlq.jedis.RedisCache"></cache>
    
        <select id="findUserById" parameterType="int" resultType="cn.qlq.bean.User">
            select
            * from user where id = #{id}
        </select>
    
        <select id="findUsersByPage" resultType="cn.qlq.bean.User">
            select * from user
        </select>
    
        <insert id="addUser">
            insert into user values(#{0},#{1})
        </insert>
    
    
    </mapper>

     4.测试二级缓存:

    •  清空redis的数据
    127.0.0.1:6379> flushall
    OK
    127.0.0.1:6379> keys *
    (empty list or set)
    127.0.0.1:6379>
    • 启动项目访问并查看日志:

    日志如下:(如下底色是黄的是发出的SQL语句,字体是红色的是缓存的命中率为0之后向redis添加缓存)

    13:01:05,064 DEBUG DefaultActionInvocation:76 - Executing action method = findPage
    13:01:05,091 DEBUG SqlSessionUtils:109 - Creating a new SqlSession
    13:01:05,109 DEBUG SqlSessionUtils:145 - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@15546ea6] was not registered for synchronization because synchronization is not active
    13:01:05,157 DEBUG LoggingCache:55 - Cache Hit Ratio [SQL_CACHE]: 0.0
    13:01:05,350  INFO RedisCache:107 - 命中mybaits二级缓存,value=null
    13:01:05,364 DEBUG DataSourceUtils:110 - Fetching JDBC Connection from DataSource
    13:01:05,365 DEBUG BasicResourcePool:1644 - trace com.mchange.v2.resourcepool.BasicResourcePool@44083847 [managed: 3, unused: 2, excluded: 0] (e.g. com.mchange.v2.c3p0.impl.NewPooledConnection@29072429)
    13:01:05,366 DEBUG SpringManagedTransaction:88 - JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@75adf2b9] will not be managed by Spring
    13:01:05,371 DEBUG findUsersByPage_COUNT:132 - ooo Using Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@75adf2b9]
    13:01:05,385 DEBUG findUsersByPage_COUNT:132 - ==>  Preparing: SELECT count(0) FROM user 
    13:01:05,447 DEBUG findUsersByPage_COUNT:132 - ==> Parameters: 
    13:01:05,487 TRACE findUsersByPage_COUNT:138 - <==    Columns: count(0)
    13:01:05,487 TRACE findUsersByPage_COUNT:138 - <==        Row: 7
    13:01:05,499  INFO RedisCache:107 - 命中mybaits二级缓存,value=null
    13:01:05,500 DEBUG findUsersByPage:132 - ooo Using Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@75adf2b9]
    13:01:05,501 DEBUG findUsersByPage:132 - ==>  Preparing: SELECT * FROM user order by id desc LIMIT ?, ? 
    13:01:05,502 DEBUG findUsersByPage:132 - ==> Parameters: 2(Integer), 2(Integer)
    13:01:05,503 TRACE findUsersByPage:138 - <==    Columns: id, name
    13:01:05,504 TRACE findUsersByPage:138 - <==        Row: 4, QLQ4
    13:01:05,505 TRACE findUsersByPage:138 - <==        Row: 3, QLQ3
    13:01:05,511  INFO RedisCache:87 - 添加mybaits二级缓存key=-167006705:2220054459:cn.qlq.mapper.UserMapper.findUsersByPage_COUNT:0:2147483647:select * from user,value=[7]
    13:01:05,515  INFO RedisCache:87 - 添加mybaits二级缓存key=-165358097:5089576455:cn.qlq.mapper.UserMapper.findUsersByPage:0:2147483647:select * from user:2:2:id desc:2,value=[cn.qlq.bean.User@68e9fd61, cn.qlq.bean.User@39835e11]
    13:01:05,515 DEBUG SqlSessionUtils:173 - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@15546ea6]
    13:01:05,516 DEBUG DataSourceUtils:332 - Returning JDBC Connection to DataSource
    • 打开RedisDesktopManager查看缓存记录

    • 再次访问发现命中缓存,所以没有发出SQL语句:

     

     

      命中率计算方法:第一次访问没有命中为0,所以将缓存加进去。

              第二次命中缓存,命中率为1/2

              第三次命中缓存,命中率为2/3

     5.测试清除二级缓存:

      经过 测试发现当SQL语句写在注解上面(mybatis使用注解)的时候不会清除缓存,而写在XML中的语句执行后会清除缓存。

    •  1.注解insert,update,delete不会清除缓存
        // @Insert("insert into user values(#{0},#{1})")
        public int addUser(int id, String name) throws SQLException;
    • 2.Mapper.xml写的SQL语句执行之后会清除缓存
        <insert id="addUser">
            insert into user values(#{0},#{1})
        </insert>
    • 3.测试XML写的SQL执行之后清除缓存

    (1)get方式添加一条记录

     (2)查看日志:

    13:21:37,876 DEBUG addUser:132 - ==>  Preparing: insert into user values(?,?) 
    13:21:37,942 DEBUG addUser:132 - ==> Parameters: 8(Integer), QLQ8(String)
    13:21:38,087 DEBUG RedisCache:45 - 清除缓存.......

    (3)查看redis是否有数据:

    git源码地址:https://github.com/qiao-zhi/Maven_SSM

    接下来还会整合redis+Shiro。与Sessionsession共享。

  • 相关阅读:
    p(str or array) 传递数据以易于阅读的样式格式化后输出 bootstarp样式的打印函数
    [Err] 1067
    php 正则表达式
    Docker使用及dnmp构建
    记一次Ubuntu18.04升级到19.10的经历
    面试-Redis
    ubuntu截图软件deepin scrot
    docker 搭建 Hadoop
    Docker 遇到的坑
    RabbitMQ遇到的坑
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/8556130.html
Copyright © 2020-2023  润新知