SpringMVC+Mybatis framework implements Redis secondary cache

1 Introduction

  When using mybatis, the second level cache can be used to improve the query speed, thereby improving the user experience.

  Using redis as the second level cache of mybatis can control the memory <for example, deploying a separate server for the second level cache>, which is easy to manage.

2. Use ideas

  2.1 Configure redis.xml to set the parameters of redis service connection

  2.1 Use the <setting> tag in the configuration file to enable the second level cache;

  2.2 Use <cache type="com.demo.RedisCacheClass" /> in mapper.xml to map the cache to the specified RedisCacheClass class;

  2.3 The mapping class RedisCacheClass implements the Cache class in the MyBatis package and rewrites its methods;

    In rewriting each method body, use redisFactory to establish a connection with the redis service, load the cached data into the specified redis memory (putObject method) or read the data in the redis service from the cache (getObject method);

    When writing and loading data in the redis service, you need to borrow the serialization (serialize) and deserialization methods (deserialize) in JdkSerializationRedisSerializer.class in spring-data-redis.jar, which is the default serialization of redis encapsulated in the package method;

  2.4 After each method in the mapping class is rewritten, the secondary cache of mybatis data can be implemented into the redis service;

3. Code Practice

  3.1 Configure 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:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    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:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-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.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/task
    http://www.springframework.org/schema/task/spring-task-3.0.xsd" >
    <!-- enable autowire -->
    <context:annotation-config /> 
       
    <task:annotation-driven/>
    
    <context:component-scan base-package="demo.util,demo.salesorder,demo.person" />
    <!-- Configures the @Controller programming model 必须加上这个,不然请求controller时会出现no mapping url错误-->
    <mvc:annotation-driven />
    <!-- 引入数据库配置文件 -->
    <bean id="propertyConfigurer"    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:sysconfig/jdbc.properties</value>
                <value>classpath:sysconfig/redis.properties</value>
            </list>
        </property>
    </bean>
    <!-- JDBC -->
    <bean id="defaultDataSource"   class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.databaseurl}"
        p:username="${jdbc.username}"
        p:password="${jdbc.password}" >
        <property name="maxActive">
            <value>${jdbc.maxActive}</value>
        </property>  
        <property name="initialSize">
            <value>${jdbc.initialSize}</value>
        </property>  
        <property name="maxWait">
            <value>${jdbc.maxWait}</value>
        </property>  
        <property name="maxIdle">
            <value>${jdbc.maxIdle}</value>
        </property>
        <property name="minIdle">
            <value>${jdbc.minIdle}</value>
        </property>
        <!-- 只要下面两个参数设置成小于8小时(MySql默认),就能避免MySql的8小时自动断开连接问题 -->
        <property name="timeBetweenEvictionRunsMillis">
            <value>18000000</value>
        </property><!-- 5小时 -->
        <property name="minEvictableIdleTimeMillis">
            <value>10800000</value>
        </property><!-- 3小时 -->
        <property name="validationQuery">
            <value>SELECT 1</value>
        </property>
        <property name="testOnBorrow">
            <value>true</value>
        </property>
    </bean>
    
    <!-- define the SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="defaultDataSource" />
        <property name="typeAliasesPackage" value="demo.salesorder,demo.person" />
        <!-- 可以单独指定mybatis的配置文件,或者写在本文件里面。 用下面的自动扫描装配(推荐)或者单独mapper --> 
        <property name="configLocation" value="classpath:sysconfig/mybatis-config.xml" />
    </bean>
    
    
    <!-- 自动扫描并组装MyBatis的映射文件和接口-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="demo.*.data" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>
    
    <!-- JDBC END -->
    
    <!-- redis数据源 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">  
        <property name="maxIdle" value="${redis.maxIdle}" />  
        <property name="maxTotal" value="${redis.maxActive}" />  
        <property name="maxWaitMillis" value="${redis.maxWait}" />  
        <property name="testOnBorrow" value="${redis.testOnBorrow}" />  
    </bean>
    
    <!-- Spring-redis连接池管理工厂 -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}" />
        <property name="port" value="${redis.port}" />
        <property name="password" value="${redis.pass}" />
        <property name="timeout" value="${redis.timeout}" />
        <property name="poolConfig" ref="poolConfig" />
    </bean>      
    <!-- 使用中间类解决RedisCache.jedisConnectionFactory的静态注入,从而使MyBatis实现第三方缓存 -->
    <bean id="redisCacheTransfer" class="demo.redis.RedisCacheTransfer">
        <property name="jedisConnectionFactory" ref="jedisConnectionFactory"/>
    </bean>              
</beans>

3.2 mybatis.xml configuration to enable secondary cache

<?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>
    <!-- 配置mybatis的缓存,延迟加载等等一系列属性 -->
    <settings>

        <!-- 全局映射器启用缓存 *主要将此属性设置完成即可-->
        <setting name="cacheEnabled" value="true"/>

        <!-- 查询时,关闭关联对象即时加载以提高性能 -->
        <setting name="lazyLoadingEnabled" value="false"/>

        <!-- 对于未知的SQL查询,允许返回不同的结果集以达到通用的效果 -->
        <setting name="multipleResultSetsEnabled" value="true"/>

        <!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指 定),不会加载关联表的所有字段,以提高性能 -->
        <setting name="aggressiveLazyLoading" value="true"/>

    </settings>
</configuration>

3.3 Map the cache class RedisCacheClass in mapper.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="demo.person.data.UserMapper">
<cache type="demo.redis.cache.RedisCache"/> <!-- *映射语句 -->

<select id="getPersonList" parameterType="map" resultType="Person">
    select *    
     from person
    <where>
        1=1
        <if test="user_name!=null">
            and user_name=#{user_name}
        </if>    
    </where>
</select>
<insert id="addPerson" parameterType="Person" keyProperty="id" useGeneratedKeys="true">
    insert into person(
        login_id,
        user_name,
        gender,
        birthday,
        remark
    )values(
        #{login_id},
        #{user_name},
        #{gender},
        #{birthday},
        #{remark}
    )
</insert>

</mapper>

3.4 Implement the Cache interface in Mybatis

    Cache.class source code:

/*
 *    Copyright 2009-2012 the original author or authors.
 *    http://www.apache.org/licenses/LICENSE-2.0*/
package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache {

  String getId();

  int getSize();

  void putObject(Object key, Object value);

  Object getObject(Object key);

  Object removeObject(Object key);

  void clear();

  ReadWriteLock getReadWriteLock();

}

RedisCache.java

package demo.redis.cache;

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;

    /**
     * The {@code ReadWriteLock}.
     */
    private final ReadWriteLock readWriteLock = 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;
    }

    @Override
    public void clear()
    {
        JedisConnection connection = null;
        try
        {
            connection = jedisConnectionFactory.getConnection(); //连接清除数据
            connection.flushDb();
            connection.flushAll();
        }
        catch (JedisConnectionException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (connection != null) {
                connection.close();
            }
        }
    }

    @Override
    public String getId()
    {
        return this.id;
    }

    @Override
    public Object getObject(Object key)
    {
        Object result = null;
        JedisConnection connection = null;
        try
        {
            connection = jedisConnectionFactory.getConnection();
            RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer(); //借用spring_data_redis.jar中的JdkSerializationRedisSerializer.class
            result = serializer.deserialize(connection.get(serializer.serialize(key))); //利用其反序列化方法获取值
        }
        catch (JedisConnectionException 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;
        try
        {
            connection = jedisConnectionFactory.getConnection();
            result = Integer.valueOf(connection.dbSize().toString());
        }
        catch (JedisConnectionException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (connection != null) {
                connection.close();
            }
        }
        return result;
    }

    @Override
    public void putObject(Object key, Object value)
    {
        JedisConnection connection = null;
        try
        {
            logger.info(">>>>>>>>>>>>>>>>>>>>>>>>putObject:"+key+"="+value);
            connection = jedisConnectionFactory.getConnection();
            RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer(); //借用spring_data_redis.jar中的JdkSerializationRedisSerializer.class
            connection.set(serializer.serialize(key), serializer.serialize(value)); //利用其序列化方法将数据写入redis服务的缓存中
            
        }
        catch (JedisConnectionException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (connection != null) {
                connection.close();
            }
        }
    }

    @Override
    public Object removeObject(Object key)
    {
        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();
            }
        }
        return result;
    }

    public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
        RedisCache.jedisConnectionFactory = jedisConnectionFactory;
    }

}

RedisCacheTransfer.java

public class RedisCacheTransfer{
    @Autowired
    public void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory){
       RedisCache.setJedisConnectionFactory(jedisConnectionFactory);
    }
}

4. Implement the second level cache in the interface Dao

@CacheNamespace(implementation=(com.hcq.dao.mybatis.cache.RedisCache.class))

5. Summary

    By overriding the method in the Cache class, the default cache space in mybatis is mapped to the redis space

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326110949&siteId=291194637