A. Spring如何配置数据库查询缓存/对象缓存EHCache
在ehcache.xml文件中配置查询缓存参数,ehcache.xml文件配置如下:
<ehcache>
<!--diskStore元素,配置一个目录,这个目录用来存放数据,
也就是说,如果EhCache需要把数据写入磁盘,将会写到这个目录下-->
<diskStorepath="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskPersistent="false"
="120"/>
<cachename="ehcacheName"
maxElementsInMemory="3000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="36000"
overflowToDisk="true"
/>
</ehcache>
2. spring的配置
第一步:给指定方法配置缓存/src/main/resources/applicationContext-resources.xml
<ehcache:proxyid="userGroupServiceProxy"refId="userGroupService">
<ehcache:cachingcacheName="cash15Min"methodName=""/>
<ehcache:cachingcacheName="cash15Min"methodName=""/>
<ehcache:cachingcacheName="cash15Min"methodName="selectuserGroupById"/>
</ehcache:proxy>
配置参数的含义如下:
id:唯一标识符
refId:需要配置缓存的service或者controller
cacheName:缓存名称
methodName:需要缓存的方法,这个方法必须是shoppingHomeService中的方法
第二步:在控制器中注入依赖的缓存userGroupServiceProxy /src/main/webapp/WEB-INF/dispatcher-servlet.xml
<beanid="PFController"class="com.java.mall.controller.PFController">
<propertyname="userService"ref="userService"></property>
<propertyname="userGroupService"ref="userGroupServiceProxy"></property>
</bean>
同时需要在实体类中注入依赖,提供setter方法,
;
publicvoidsetuserGroupService(){
this.userGroupService=userGroupService;
}
B. spring cache 使用什么框架
从3.1开始,Spring引入了对Cache的支持。其使用方法和原理都类似于Spring对事务管理的支持。Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。
使用Spring Cache需要我们做两方面的事:
声明某些方法使用缓存
配置Spring对Cache的支持
和Spring对事务管理的支持一样,Spring对Cache的支持也有基于注解和基于XML配置两种方式。下面我们先来看看基于注解的方式。
1基于注解的支持
Spring为我们提供了几个注解来支持Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable标记的方法在执行后Spring Cache将缓存其返回结果,而使用@CacheEvict标记的方法会在方法执行前或者执行后移除Spring Cache中的某些元素。下面我们将来详细介绍一下Spring基于注解对Cache的支持所提供的几个注解。
1.1@Cacheable
@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略,这个稍后会进行说明。需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。@Cacheable可以指定三个属性,value、key和condition。
1.1.1value属性指定Cache名称
value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
@Cacheable("cache1")//Cache是发生在cache1上的
publicUserfind(Integerid){
returnnull;
}
@Cacheable({"cache1","cache2"})//Cache是发生在cache1和cache2上的
publicUserfind(Integerid){
returnnull;
}
1.1.2使用key属性自定义key
key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key。我们这里先来看看自定义策略,至于默认策略会在后文单独介绍。
自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。下面是几个使用参数作为key的示例。
@Cacheable(value="users",key="#id")
publicUserfind(Integerid){
returnnull;
}
@Cacheable(value="users",key="#p0")
publicUserfind(Integerid){
returnnull;
}
@Cacheable(value="users",key="#user.id")
publicUserfind(Useruser){
returnnull;
}
@Cacheable(value="users",key="#p0.id")
publicUserfind(Useruser){
returnnull;
}
除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。
<!--Ehcache实现-->
<beanid="cacheManager"class="org.springframework.cache.ehcache.EhCacheCacheManager"p:cache-manager-ref="ehcacheManager"/>
<beanid="ehcacheManager"class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"p:config-location="ehcache-spring.xml"/>
上面的配置使用了一个Spring提供的EhCacheCacheManager来生成一个Spring的CacheManager,其接收一个Ehcache的CacheManager,因为真正用来存入缓存数据的还是Ehcache。Ehcache的CacheManager是通过Spring提供的EhCacheManagerFactoryBean来生成的,其可以通过指定ehcache的配置文件位置来生成一个Ehcache的CacheManager。若未指定则将按照Ehcache的默认规则取classpath根路径下的ehcache.xml文件,若该文件也不存在,则获取Ehcache对应jar包中的ehcache-failsafe.xml文件作为配置文件。更多关于Ehcache的内容这里就不多说了,它不属于本文讨论的内容,欲了解更多关于Ehcache的内容可以参考我之前发布的Ehcache系列文章,也可以参考官方文档等。
C. 关于spring boot 缓存application.yml文件的配置
这些方式优先级如下:命令行参数来自java:comp/env的JNDI属性Java系统属性(System.getProperties())操作系统环境变量RandomValuePropertySource配置的random.*属性值jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件jar包外部的application.properties或application.yml(不带spring.profile)配置文件jar包内部的application.properties或application.yml(不带spring.profile)配置文件@Configuration注解类上的@PropertySource
D. 如何在spring中使用缓存
在Spring缓存机制中,包括了两个方面的缓存操作:1.缓存某个方法返回的结果;2.在某个方法执行前或后清空缓存。
Spring仅仅是提供了对缓存的支持,但它并没有任何的缓存功能的实现,spring使用的是第三方的缓存框架来实现缓存的功能。其中,spring对EHCache提供了很好的支持。
参考博客:http://www.cnblogs.com/fysola/p/6378400.html
E. 图文并茂,揭秘 Spring 的 Bean 的加载过程
目录
Spring 作为 Ioc 框架,实现了依赖注入,由一个中心化的 Bean 工厂来负责各个 Bean 的实例化和依赖管理。各个 Bean 可以不需要关心各自的复杂的创建过程,达到了很好的解耦效果。
我们对 Spring 的工作流进行一个粗略的概括,主要为两大环节:
我们假设所有的配置和扩展类都已经装载到了 ApplicationContext 中,然后具体的分析一下 Bean 的加载流程。
思考一个问题,抛开 Spring 框架的实现,假设我们手头上已经有一套完整的 Bean Definition Map,然后指定一个 beanName 要进行实例化,需要关心什么?即使我们没有 Spring 框架,也需要了解这两方面的知识:
Spring 进行了抽象和封装,使得作用域和依赖关系的配置对开发者透明,我们只需要知道当初在配置里已经明确指定了它的生命周期和依赖了谁,至于是怎么实现的,依赖如何注入,托付给了 Spring 工厂来管理。
Spring 只暴露了很简单的接口给调用者,比如 getBean :
那我们就从 getBean 方法作为入口,去理解 Spring 加载的流程是怎样的,以及内部对创建信息、作用域、依赖关系等等的处理细节。
上面是跟踪了 getBean 的调用链创建的流程图,为了能够很好地理解 Bean 加载流程,省略一些异常、日志和分支处理和一些特殊条件的判断。
从上面的流程图中,可以看到一个 Bean 加载会经历这么几个阶段(用绿色标记):
整个流程最为复杂的是对循环依赖的解决方案,后续会进行重点分析。
而在我们解析完配置后创建的 Map,使用的是 beanName 作为 key。见 DefaultListableBeanFactory:
BeanFactory.getBean 中传入的 name,有可能是这几种情况:
为了能够获取到正确的 BeanDefinition,需要先对 name 做一个转换,得到 beanName。
见 AbstractBeanFactory.doGetBean :
如果是 alias name ,在解析阶段,alias name 和 bean name 的映射关系被注册到 SimpleAliasRegistry 中。从该注册器中取到 beanName。见 SimpleAliasRegistry.canonicalName :
如果是 factorybean name ,表示这是个工厂 bean,有携带前缀修饰符 & 的,直接把前缀去掉。见 BeanFactoryUtils.transformedBeanName :
我们从配置文件读取到的 BeanDefinition 是 GenericBeanDefinition 。它的记录了一些当前类声明的属性或构造参数,但是对于父类只用了一个 parentName 来记录。
接下来会发现一个问题,在后续实例化 Bean 的时候,使用的 BeanDefinition 是 RootBeanDefinition 类型而非 GenericBeanDefinition 。这是为什么?
答案很明显,GenericBeanDefinition 在有继承关系的情况下,定义的信息不足:
为了能够正确初始化对象,需要完整的信息才行 。需要递归 合并父类的定义 :
见 AbstractBeanFactory.doGetBean :
在判断 parentName 存在的情况下,说明存在父类定义,启动合并。如果父类还有父类怎么办?递归调用,继续合并。
见 AbstractBeanFactory.getMergedBeanDefinition 方法:
每次合并完父类定义后,都会调用 RootBeanDefinition.overrideFrom 对父类的定义进行覆盖,获取到当前类能够正确实例化的 全量信息 。
什么是循环依赖?
举个例子,这里有三个类 A、B、C,然后 A 关联 B,B 关联 C,C 又关联 A,这就形成了一个循环依赖。如果是方法调用是不算循环依赖的,循环依赖必须要持有引用。
循环依赖根据注入的时机分成两种类型:
如果是构造器循环依赖,本质上是无法解决的 。比如我们准调用 A 的构造器,发现依赖 B,于是去调用 B 的构造器进行实例化,发现又依赖 C,于是调用 C 的构造器去初始化,结果依赖 A,整个形成一个死结,导致 A 无法创建。
如果是设值循环依赖,Spring 框架只支持单例下的设值循环依赖 。Spring 通过对还在创建过程中的单例,缓存并提前暴露该单例,使得其他实例可以引用该依赖。
Spring 不支持原型模式的任何循环依赖 。检测到循环依赖会直接抛出 异常。
使用了一个 ThreadLocal 变量 prototypesCurrentlyInCreation 来记录当前线程正在创建中的 Bean 对象,见 AbtractBeanFactory#prototypesCurrentlyInCreation :
在 Bean 创建前进行记录,在 Bean 创建后删除记录。见 AbstractBeanFactory.doGetBean :
见 AbtractBeanFactory.beforePrototypeCreation 的记录操作:
见 AbtractBeanFactory.beforePrototypeCreation 的删除操作:
为了节省内存空间,在单个元素时 prototypesCurrentlyInCreation 只记录 String 对象,在多个依赖元素后改用 Set 集合。这里是 Spring 使用的一个节约内存的小技巧。
了解了记录的写入和删除过程好了,再来看看读取以及判断循环的方式。这里要分两种情况讨论。
这两个地方的实现略有不同。
如果是构造函数依赖的,比如 A 的构造函数依赖了 B,会有这样的情况。实例化 A 的阶段中,匹配到要使用的构造函数,发现构造函数有参数 B,会使用 BeanDefinitionValueResolver 来检索 B 的实例。见 BeanDefinitionValueResolver.resolveReference :
我们发现这里继续调用 beanFactory.getBean 去加载 B。
如果是设值循环依赖的的,比如我们这里不提供构造函数,并且使用了 @Autowire 的方式注解依赖(还有其他方式不举例了):
加载过程中,找到无参数构造函数,不需要检索构造参数的引用,实例化成功。接着执行下去,进入到属性填充阶段 AbtractBeanFactory.populateBean ,在这里会进行 B 的依赖注入。
为了能够获取到 B 的实例化后的引用,最终会通过检索类 DependencyDescriptor 中去把依赖读取出来,见 DependencyDescriptor.resolveCandidate :
发现 beanFactory.getBean 方法又被调用到了。
在这里,两种循环依赖达成了同一 。无论是构造函数的循环依赖还是设置循环依赖,在需要注入依赖的对象时,会继续调用 beanFactory.getBean 去加载对象,形成一个递归操作。
而每次调用 beanFactory.getBean 进行实例化前后,都使用了 prototypesCurrentlyInCreation 这个变量做记录。按照这里的思路走,整体效果等同于 建立依赖对象的构造链 。
prototypesCurrentlyInCreation 中的值的变化如下:
调用判定的地方在 AbstractBeanFactory.doGetBean 中,所有对象的实例化均会从这里启动。
判定的实现方法为 AbstractBeanFactory. :
所以在原型模式下,构造函数循环依赖和设值循环依赖,本质上使用同一种方式检测出来。Spring 无法解决,直接抛出 异常。
Spring 也不支持单例模式的构造循环依赖 。检测到构造循环依赖也会抛出 异常。
和原型模式相似,单例模式也用了一个数据结构来记录正在创建中的 beanName。见 DefaultSingletonBeanRegistry :
会在创建前进行记录,创建化后删除记录。
见 DefaultSingletonBeanRegistry.getSingleton
记录和判定的方式见 DefaultSingletonBeanRegistry.beforeSingletonCreation :
这里会尝试往 singletonsCurrentlyInCreation 记录当前实例化的 bean。我们知道 singletonsCurrentlyInCreation 的数据结构是 Set,是不允许重复元素的, 所以一旦前面记录了,这里的 add 操作将会返回失败 。
比如加载 A 的单例,和原型模式类似,单例模式也会调用匹配到要使用的构造函数,发现构造函数有参数 B,然后使用 BeanDefinitionValueResolver 来检索 B 的实例,根据上面的分析,继续调用 beanFactory.getBean 方法。
所以拿 A,B,C 的例子来举例 singletonsCurrentlyInCreation 的变化,这里可以看到和原型模式的循环依赖判断方式的算法是一样:
单例模式下,构造函数的循环依赖无法解决,但设值循环依赖是可以解决的 。
这里有一个重要的设计: 提前暴露创建中的单例 。
我们理解一下为什么要这么做。
还是拿上面的 A、B、C 的的设值依赖做分析,
=> 1. A 创建 -> A 构造完成,开始注入属性,发现依赖 B,启动 B 的实例化
=> 2. B 创建 -> B 构造完成,开始注入属性,发现依赖 C,启动 C 的实例化
=> 3. C 创建 -> C 构造完成,开始注入属性,发现依赖 A
重点来了,在我们的阶段 1中, A 已经构造完成,Bean 对象在堆中也分配好内存了,即使后续往 A 中填充属性(比如填充依赖的 B 对象),也不会修改到 A 的引用地址。
所以,这个时候是否可以 提前拿 A 实例的引用来先注入到 C ,去完成 C 的实例化,于是流程变成这样。
=> 3. C 创建 -> C 构造完成,开始注入依赖,发现依赖 A,发现 A 已经构造完成,直接引用,完成 C 的实例化。
=> 4. C 完成实例化后,B 注入 C 也完成实例化,A 注入 B 也完成实例化。
这就是 Spring 解决单例模式设值循环依赖应用的技巧。流程图为:
为了能够实现单例的提前暴露。Spring 使用了三级缓存,见 DefaultSingletonBeanRegistry :
这三个缓存的区别如下:
从 getBean("a") 开始,添加的 SingletonFactory 具体实现如下:
可以看到如果使用该 SingletonFactory 获取实例,使用的是 getEarlyBeanReference 方法,返回一个未初始化的引用。
读取缓存的地方见 DefaultSingletonBeanRegistry :
先尝试从 singletonObjects 和 singletonFactory 读取,没有数据,然后尝试 singletonFactories 读取 singletonFactory,执行 getEarlyBeanReference 获取到引用后,存储到 earlySingletonObjects 中。
这个 earlySingletonObjects 的好处是,如果此时又有其他地方尝试获取未初始化的单例,可以从 earlySingletonObjects 直接取出而不需要再调用 getEarlyBeanReference 。
从流程图上看,实际上注入 C 的 A 实例,还在填充属性阶段,并没有完全地初始化。等递归回溯回去,A 顺利拿到依赖 B,才会真实地完成 A 的加载。
获取到完整的 RootBeanDefintion 后,就可以拿这份定义信息来实例具体的 Bean。
具体实例创建见 .createBeanInstance ,返回 Bean 的包装类 BeanWrapper,一共有三种策略:
使用工厂方法创建,会先使用 getBean 获取工厂类,然后通过参数找到匹配的工厂方法,调用实例化方法实现实例化,具体见 ConstructorResolver.instantiateUsingFactoryMethod :
使用有参构造函数创建,整个过程比较复杂,涉及到参数和构造器的匹配。为了找到匹配的构造器,Spring 花了大量的工作,见 ConstructorResolver.autowireConstructor :
使用无参构造函数创建是最简单的方式,见 .instantiateBean :
我们发现这三个实例化方式,最后都会走 getInstantiationStrategy().instantiate(...) ,见实现类 SimpleInstantiationStrategy.instantiate :
虽然拿到了构造函数,并没有立即实例化。因为用户使用了 replace 和 lookup 的配置方法,用到了动态代理加入对应的逻辑。如果没有的话,直接使用反射来创建实例。
创建实例后,就可以开始注入属性和初始化等操作。
但这里的 Bean 还不是最终的 Bean。返回给调用方使用时,如果是 FactoryBean 的话需要使用 getObject 方法来创建实例。见 AbstractBeanFactory.getObjectFromBeanInstance ,会执行到 doGetObjectFromFactoryBean :
实例创建完后开始进行属性的注入,如果涉及到外部依赖的实例,会自动检索并关联到该当前实例。
Ioc 思想体现出来了。正是有了这一步操作,Spring 降低了各个类之间的耦合。
属性填充的入口方法在 .populateBean 。
可以看到主要的处理环节有:
如果我们的 Bean 需要容器的一些资源该怎么办?比如需要获取到 BeanFactory、ApplicationContext 等等。
Spring 提供了 Aware 系列接口来解决这个问题。比如有这样的 Aware:
Spring 在初始化阶段,如果判断 Bean 实现了这几个接口之一,就会往 Bean 中注入它关心的资源。
见 .invokeAwareMethos :
在 Bean 的初始化前或者初始化后,我们如果需要进行一些增强操作怎么办?
这些增强操作比如打日志、做校验、属性修改、耗时检测等等。Spring 框架提供了 BeanPostProcessor 来达成这个目标。比如我们使用注解 @Autowire 来声明依赖,就是使用 来实现依赖的查询和注入的。接口定义如下:
实现该接口的 Bean 都会被 Spring 注册到 beanPostProcessors 中, 见 AbstractBeanFactory :
只要 Bean 实现了 BeanPostProcessor 接口,加载的时候会被 Spring 自动识别这些 Bean,自动注册,非常方便。
然后在 Bean 实例化前后,Spring 会去调用我们已经注册的 beanPostProcessors 把处理器都执行一遍。
这里使用了责任链模式,Bean 会在处理器链中进行传递和处理。当我们调用 BeanFactory.getBean 的后,执行到 Bean 的初始化方法 .initializeBean 会启动这些处理器。
自定义初始化有两种方式可以选择:
见 .invokeInitMethods :
Bean 已经加载完毕,属性也填充好了,初始化也完成了。
在返回给调用者之前,还留有一个机会对 Bean 实例进行类型的转换。见 AbstractBeanFactory.doGetBean :
抛开一些细节处理和扩展功能,一个 Bean 的创建过程无非是:
获取完整定义 -> 实例化 -> 依赖注入 -> 初始化 -> 类型转换。
作为一个完善的框架,Spring 需要考虑到各种可能性,还需要考虑到接入的扩展性。
所以有了复杂的循环依赖的解决,复杂的有参数构造器的匹配过程,有了 BeanPostProcessor 来对实例化或初始化的 Bean 进行扩展修改。
先有个整体设计的思维,再逐步击破针对这些特殊场景的设计,整个 Bean 加载流程迎刃而解。
F. spring boot@cacheable的缓存怎么使用
从3.1开始,Spring引入了对Cache的支持。其使用方法和原理都类似于Spring对事务管理的支持。Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中
G. spring一级缓存和二级缓存的区别是什么
一级缓存:
就是Session级别的缓存。一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中。
如果短时间内这个session(一定要同一个session)又做了同一个操作,那么hibernate直接从一级缓存中拿,而不会再去连数据库,取数据。
它是内置的事务范围的缓存,不能被卸载。
二级缓存:
就是SessionFactory级别的缓存。顾名思义,就是查询的时候会把查询结果缓存到二级缓存中。
如果同一个sessionFactory创建的某个session执行了相同的操作,hibernate就会从二级缓存中拿结果,而不会再去连接数据库。
这是可选的插件式的缓存,在默认情况下,SessionFactory不会启用这个插件。
可以在每个类或每个集合的粒度上配置。缓存适配器用于把具体的缓存实现软件与Hibernate集成。
严格意义上说,SessionFactory缓存分为两类:内置缓存和外置缓存。我们通常意义上说的二级缓存是指外置缓存。
内置缓存与session级别缓存实现方式相似。前者是SessionFactory对象的一些集合属性包含的数据,后者是指Session的一些集合属性包含的数据
SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句。
映射元数据是映射文件中数据的拷贝;
而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来。
SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。
Hibernate的这两级缓存都位于持久化层,存放的都是数据库数据的拷贝。
缓存的两个特性:
缓存的范围
缓存的并发访问策略
1、缓存的范围
决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。
事务范围
进程范围
集群范围
注:
对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。
事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。
2、缓存的并发访问策略
当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。
在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。
因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低。
A 事务型:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。
对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
B 读写型:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。
对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
C 非严格读写型:不保证缓存与数据库中数据的一致性。
如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。
对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
D 只读型:对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。
什么样的数据适合存放到第二级缓存中?
1、很少被修改的数据
2、不是很重要的数据,允许出现偶尔并发的数据
3、不会被并发访问的数据
4、参考数据
不适合存放到第二级缓存的数据?
1、经常被修改的数据
2、财务数据,绝对不允许出现并发
3、与其他应用共享的数据。
Hibernate的二级缓存策略的一般过程如下:
1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
注:
Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。
Query缓存策略的过程如下:
1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。
2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。
3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。
H. windows环境下Redis+Spring缓存实例
一、Redis了解
1.1、Redis介绍:
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis数据库完全在内存中,使用磁盘仅用于持久性。相比许多键值数据存储,Redis拥有一套较为丰富的数据类型。Redis可以将数据复制到任意数量的从服务器。
1.2、Redis优点:
(1)异常快速:Redis的速度非常快,每秒能执行约11万集合,每秒约81000+条记录。
(2)支持丰富的数据类型:Redis支持最大多数开发人员已经知道像列表,集合,有序集合,散列数据类型。这使得它非常容易解决各种各样的问题,因为我们知道哪些问题是可以处理通过它的数据类型更好。
(3)操作都是原子性:所有Redis操作是原子的,这保证了如果两个客户端同时访问的Redis服务器将获得更新后的值。
(4)多功能实用工具:Redis是一个多实用的工具,可以在多个用例如缓存,消息,队列使用(Redis原生支持发布/订阅),任何短暂的数据,应用程序,如Web应用程序会话,网页命中计数等。
1.3、Redis缺点:
(1)单线程
(2)耗内存
二、64位windows下Redis安装
Redis官方是不支持windows的,但是Microsoft Open Tech group 在 GitHub上开发了一个Win64的版本,下载地址:https://github.com/MSOpenTech/redis/releases。注意只支持64位哈。
小宝鸽是下载了Redis-x64-3.0.500.msi进行安装。安装过程中全部采取默认即可。
安装完成之后可能已经帮你开启了Redis对应的服务,博主的就是如此。查看资源管理如下,说明已经开启:
已经开启了对应服务的,我们让它保持,下面例子需要用到。如果没有开启的.,我们命令开启,进入Redis的安装目录(博主的是C:Program FilesRedis),然后如下命令开启:
redis-server redis.windows.conf
OK,下面我们进行实例。
三、详细实例
本工程采用的环境:Eclipse + maven + spring + junit
3.1、添加相关依赖(spring+junit+redis依赖),pom.xml:
4.0.0 com.luo redis_project 0.0.1-SNAPSHOT 3.2.8.RELEASE 4.10 org.springframework spring-core ${spring.version} org.springframework spring-webmvc ${spring.version} org.springframework spring-context ${spring.version} org.springframework spring-context-support ${spring.version} org.springframework spring-aop ${spring.version} org.springframework spring-aspects ${spring.version} org.springframework spring-tx ${spring.version} org.springframework spring-jdbc ${spring.version} org.springframework spring-web ${spring.version} junit junit ${junit.version} test org.springframework spring-test ${spring.version} test org.springframework.data spring-data-redis 1.6.1.RELEASE redis.clients jedis 2.7.3
3.2、spring配置文件application.xml:
<"1.0" encoding="UTF-8"> classpath:properties/*.properties
3.3、Redis配置参数,redis.properties:
#redis中心#绑定的主机地址redis.host=127.0.0.1#指定Redis监听端口,默认端口为6379redis.port=6379#授权密码(本例子没有使用)redis.password=123456 #最大空闲数:空闲链接数大于maxIdle时,将进行回收redis.maxIdle=100 #最大连接数:能够同时建立的“最大链接个数”redis.maxActive=300 #最大等待时间:单位msredis.maxWait=1000 #使用连接时,检测连接是否成功 redis.testOnBorrow=true#当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能redis.timeout=10000
3.4、添加接口及对应实现RedisTestService.Java和RedisTestServiceImpl.java:
package com.luo.service; public interface RedisTestService { public String getTimestamp(String param);}
package com.luo.service.impl; import org.springframework.stereotype.Service;import com.luo.service.RedisTestService; @Servicepublic class RedisTestServiceImpl implements RedisTestService { public String getTimestamp(String param) { Long timestamp = System.currentTimeMillis(); return timestamp.toString(); } }
3.5、本例采用spring aop切面方式进行缓存,配置已在上面spring配置文件中,对应实现为MethodCacheInterceptor.java:
package com.luo.redis.cache; import java.io.Serializable;import java.util.concurrent.TimeUnit;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations; public class MethodCacheInterceptor implements MethodInterceptor { private RedisTemplate redisTemplate; private Long defaultCacheExpireTime = 10l; // 缓存默认的过期时间,这里设置了10秒 public Object invoke(MethodInvocation invocation) throws Throwable { Object value = null; String targetName = invocation.getThis().getClass().getName(); String methodName = invocation.getMethod().getName(); Object[] arguments = invocation.getArguments(); String key = getCacheKey(targetName, methodName, arguments); try { // 判断是否有缓存 if (exists(key)) { return getCache(key); } // 写入缓存 value = invocation.proceed(); if (value != null) { final String tkey = key; final Object tvalue = value; new Thread(new Runnable() { public void run() { setCache(tkey, tvalue, defaultCacheExpireTime); } }).start(); } } catch (Exception e) { e.printStackTrace(); if (value == null) { return invocation.proceed(); } } return value; } /** * 创建缓存key * * @param targetName * @param methodName * @param arguments */ private String getCacheKey(String targetName, String methodName, Object[] arguments) { StringBuffer sbu = new StringBuffer(); sbu.append(targetName).append("_").append(methodName); if ((arguments != null) && (arguments.length != 0)) { for (int i = 0; i < arguments.length; i++) { sbu.append("_").append(arguments[i]); } } return sbu.toString(); } /** * 判断缓存中是否有对应的value * * @param key * @return */ public boolean exists(final String key) { return redisTemplate.hasKey(key); } /** * 读取缓存 * * @param key * @return */ public Object getCache(final String key) { Object result = null; ValueOperations operations = redisTemplate .opsForValue(); result = operations.get(key); return result; } /** * 写入缓存 * * @param key * @param value * @return */ public boolean setCache(final String key, Object value, Long expireTime) { boolean result = false; try { ValueOperations operations = redisTemplate .opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } public void setRedisTemplate( RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; }}
3.6、单元测试相关类:
package com.luo.baseTest; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; //指定bean注入的配置文件 @ContextConfiguration(locations = { "classpath:application.xml" }) //使用标准的JUnit @RunWith注释来告诉JUnit使用Spring TestRunner @RunWith(SpringJUnit4ClassRunner.class) public class SpringTestCase extends { }
package com.luo.service; import org.junit.Test;import org.springframework.beans.factory.annotation.Autowired; import com.luo.baseTest.SpringTestCase; public class RedisTestServiceTest extends SpringTestCase { @Autowired private RedisTestService redisTestService; @Test public void getTimestampTest() throws InterruptedException{ System.out.println("第一次调用:" + redisTestService.getTimestamp("param")); Thread.sleep(2000); System.out.println("2秒之后调用:" + redisTestService.getTimestamp("param")); Thread.sleep(11000); System.out.println("再过11秒之后调用:" + redisTestService.getTimestamp("param")); } }
3.7、运行结果:
四、源码下载:redis-project().rar
以上就是本文的全部内容,希望对大家的学习有所帮助。