欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ssm+reids缓存整合

程序员文章站 2022-06-20 21:01:55
在说正文之前我们先介绍一下redis: redis是当今比较热门的非关系型数据库之一,他使用的是key-value的键值对来进行存储,是一个存在于内存之中的数据库,我们一般用于做数据缓存。当我们需要大量的数据查询时,如果我们都直接访问数据库时,会严重影响数据库性能。所以我们一般的操作就是在db层之上 ......

在说正文之前我们先介绍一下redis:

  redis是当今比较热门的非关系型数据库之一,他使用的是key-value的键值对来进行存储,是一个存在于内存之中的数据库,我们一般用于做数据缓存。当我们需要大量的数据查询时,如果我们都直接访问数据库时,会严重影响数据库性能。所以我们一般的操作就是在db层之上的各级使用多级的no-sql来为db提供缓冲。

  因为redis是存在于内存之中,那么问题来了当我们断电时或者宕机时就会产生数据丢失,所以redis为我们提供了rdb和aof的两种持久化保存的方式,这也是为什么同样是缓存数据库我们选择redis而不选择memcache的原因。而且为了在大流量下提供稳定业务,redis还提供了redis-cluster,twemproxy,codis等集群化方案,为我们搭建分布式系统提供了可能。废话说了这么多下面开始正文。

一.添加redis对应的依赖

  <!-- reids缓存-->
    <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
    <dependency>
      <groupid>org.springframework.data</groupid>
      <artifactid>spring-data-redis</artifactid>
      <version>${redis.data.version}</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
    <dependency>
      <groupid>redis.clients</groupid>
      <artifactid>jedis</artifactid>
      <version>${redis.clients.version}</version>
    </dependency>

依赖版本大家可以下用的最多的比较稳定。

二.添加相应的spring配置

<?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"
       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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--开启aop代理-->
    <aop:aspectj-autoproxy expose-proxy="true"/>
    <!--spring添加注解扫描-->
    <context:annotation-config></context:annotation-config>
    <!--spring 注解扫描但是要排除spring mvc的控制器-->
    <context:component-scan base-package="com">
        <context:exclude-filter type="annotation"
                                expression="org.springframework.stereotype.controller"></context:exclude-filter>
    </context:component-scan>

    <!--加载资源文件,该标签全文只能有一个-->
    <context:property-placeholder location="classpath:jdbc.properties,classpath:redisconfig.properties"/>

    <!--配置数据源,阿里数据连接池-->
    <bean id="datasource" class="${jdbc.datatype}" destroy-method="close">
        <property name="driverclassname" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.usename}"/>
        <property name="password" value="${jdbc.password}"/>

        <!-- 数据库连接池配置 -->
        <property name="initialsize" value="20"/><!-- 初始化连接数量 -->
        <property name="minidle" value="5"/>   <!-- 最小空闲连接数量 -->
        <property name="maxactive" value="1500"/> <!-- 最大连接数量 -->
        <property name="maxwait" value="60000"/>  <!-- 最大建立连接等待时间(毫秒)。如果超过此时间将接到异常。设为-1表示无限制-->
        <property name="timebetweenevictionrunsmillis" value="60000"/>  <!--  配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒   -->
        <property name="minevictableidletimemillis" value="300000"/>   <!-- 配置一个连接在池中最小生存的时间,单位是毫秒   -->
        <property name="validationquery" value="select 'x'"/>
        <property name="testwhileidle" value="true"/>  <!--空闲时是否进行验证,检查对象是否有效 -->
        <property name="testonborrow" value="false"/>  <!--取得对象时是否进行验证,检查对象是否有效 -->
        <property name="testonreturn" value="false"/>  <!--返回对象时是否进行验证 -->
        <!--  打开pscache,并且指定每个连接上pscache的大小   -->
        <property name="poolpreparedstatements" value="true"/>  <!-- 表明是否开启statement cache,默认为false,也就是不开启 -->
        <property name="maxpoolpreparedstatementperconnectionsize"
                  value="20"/>  <!-- statement cache的大小,默认为-1,也就是不限制 -->

        <!-- 配置监控统计拦截的filters,去掉后监控界面sql无法统计   -->
        <property name="filters" value="stat"/>
    </bean>


    <bean id="sqlsessionfactory" class="org.mybatis.spring.sqlsessionfactorybean">
        <property name="configlocation" value="classpath:mybatis-config.xml"/>
        <property name="datasource" ref="datasource"/>
        <!-- 扫描entity包 使用别名 -->
        <property name="typealiasespackage" value="com.lwc.pojo"/>
        <!-- 扫描sql配置文件:mapper需要的xml文件 -->
        <property name="mapperlocations" value="classpath*:com/lwc/dao/mapper/*.xml"/>
    </bean>
    <!--根据接口名生成对应的代理类-->
    <bean class="org.mybatis.spring.mapper.mapperscannerconfigurer">
        <property name="basepackage" value="com.lwc.dao"/>
        <property name="sqlsessionfactorybeanname" value="sqlsessionfactory"/>
    </bean>


    <!-- 配置事务管理器 -->
    <bean id="transactionmanager"
          class="org.springframework.jdbc.datasource.datasourcetransactionmanager">
        <!-- 注入数据库连接池 -->
        <property name="datasource" ref="datasource"/>
    </bean>

    <!--配置事务切面-->
    <aop:config>
        <aop:advisor advice-ref="txadvice" pointcut="execution(* com.lwc.service.*.*(..))"></aop:advisor>
    </aop:config>
    <!-- 2 事务详情(事务通知)  , 在aop筛选基础上,比如对abc三个确定使用什么样的事务。例如:ac读写、b只读 等
       <tx:attributes> 用于配置事务详情(属性属性)
           <tx:method name=""/> 详情具体配置
               propagation 传播行为 , required:必须;requires_new:必须是新的
               isolation 隔离级别
   -->
    <tx:advice id="txadvice" transaction-manager="transactionmanager">
        <tx:attributes>
            <tx:method name="*" propagation="required"/>
        </tx:attributes>
    </tx:advice>
    <!--配置redis缓存-->
    <!--redis配置-->
    <bean id="jedispoolconfig" class="redis.clients.jedis.jedispoolconfig">

        <!--控制一个pool最多有多少个状态为空闲的jedis实例-->
        <property name="maxidle" value="${redis.maxidle}"></property>
        <!--当borrow(引入)一个实例时,最大的等待时间,如果超过则抛出jedisconnectionexception-->
        <property name="maxwaitmillis" value="${redis.maxwaitmillis}"></property>
        <property name="maxtotal" value="${redis.maxtotal}"></property>
        <!-- 在在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的-->
        <property name="testonborrow" value="${redis.testonborrow}"></property>

    </bean>

    <!--redis连接池-->
    <bean id="jedispool" class="redis.clients.jedis.jedispool">
        <constructor-arg index="0" ref="jedispoolconfig"></constructor-arg>
        <constructor-arg index="1" value="${redis.host}"></constructor-arg>
        <constructor-arg index="2" value="${redis.port}"></constructor-arg>
        <constructor-arg index="3" value="${redis.timeout}"></constructor-arg>

    </bean>
    <!-- redis连接工厂 -->
    <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="poolconfig" ref="jedispoolconfig"></property>
    </bean>
    <!-- redis操作模板,这里采用尽量面向对象的模板 -->
    <bean id="redistemplate" class="org.springframework.data.redis.core.redistemplate">
        <property name="connectionfactory" ref="jedisconnectionfactory"/>
        <property name="keyserializer" ref="stringreadisserializer"/>
        <property name="valueserializer" ref="stringreadisserializer"/>
        <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>


    <!--使用字符串进行序列化-->
    <bean id="stringreadisserializer" class="org.springframework.data.redis.serializer.stringredisserializer"/>
    <!--使用jdk的序列化器进行转化-->
    <bean id="jdkserializationredisserializer" class="org.springframework.data.redis.serializer.jdkserializationredisserializer"/>

    <bean id="getcache" class="com.lwc.redis.aspectj.getcachaop">
        <property name="redistemplate" ref="redistemplate"/>
    </bean>
</beans>

这里是spring的基本配置,这里我没有分开来,反正都有注释

三.redis的一些基本资源文件

redis.host=localhost
redis.port=6379
redis.maxidle=50
redis.maxtotal=100
redis.maxwaitmillis=3000
redis.testonborrow=true
redis.timeout=5000

这里我没有配置密码,所以就没有写pass,但是如果有需要的话可以找到redis的配置文件加上如下语句

ssm+reids缓存整合

这样就可以了,如果没有配置密码而配置了<property name="password" value="${redis.password}" />将会报错

四.利用aop和自定义注解来进行实现环绕

  下面是自定义注解,并且配上了一些注解的解释,还没有学过自定注解的小伙伴可以看看

package com.lwc.redis.annotation;

import java.lang.annotation.*;

/**自定义注解
 *   2.1.1 target:表示注解的作用目标 
 *
 *                @target(elementtype.type)   //接口、类、枚举、注解
 *
 *     @target(elementtype.field) //字段、枚举的常量
 *
 *     @target(elementtype.method) //方法
 *
 *     @target(elementtype.parameter) //方法参数
 *
 *     @target(elementtype.constructor)  //构造函数
 *
 *     @target(elementtype.local_variable)//局部变量
 *
 *     @target(elementtype.annotation_type)//注解
 *
 *     @target(elementtype.package) ///包
 *
 *   2.1.2 @documented:说明该注解将被包含在javadoc中;
 *
 *   2.1.3 @inherited:说明子类可以继承父类中的该注解 ;
 *
 *   2.1.4 @retention:注解的保留位置;
 *
 *                @retention(retentionpolicy.source)   //注解仅存在于源码中,在class字节码文件中不包含
 *
 *        @retention(retentionpolicy.class)     // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
 *
 *     @retention(retentionpolicy.runtime)  // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
 */
@target(elementtype.method)//目标为方法
@retention(retentionpolicy.runtime)//注解在类中存在,运行时通过反射获取
@documented
@inherited
public @interface getcache {
    string name() default "";
    string value() default "";
}

定义这个注解是为了让aop可以直接根据注解来进行切面环绕,而不需要根据传统的方法来进行切点,这样会方便很多,否则的话就需要

定义接口然后对接口需要的方法进行定义切点,在实现该接口,这样也可以做到切面环绕,但是会更麻烦点,有兴趣的小伙伴可以自己试试

 通知类:

package com.lwc.redis.aspectj;

import com.lwc.redis.annotation.getcache;
import com.lwc.util.rediscache;
import org.aspectj.lang.proceedingjoinpoint;
import org.aspectj.lang.annotation.around;
import org.aspectj.lang.annotation.aspect;
import org.aspectj.lang.annotation.pointcut;
import org.aspectj.lang.reflect.methodsignature;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.stereotype.component;

import java.io.serializable;
import java.lang.reflect.method;

@component
@aspect
public class getcachaop {
    @autowired
    private redistemplate<serializable,object> redistemplate;
   private rediscache rediscache=new rediscache();
   //定义切点
   @pointcut("@annotation(com.lwc.redis.annotation.getcache)")
    public void getcache(){
       system.out.println("获取内存数据切入点");
   }
   /*在所有标注了getcache的地方切入*/
   @around("getcache()")
    public object beforeexec(proceedingjoinpoint joinpoint){
       //生成redis中的id,根据自己指定的格式
       string rediskey=getcachekey(joinpoint);

       //获取从redis中查询得到的对象
       object object=rediscache.getdatafromredis(rediskey);

       //如果查询到了
       if(null!=object){
           system.out.println("从redis中获取到了数据");
           return object;
       }else{
           system.out.println("从数据库中查询数据");
           //如果没有查询到,则在数据库中进行查询
           try {
               object=joinpoint.proceed();
           } catch (throwable throwable) {
               throwable.printstacktrace();
           }
       }

       //在目标方法执行完之后:将查到的数据放入到redis中
       rediscache.setdatatoredis(rediskey,object);
       return object;
   }

    /**
     * 根据方法名参数名类名获取唯一的一个键值
     * 格式为yourpackage.classname.methodname(int).argsvalue123
     * @param joinpoint
     * @return
     */
    //变量没有用到时不让警告
   @suppresswarnings("unused")
   private string getcachekey(proceedingjoinpoint joinpoint){
       //获取切入方法的一些相关的信息
       methodsignature ms=(methodsignature) joinpoint.getsignature();

       method method=ms.getmethod();
       //获取注解中设置的对应参数
       string actionname=method.getannotation(getcache.class).value();
       string fieldlist=method.getannotation(getcache.class).name();
       for(string field:fieldlist.split("."))
           actionname+="."+field;

       //获取切点方法的参数
       string id=null;
       object[] args=joinpoint.getargs();
       if(args!=null && args.length>0)
           id=string.valueof(args[0]);
       actionname+="="+id;
       string rediskey=ms+"."+actionname;
       return rediskey;
   }

   public void setredistemplate( redistemplate<serializable, object> redistemplate){
       this.redistemplate = redistemplate;
   }
}

上面代码都有详细注释这里就不重复了

下面是序列化的工具类:

package com.lwc.util;

import java.io.*;

public class serializableutil {

    //将字节数组反序列化为对象
    public static  object toobject(byte[] bytes){
        object obj=null;
        try{
            bytearrayinputstream bis=new bytearrayinputstream(bytes);
            objectinputstream ois=new objectinputstream(bis);
            obj=ois.read();
            ois.close();
            bis.close();
        } catch (ioexception e) {
            e.printstacktrace();
        }
        return obj;
    }
    //将对象序列化为字节数组
    public static byte[] tobytearray(object obj){
        byte[] bytes =null;
        bytearrayoutputstream bos =new bytearrayoutputstream();
        try{
            objectoutputstream oos=new objectoutputstream(bos);
            oos.writeobject(obj);
            oos.flush();
            bytes=bos.tobytearray();
            oos.close();
            bos.close();
        } catch (ioexception e) {
            e.printstacktrace();
        }
        return bytes;
    }
}

下面是缓存工具类:

package com.lwc.util;

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.context.support.classpathxmlapplicationcontext;
import org.springframework.stereotype.component;
import redis.clients.jedis.jedis;
import redis.clients.jedis.jedispool;
/**
 * redis缓存工具类
 */

public class rediscache  {

    private static jedispool jedispool;
    //静态类进行参数的初始化
    static {
         classpathxmlapplicationcontext cxac=new classpathxmlapplicationcontext("applicationcontext.xml");
        jedispool=(jedispool) cxac.getbean("jedispool");
    }
    //从缓存中读取数据,进行反序列化
    public object getdatafromredis(string rediskey){
        jedis jedis=jedispool.getresource();
        byte[] result=jedis.get(rediskey.getbytes());
       //如果没有查询到,就返回空
        if(null==result)
            return null;
        return serializableutil.toobject(result);
    }

    //将数据库中查到的数据放入redis中
    public void setdatatoredis(string rediskey,object obj){
        byte[] bytes =serializableutil.tobytearray(obj);
        jedis jedis=jedispool.getresource();
        string sucess=jedis.set(rediskey.getbytes(),bytes);
        if("ok".equals(sucess)){
            system.out.println("数据保存成功");
        }
    }
}

当然我也看到有人继承cach类来使用redis缓存,下面贴出别人的代码并进行解释:

package com.ssm.redis;
 
import java.util.concurrent.locks.readwritelock;
import java.util.concurrent.locks.reentrantreadwritelock;
 
import org.apache.ibatis.cache.cache;
import org.springframework.beans.factory.annotation.autowired;
 
import redis.clients.jedis.jedis;
import redis.clients.jedis.jedispool;
import redis.clients.jedis.jedispoolconfig;
 
public class rediscache implements cache {
 
    private final readwritelock readwritelock = new reentrantreadwritelock();
    /**
     * jedis客户端
     */
 
    @autowired
    private jedis redisclient = createclient();
 
    private string id;
 
    public rediscache(final string id) {
        if (id == null) {
            throw new illegalargumentexception("必须传入id");
        }
        system.out.println("mybatisrediscache:id=" + id);
        this.id = id;
    }
 
    @override
    public void clear() {
        redisclient.flushdb();
    }
 
    @override
    public string getid() {
        return this.id;
    }
 
    @override
    public object getobject(object key) {
        byte[] ob = redisclient.get(serializeutil.serialize(key.tostring()));
        if (ob == null) {
            return null;
        }
        object value = serializeutil.unserialize(ob);
        return value;
    }
 
    @override
    public readwritelock getreadwritelock() {
        return readwritelock;
    }
 
    @override
    public int getsize() {
        return integer.valueof(redisclient.dbsize().tostring());
    }
 
    @override
    public void putobject(object key, object value) {
        redisclient.set(serializeutil.serialize(key.tostring()), serializeutil.serialize(value));
    }
 
    @override
    public object removeobject(object key) {
        return redisclient.expire(serializeutil.serialize(key.tostring()), 0);
    }
 
    protected static jedis createclient() {
 
        try {
            @suppresswarnings("resource")
            jedispool pool = new jedispool(new jedispoolconfig(), "127.0.0.1", 6379);
            return pool.getresource();
        } catch (exception e) {
            e.printstacktrace();
        }
        throw new runtimeexception("初始化连接池错误");
    }
 
}

这个其实是使用mybatis自带的二级缓存,从写mybatis中的缓存类来进行实现使用这种方法就不需要额外的添加aop之类的只需要在映射的dao文件中添加<cache type="packagename.rediscache" />这样便可以直接使用redis而不需要aop配置

言归正传,我们接着使用aop来实现redis整合,下面是service使用注解来进行实现redis存取

package com.lwc.service;

import com.lwc.dao.userdao;
import com.lwc.pojo.user;
import com.lwc.redis.annotation.getcache;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;

@service
public class userservice {
    @autowired
    private userdao userdao;
    @getcache(name = "user" ,value = "id")
    public user getuser(integer id){
        return userdao.selectuser(id);
    }
}

这样我们每次调用service的这个方法就可以实现从redis存取数据了。其他的userdao和对应的映射文件我这里就不贴出来了

值得一说的是这里的实体类要继承serializable 接口,不然会报错,因为他时间实例化对象进行序列化存入内存之中。

以上就完成了基本的redis的使用,下篇博文将会介绍redis的持久化