【ActiveJdbc】04
一、乐观锁
作者po的乐观锁思想:
http://en.wikipedia.org/wiki/Optimistic_concurrency_control
维基百科,墙了看不到
作者要求表字段必须存在一个record_version
ActiveJDBC 通过一个简单的约定来支持乐观并发:
一个数据库表需要提供一个以record_version能够存储非十进制类型的类型命名的列,
例如 MySQL 的 LONG、Oracle 的 NUMBER 等。
向表中新加入的记录,ActiveJdbc会默认向这个记录record_version设置值1
值为1时,表示这个记录没有被更新,或者说,没有被操作过
操作冲突:
有时,您的代码可能会从表中读取相同的记录以进行更新。在这些情况下,第一次更新会成功,但第二次不会。让我们检查一下这种情况:
Profile p1 = Profile.findById(1);
Profile p2 = Profile.findById(1); p1.set("profile_type", "hotel");
p1.saveIt(); p2.set("profile_type", "vacation");
p2.saveIt(); //<<<========= This will throw a StaleModelException
在上面的代码片段中,在第 1 行和第 2 行,相同的记录被加载到模型中。然后,在第 5 行,更新第一个。
这会将记录的版本增加到 3,并使模型 p2 过时。
以后,当您尝试保存模型 p2 时,您将收到异常。
ActiveJdbc会抛出异常:
org.javalite.activejdbc.StaleModelException:
Failed to update record for model 'class com.acme.Profile', with id = 1 and record_version = 2.
Either this record does not exist anymore, or has been updated to have another record_version.
项目里面会重写或者封装这个异常,改成友好的提示信息抛给前台,做警告弹窗使用
目的就是阻止冲突操作,然后回到主列表刷新记录
更改乐观锁设定:
如果不使用record_version字段,作者也提供了注解,使用这个注解来更换乐观锁字段
默认情况下,版本列称为version_record。如果已经存在架构或者您需要与其他系统兼容,您可以使用注释覆盖名称:
@VersionColumn("lock_version")
public class Item extends Model { ... }
版本号将存储在lock_version列中,而其他一切都将按预期工作。
作者的建议:
如何使用乐观锁
规则非常简单,ActiveJDBC 查找record_version列并动态配置自身以处理乐观锁。
这意味着如果此列存在,则将使用乐观锁定。
如果记录不存在,则不会使用乐观锁定。
如果没有此列,后来又添加了,则需要重新启动系统,因为 ActiveJDBC 在开始时会扫描数据库架构。
相反,如果要关闭它,请删除列record_version并重新启动系统。
使用乐观锁定的应用程序开发人员应该注意异常StaleModelException(即使它是一个 RuntimeException)
并在他们的代码中构建控件以适当地拦截和处理它。
二、生命周期回调
与 ActiveRecord 一样,ActiveJDBC 也有生命周期回调。
这些方法可以在 Model 子类上实现,以获取在模型上执行的特殊生命周期事件的通知。
这些回调在由Model类实现的接口中捕获:
public interface CallbackListener {
void afterLoad(Model m);
void beforeSave(Model m);
void afterSave(Model m);
void beforeCreate(Model m);
void afterCreate(Model m);
void beforeDelete(Model m);
void afterDelete(Model m);
void beforeValidation(Model m);
void afterValidation(Model m);
}
请参阅:回调监听器
子类可以覆盖总共八个调用以获取特定事件的通知。
外部监听器的注册
您可以在任何模型外部实现CallbackListener接口,然后注册它:
CallbackAdapter adapter = new CallbackAdapter() {
@Override
public void afterLoad(Model m) {
//do what you need to after a model is loaded with data from the database.
}
};
Person.callbackWith(adapter);
这是假设 Person 是一个模型。
您可以实现CallbackListener接口或扩展CallbackAdapter(其中所有方法都使用空白主体实现)并且仅覆盖您需要的那些。
覆盖模型回调方法
Model 类已经扩展了一个类CallbackAdapter,它提供了这八个方法的空实现。开发人员所需要做的就是覆盖一个或多个方法以在特定时间执行任务。
作者下面提供了一个应用场景:
假设我们有一个模型User:
public class User extends Model{}
用户还有一个密码,需要以加密形式存储在数据库中。在这种情况下使用回调很有用,因为您所要做的就是覆盖一个beforeSave()方法并提供一些加密例程来确保密码安全:
public class User extends Model{
public void beforeSave(){
set("password" encryptPassword());
}
private String encryptPassword(){
//do what it takes
}
}
该框架将在适当beforeSave()的上下文中save()或saveIt()在适当的时候调用,并且您的代码将对密码进行加密以进行存储。
三、缓存
缓存是每个主要系统不可或缺的一部分,它提高了性能,减少了 IO,并使整体用户体验更加愉快。ActiveJDBC 中的缓存适用于模型实例的查询和创建级别。例如,调用:
List<Library> illLibs = Library.where("state = ?", "IL");
可能会调用数据库,或者结果可能来自缓存,具体取决于缓存和具体模型的Library配置方式
开启缓存:
ActiveJDBC 提供注解来指定查询哪些表将被缓存:
@Cached
public class Library extends Model {}
与其他情况一样,这是一个将模型标记为可缓存
的声明。如果您启用日志记录(通过提供系统属性activejdbc.log),您将看到来自 ActiveJDBC 的大量输出,类似于:
3076 [main] INFO org.javalite.activejdbc.DB - Query: "SELECT * FROM libraries WHERE id = ?", with parameters: [1], took: 0 milliseconds
3076 [main] INFO org.javalite.activejdbc.cache.QueryCache - HIT, "SELECT * FROM libraries WHERE id = ?", with parameters: [1]
3077 [main] INFO org.javalite.activejdbc.DB - Query: "INSERT INTO libraries (address, state, city) VALUES (?, ?, ?)", with parameters: [123 Pirate Street, CA, Bloomington], took: 1 milliseconds
3077 [main] INFO org.javalite.activejdbc.cache.QueryCache - table cache purged for: libraries
3077 [main] INFO org.javalite.activejdbc.cache.QueryCache - table cache purged for: books
3077 [main] INFO org.javalite.activejdbc.cache.QueryCache - MISS, "SELECT * FROM libraries WHERE id = ?", with parameters: [1]
3078 [main] INFO org.javalite.activejdbc.DB - Query: "SELECT * FROM libraries WHERE id = ?", with parameters: [1], took: 0 milliseconds
缓存配置
缓存配置包括在文件中提供缓存管理器类名activejdbc.properties。
该文件必须位于类路径的根目录下。
这是一个例子:
#inside file: activejdbc.properties
#or EHCache:
cache.manager=org.javalite.activejdbc.cache.EHCacheManager
#cache.manager=org.javalite.activejdbc.cache.OSCacheManager
这里发生了两件事:
1. 缓存一般是启用的(即使你在类上有@Cached注释也不会启用),以及
2. ActiveJDBC 将使用 EHCacheManager 作为缓存的实现。
自动缓存清除
如果从上面检查日志,您将看到在向LIBRARIES
表中执行插入语句后,系统正在清除与该表以及BOOKS
表相关的缓存。
ActiveJDBC 这样做是因为内存中的缓存可能与数据库中的数据不同步,因此将被清除。
相关表?缓存也被清除。
由于存在关系:图书馆有很多书籍,书籍缓存也可能是陈旧的,这也是清除表BOOKS
的原因。
手动缓存清除
如果您想手动清除缓存(以防您在 Model API 之外进行可变或破坏性数据操作),您可以这样做:
org.javalite.activejdbc.cache.QueryCache
.instance()
.purgeTableCache("books");
清除所有缓存
如果你想清除所有缓存,这里是一个片段:
QueryCache
.instance()
.getCacheManager()
.flush(CacheEvent.ALL);
侦听/传播缓存事件
在更复杂的应用程序中,您可能希望监听缓存事件,然后对它们采取行动:假设跨集群传播缓存清除。
首先,您需要为缓存事件创建一个侦听器:
public class AppCacheEventListener implements CacheEventListener{
public void void onFlush(CacheEvent event){
// implementation goes here
}
}
然后,注册监听器:
Registry
.cacheManager()
.addCacheEventListener(new AppCacheEventListener());
完成此操作后,如果特定表的缓存被清除,则侦听器将开始收到通知。
缓存什么
虽然缓存是一个复杂的问题,但我建议缓存主要是查找数据。
查找数据是不经常改变的东西。
如果您开始缓存所有内容,您可能会遇到缓存抖动问题,您用数据填充缓存,然后很快将其清除,而没有缓存的好处。
您将使用额外的 CPU、RAM 和 IO(是否配置了集群)来降低性能,而不是提高性能,而最初拥有缓存的好处很少或没有。
内存爆炸问题
ActiveJDBC 缓存对象级别查询的结果。
例如,让我们考虑以下代码:
@Cached
public class Student(){} ... List<Student> students = professor.getAll(Student.class);
本质上,该框架会生成如下查询:
SELECT * FROM students WHERE professor_id = ?;
并professor_id为准备好的语句设置一个参数。
由于模型Student是@Cached,那么整个List<Student> students列表将被缓存。
作为缓存对象的列表的关键是查询文本以及查询的所有参数的组合。
结果,这两个查询:
SELECT * FROM students WHERE professor_id = 1;
SELECT * FROM students WHERE professor_id = 2;
将在缓存中产生两个独立的列表,因为它们的参数不同。
那么,如果您运行数千或数百万个相同但仅参数不同的查询,会发生什么?
你猜对了,你最终会在缓存中得到数百万个无用的对象,最终会得到一个OutOfMemoryError。
解决方案是检查代码,并确保您正在缓存实际可重用的对象。
可以直接访问和管理缓存而不是@Cached注解:
import org.javalite.activejdbc.Registry; CacheManager manager = Registry.cacheManager();
manager.addCache(group, key, object); ///then later in code: List<Students> students = (List<Students>)manager.getCache(group, key);
通过这种方式,您可以微调仅在缓存中存储特定对象的能力。
缓存数据直接暴露
检索缓存模型的实例时,请注意,对同一查询的后续调用可能会返回完全相同的实例。
ActiveJDBC 作为一个轻量级的框架,不会试图变得智能
并为您管理缓存数据的克隆。
因此,例如,考虑Person被注释为@Cached,随后的两次调用Person.findById(1)将返回相同的实例:
Person p1 = Person.findById(1);
System.out.println(p1.get("name")); // prints: John p1.set("name", "Jane"); // changes the cached data directly
// don't save p1, and ... Person p2 = Person.findById(1); // ... find the same person again
System.out.println(p2.get("name")); // prints: Jane
缓存或乐观锁
缓存和optimistic_locking不能相处。不要同时使用两者。
缓存保证对同一查询的后续调用返回相同的实例。因此,缓存管理器共享的内存中不能存在相同结果集的不同版本。
假设Profile,一个带有optimistic_locking的模型,也被注释为@Cached。将发生以下情况:
Profile p1 = Profile.findById(1);
Profile p2 = Profile.findById(1);
// p1 and p2 are actually references to the same instance. p1.set("profile_type", "hotel");
p1.saveIt();
// record_version of the instance is incremented, then updated in the database. p2.set("profile_type", "vacation");
p2.saveIt();
// As this is the same instance that had record_version incremented ealier,
// its record_version value will match the database
// and no StaleModelException will be thrown.
关系
ActiveJDBC 管理模型及其各自关系的缓存(见上文),但在某些情况下,您将使用将不相关模型联系在一起的查询:
List<User> users = User.where("id not in (select user_id from restricted_users)");
如果存在缓存的模型 User 和模型 RestrictedUser,并且这些表/模型没有关系,那么上面的行可能会出现逻辑问题。如果您执行上面的行,然后更改 RESTRICTED_USERS 表的内容,那么上面的查询将看不到更改,并将返回陈旧数据。开发人员需要意识到这一点,并谨慎处理这些问题。每当您更改 RESTRICTED_USERS 表中的数据时,请清除 User 模型:
User.purgeCache();
变异或破坏性操作
每当您对模型(INSERT、UPDATE、DELETE)执行可变或破坏性操作时,该模型的整个缓存都会失效。这意味着缓存最适合用于查找数据(废话!)。
该框架还将使所有相关表的缓存失效并删除。例如:
鉴于表:
create table USERS (INT id, name VARCHAR);
create table ADDRESSES (INT id, street VARCHAR, city VARCHAR, user_id INT);
和模型:
@Cached
public class User extends Model{} @Cached
public class Address extends Model{}
如果你这样做:
user.delete();
该框架将重置缓存:用户和地址模型,而不仅仅是用户。这样做是为了防止应用程序中出现逻辑错误。
不断变化的缓存数据将导致Cache Stampede。
不相关的模型
在某些情况下,您需要在表中使用适当的外键,但希望在代码中断开基于约定的关系
鉴于表:
create table USERS (INT id, name VARCHAR);
create table ADDRESSES (INT id, street VARCHAR, city VARCHAR, user_id INT);
和模型:
@Cached
public class User extends Model{} @Cached UnrelatedTo({User.class})
public class Address extends Model{}
如果你这样做:
user.delete();
框架只会重置 User 模型的缓存,不会触及 Address 模型的缓存。
有关更多信息,请参阅 JavaDoc:UnrelatedTo。
缓存提供者
ActiveJDBC 有一个简单的插件框架来添加缓存提供者。目前支持:
- OSCache 现在已经死了。尽管它在我们的许多项目中都运行良好,但我们建议使用 EHCache
- 高速缓存。EHCache 是高性能流行的开源项目。文档请参考:http : //ehcache.org/documentation
- 基于 Redis 的缓存提供程序(最近添加)
EHCache 配置 (v 2.x)
需要在ehcache.xml类路径根目录下的一个名为found的文件中提供配置。文件内容示例:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="true" monitoring="autodetect"> <diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
请注意,ActiveJDBC 不会在 EHCache 中创建命名缓存,而只会使用defaultCache此文件中元素指定的默认配置。
EHCache 配置 (v 3.6.3)
缓存管理器类的名称:org.javalite.activejdbc.cache.EHCache3Manager. 在文件中设置以下内容activejdbc.properties:
cache.manager=org.javalite.activejdbc.cache.EHCache3Manager
此外,您还需要配置 EHCache 本身。为此,添加一个名为activejdbc-ehcache.xml. 这是简单的 EHCache v3 配置:
<config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xsi:schemaLocation="http://www.ehcache.org/v3 ../../../main/resources/ehcache-core.xsd">
<cache-template name="activejdbc">
<key-type>java.lang.String</key-type>
<value-type>java.lang.Object</value-type>
<heap unit="entries">200</heap>
</cache-template>
</config>
有关更多涉及的配置选项,请参阅 EHCache v3 文档。
Redis缓存配置
缓存管理器类的名称:org.javalite.activejdbc.cache.RedisCacheManager.
在文件中设置以下内容activejdbc.properties:
cache.manager=org.javalite.activejdbc.cache.RedisCacheManager
此外,提供一个activejdbc-redis.properties具有两个属性的属性文件:redis.cache.manager.host和redis.cache.manager.port属性文件需要位于类路径的根目录下。
限制: Redis 缓存管理器不支持
CacheManager#flush(CacheEvent)值为ALL。
【ActiveJdbc】04的更多相关文章
- 【C】 04 - 表达式和语句
程序的生命力体现在它千变万化的行为,而再复杂的系统都是由最基本的语句组成的.C语句形式简单自由,但功能强大.从规范的角度学习C语法,一切显得简单而透彻,无需困扰于各种奇怪的语法. 1. 表达式(exp ...
- 【二叉查找树】04根据升序数组构造二叉查找树【Convert Sorted Array to Binary Search Tree】
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 给定一个升序的数组,把他转换成一个 ...
- 【SpringCloud】04.SpringCloud Eureka Server与Client的创建
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的.SpringCloud将它集成在其子项 ...
- 【SpringBoot】04.SpringBoot整合Filter的两种方式
SpringBoot整合Filter过滤器的两种方式: 1.通过扫描注解完成Filter组件注册 创建一个类,实现Filter接口,实现doFilter()方法 在该类使用注解@WebFilter,设 ...
- 【jenkins】04.SSH认证方式拉取Git代码
首先需要会git ssh 我们一般用http的形式拉取代码. ssh的好处就是不用每次输入密码,而且貌似会快丢丢,不知道是不是错觉. 大概需要三个步骤: 一.本地生成密钥对: 二.设置github上的 ...
- 【BIEE】06_UNION /UNION ALL集合中分类汇总求和占比字段特殊处理
环境准备 基于[BIEE]04..中建立的事实表 通过UNION ALL后得到如下报表: 优秀员工薪水公式:CASE WHEN "EMP_FACT"."级别"= ...
- 【scikit-learn】06:make_blobs聚类数据生成器
版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/kevinelstri/article/ ...
- 【一】Ubuntu14.04+Jekyll+Github Pages搭建静态博客
本系列有五篇:分别是 [一]Ubuntu14.04+Jekyll+Github Pages搭建静态博客:主要是安装方面 [二]jekyll 的使用 :主要是jekyll的配置 [三]Markdown+ ...
- disconf系列【1】——百度disconf在ubuntu14.04环境下的安装
disconf官网给出的安装文档默认读者已经非常熟练本文1.2章节给出的依赖软件的原理及使用方法,且官网默认安装环境为linux(windows安装方法只字未提).同时,官网对很多重要的细节语焉不详, ...
- 【博客美化】04.自定义地址栏logo
博客园美化相关文章目录: [博客美化]01.推荐和反对炫酷样式 [博客美化]02.公告栏显示个性化时间 [博客美化]03.分享按钮 [博客美化]04.自定义地址栏logo [博客美化]05.添加Git ...
随机推荐
- 再谈中断机制(APIC)
中断是硬件和软件交互的一种机制,可以说整个操作系统,整个架构都是由中断来驱动的.一个中断的起末会经历设备,中断控制器,CPU 三个阶段:设备产生中断信号,中断控制器翻译信号,CPU 来实际处理信号. ...
- C#.NET Winform承载WCF RESTful API (硬编码配置)
1.新建一个名为"WindowsForms承载WCF"的WINFORM程序. 2.在解决方案里添加一个"WCF 服务库"的项目,名为"WcfYeah& ...
- EF MYSQL 出现:输入字符串的格式不正确
实体类字段和数据库类型不一致. 比如:数据库是char类型字段,程序里声明为int.
- String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的
a.可变性:String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的.StringBuilder与StringBuffer ...
- reactHooks的组件通信
父组件调用子组件的方法 // 父组件 import React, { useEffect, useRef, useState } from 'react'; import StopModal from ...
- insert into 表名 set
insert into 表名 set CREATE TABLE `tbl_str` ( `id` INT DEFAULT NULL, `Str` VARCHAR(30) DEFAULT NULL ) ...
- 引入feign注入报错 org.springframework.beans.factory.NoSuchBeanDefinitionException解决
引入feign注入报错 org.springframework.beans.factory.NoSuchBeanDefinitionException解决 [172.16.22.215] out: C ...
- Linux 内核:设备驱动模型(2)driver-bus-device与probe
Linux 内核:设备驱动模型(2)driver-bus-device与probe 系列:Linux 内核:设备驱动模型 学习总结 参考: https://blog.csdn.net/lizuobin ...
- Freertos学习:07-队列
--- title: rtos-freertos-07-队列 EntryName : rtos-freertos-07 date: 2020-06-23 09:43:28 categories: ta ...
- selenium窗口之间的切换
import time from selenium.webdriver import Edge from selenium.webdriver.common.by import By from sel ...