- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
很多时候为了方便我们都采用实体对象进行前后端的数据交互,然后为了便捷开发我们都会采用DTO对象进行转换为数据库对象,然后调用 UpdateById 将变更后的数据存入到数据库内,这样的一个做法有什么问题呢,如果你的系统并发量特别少甚至没有并发量那么这么做是没什么关系的无可厚非,但是如果你的系统有并发量那么在某些情况下会有严重的问题. 。
现在我们有一条待审核记录,其中 status 0表示待提交, 1表示待审核 。
id | name | status | description |
---|---|---|---|
1 | 记录1 | 0 | 我是备注 |
假设有两个用户,A用户想对当前记录的 description 字段进行修改,B用户想对当前记录进行提交 。
/api/update 。
{"id":1,"name":"记录1","status":0,"description":"修改后的备注"}
{"id":1,"name":"记录1","status":1,"description":"我是备注 "}
A用户伪代码 。
Entity entity = entityMapper.selectOne(1);//A1
//查询结果{"id":1,"name":"记录1","status":0,"description":"我是备注'"}
if(status.待审核!=entity.status){//A2
throw new BusinessException("当前记录无法修改");
}
BeanUtil.copyProperties(request,entity);//A3
entityMapper.updateById(entity);//A4
-- update table set name='记录1',status=0,description='修改后的备注' where id=1
B用户伪代码 。
Entity entity = entityMapper.selectOne(1);//B1
//查询结果{"id":1,"name":"记录1","status":0,"description":"我是备注'"}
if(status.待审核!=entity.status){//B2
throw new BusinessException("当前记录无法提交");
}
entity.status=status.待审核;//B3
entityMapper.updateById(entity);//B4
-- update table set name='记录1',status=1,description='我是备注', where id=1
A1=>A2=>A3=>B1=>B2=>B3=>B4=>A4 加入并发情况下那么针对当前记录我们生成的两个操作因为没有考虑并发问题基于上述执行顺序,最终数据库的记录将会被A4覆盖也就是提交失败,那么如果提交审核会触发一些事件那么就就会有严重的问题产生,操作将会变得不是幂等.
首先我们修改表结构添加版本号字段 。
id | name | status | description | version |
---|---|---|---|---|
1 | 记录1 | 0 | 我是备注 | 1 |
A4和B4的执行sql改为orm支持的乐观锁模式 。
-- A4
update table set name='记录1',status=0,description='修改后的备注',version=2 where id=1 and version=1
-- B4
update table set name='记录1',status=1,description='我是备注',version=2 where id=1 and version=1
因为A4和B4两条记录只有一条记录可以生效,所以另一条语句肯定返回受影响行数为0.对于返回为0的操作可以告知用户端操作失败请重试.
这种方式看着看着很美好但是也是有一定的缺点的,就是他是乐观锁强串行化,针对一些不必要的字段其实大部分的时候我们完全可以采取 后覆盖 模式比如修改 name ,修改 description ,但是因为乐观锁的存在导致我们的并发粒度变粗所以是否使用乐观锁需要进行一个取舍.
通过在请求外部也就是A1-A4和B1-B4外部进行lock包裹,让两个执行变成串行化,可以用id:1作为分布式锁的key,加入A先执行那么B执行后可以提交,加入B先执行那么A就会报错,缺点也很明显需要将对应记录的任何操作都进行分布式锁进行处理。需要掌握好锁的粒度和管理,如果出现其他业务操作中涉及到当前记录的修改那么分布式锁又会遇到很多问题,在单一环境下分布式锁可以解决,但是大部分情况下并不是用在这个场景下.
既然乐观锁有粒度太粗导致并发度太低,那么可以选择性不要一刀切,我们以状态来作为乐观锁更新数据 。
-- A4
update table set name='记录1',status=0,description='修改后的备注' where id=1 and status=0//status=0是因为我们查到的是0
-- B4
update table set name='记录1',status=1,description='我是备注' where id=1 and status=0//status=0是因为我们查到的是0
这种方式我们解决了 name 或者 description 这些无关顺序痛痒的更新粒度,使其更新其余字段并发度大大提高,大家可以多个线程一起更新name或者description都是不会出现乐观锁的错误.
虽然我们解决了普通字段的更新修改但是针对部分关键字段的更新如果是整个对象更新依然会有问题,那么又回到了乐观锁是一个比较好的处理方式,比如 stock_num 字段 。
我们来看看如果在 easy-query 下我们分别如何实现上述功能,首先我们还是在之前的solon项目中进行代码添加, 。
@Data
@Table("test_update")
public class TestUpdateEntity {
@Column(primaryKey = true)
private String id;
private String name;
private Integer status;
private String description;
}
//添加测试数据
TestUpdateEntity testUpdateEntity = new TestUpdateEntity();
testUpdateEntity.setId("1");
testUpdateEntity.setName("测试1");
testUpdateEntity.setStatus(0);
testUpdateEntity.setDescription("描述信息");
easyQuery.insertable(testUpdateEntity).executeRows();
return "ok";
一般而言我们会先选择查询对象,然后判断状态然后将dto请求赋值给对象,之后更新对象 。
@Mapping(value = "/testUpdate2",method = MethodType.POST)
public String testUpdate2(@Validated TestUpdate2Rquest request){
TestUpdateEntity testUpdateEntity = easyQuery.queryable(TestUpdateEntity.class)
.whereById(request.getId()).firstNotNull("未找到对应的记录");
if(!testUpdateEntity.getStatus().equals(0)){
return "当前状态不是0";
}
BeanUtil.copyProperties(request,testUpdateEntity);
testUpdateEntity.setStatus(1);
easyQuery.updatable(testUpdateEntity).executeRows();
return "ok";
}
==> Preparing: SELECT `id`,`name`,`status`,`description` FROM `test_update` WHERE `id` = ? LIMIT 1
==> Parameters: 1(String)
<== Time Elapsed: 22(ms)
<== Total: 1
==> Preparing: UPDATE `test_update` SET `name` = ?,`status` = ?,`description` = ? WHERE `id` = ?
==> Parameters: 测试1(String),1(Integer),123(String),1(String)
<== Total: 1
我们看到这边更新将status由0改成了1,虽然我们中间做了一次是否为0的判断,但是在并发环境下这么更新是有问题的,而且这边我们仅更新了 description 和 status 字段缺把name字段也更新了 。
首先我们改造一下代码,在请求方法上添加了对应的注解 @EasyQueryTrack 又因为我们配置了默认开启追踪所以仅需要查询数据库对象既可以追踪数据 。
//自动追踪差异更新 需要开启default-track: true如果没开启那么就使用`asTracking`启用追踪
@EasyQueryTrack
@Mapping(value = "/testUpdate3",method = MethodType.POST)
public String testUpdate3(@Validated TestUpdate2Rquest request){
TestUpdateEntity testUpdateEntity = easyQuery.queryable(TestUpdateEntity.class)
//.asTracking() //如果配置文件默认选择追踪那么只需要添加 @EasyQueryTrack 注解
.whereById(request.getId())
.firstNotNull("未找到对应的记录");
if(!testUpdateEntity.getStatus().equals(0)){
return "当前状态不是0";
}
BeanUtil.copyProperties(request,testUpdateEntity);
testUpdateEntity.setStatus(1);
easyQuery.updatable(testUpdateEntity)
//指定更新条件为主键和status字段
.whereColumns(o->o.columnKeys().column(TestUpdateEntity::getStatus))
.executeRows(1,"当前状态不是0");//如果更新返回的受影响函数不是1,那么就抛出错误,当然你也可以获取返回结果自行处理
return "ok";
}
==> Preparing: SELECT `id`,`name`,`status`,`description` FROM `test_update` WHERE `id` = ? LIMIT 1
==> Parameters: 1(String)
<== Time Elapsed: 23(ms)
<== Total: 1
==> Preparing: UPDATE `test_update` SET `status` = ?,`description` = ? WHERE `id` = ? AND `status` = ?
==> Parameters: 1(Integer),123(String),1(String),0(Integer)
<== Total: 1
更新条件自动感知需要更新的列,不会无脑全更新 ,并且支持简单的配置支持当前status并发更新,会自动在where上带上原来的值,并且在set处更新为新值,整个更新条件对于并发情况下的处理变得非常简单 。
@Data
@Table("test_update_version")
public class TestUpdateVersionEntity {
@Column(primaryKey = true)
private String id;
private String name;
private Integer status;
private String description;
@Version(strategy = VersionUUIDStrategy.class)
private String version;
}
//初始化数据
TestUpdateVersionEntity testUpdateVersionEntity = new TestUpdateVersionEntity();
testUpdateVersionEntity.setId("1");
testUpdateVersionEntity.setName("测试1");
testUpdateVersionEntity.setStatus(0);
testUpdateVersionEntity.setDescription("描述信息");
testUpdateVersionEntity.setVersion(UUID.randomUUID().toString().replaceAll("-",""));
easyQuery.insertable(testUpdateVersionEntity).executeRows();
==> Preparing: INSERT INTO `test_update_version` (`id`,`name`,`status`,`description`,`version`) VALUES (?,?,?,?,?)
==> Parameters: 1(String),测试1(String),0(Integer),描述信息(String),0603b2e00a1d4b869d13cf974a5cc885(String)
<== Total: 1
@Mapping(value = "/testUpdate2",method = MethodType.POST)
public String testUpdate2(@Validated TestUpdate2Rquest request){
TestUpdateVersionEntity testUpdateVersionEntity = easyQuery.queryable(TestUpdateVersionEntity.class)
.whereById(request.getId()).firstNotNull("未找到对应的记录");
if(!testUpdateVersionEntity.getStatus().equals(0)){
return "当前状态不是0";
}
BeanUtil.copyProperties(request,testUpdateVersionEntity);
testUpdateVersionEntity.setStatus(1);
easyQuery.updatable(testUpdateVersionEntity).executeRows();
return "ok";
}
==> Preparing: SELECT `id`,`name`,`status`,`description`,`version` FROM `test_update_version` WHERE `id` = ? LIMIT 1
==> Parameters: 1(String)
<== Time Elapsed: 16(ms)
<== Total: 1
==> Preparing: UPDATE `test_update_version` SET `name` = ?,`status` = ?,`description` = ?,`version` = ? WHERE `version` = ? AND `id` = ?
==> Parameters: 测试1(String),1(Integer),123(String),cf6c2f3106b24aba965bb4cc54235076(String),0603b2e00a1d4b869d13cf974a5cc885(String),1(String)
<== Total: 1
虽然我们采用了乐观锁但是还是会出现全字段更新的情况,所以这边再次使用差异更新来实现 。
@EasyQueryTrack
@Mapping(value = "/testUpdate3",method = MethodType.POST)
public String testUpdate3(@Validated TestUpdate2Rquest request){
TestUpdateVersionEntity testUpdateVersionEntity = easyQuery.queryable(TestUpdateVersionEntity.class)
.whereById(request.getId()).firstNotNull("未找到对应的记录");
if(!testUpdateVersionEntity.getStatus().equals(0)){
return "当前状态不是0";
}
BeanUtil.copyProperties(request,testUpdateVersionEntity);
testUpdateVersionEntity.setStatus(1);
easyQuery.updatable(testUpdateVersionEntity).executeRows();
return "ok";
}
==> Preparing: UPDATE `test_update_version` SET `status` = ?,`description` = ?,`version` = ? WHERE `version` = ? AND `id` = ?
==> Parameters: 1(Integer),1234(String),7e96f217bc13451c9d10a8fba50780a6(String),cf6c2f3106b24aba965bb4cc54235076(String),1(String)
<== Total: 1
使用追踪查询仅更新我们需要更新的字段 easy-query 一款为开发者而生的orm框架,拥有非常完善的功能且支持非常易用的功能,让你在编写业务时可以非常轻松的实现并发操作,哪怕没有乐观锁.
看到这边您应该已经知道了 solon 国产框架的简洁和 easy-query 的便捷,如果本篇文章对您有帮助或者您觉得还行请给我一个星星表示支持谢谢 当前项目地址 demo https://gitee.com/xuejm/solon-encrypt 。
文档地址 https://xuejm.gitee.io/easy-query-doc/ 。
GITHUB地址 https://github.com/xuejmnet/easy-query 。
GITEE地址 https://gitee.com/xuejm/easy-query 。
文档地址 https://xuejm.gitee.io/easy-query-doc/ 。
GITHUB地址 https://github.com/noear/solon 。
GITEE地址 https://gitee.com/noear/solon 。
最后此篇关于解放生产力orm并发更新下应该这么处理求求你别再用UpdateById了的文章就讲到这里了,如果你想了解更多关于解放生产力orm并发更新下应该这么处理求求你别再用UpdateById了的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试在多线程环境中实现某种累积逻辑;我想知道没有 lock 和 synchronized 关键字是否有更好/更快的方法来做到这一点?以下是我当前的代码: public class Concurr
我需要帮助构建一个实现信号量的监视器,简单的 C 示例就可以。 这是为了证明可以在任何可以使用信号量的地方使用监视器。 最佳答案 如果您说允许使用互斥锁/condvars,请检查: #include
我已经构建了一些返回部分产品目录的 ajax,并且我正在尝试将 xml 输出到文档中,到目前为止,这是我所拥有的: $("#catalog").append("Item NamePriceDe
很抱歉,如果我的问题之前已经被问过,或者它太明显了,但我真的需要澄清这一点。感谢您的帮助。 在多用户界面中,如果来自不同用户的相同事务同时到达服务器,会发生什么? 我有下一张表: create tab
这可能是一个愚蠢的问题,但是这个程序的输出(它的方式)可以为零吗? public class Test2{ int a = 0; AtomicInteger b = new Atomi
假设我本地主机上的一个网站处理每个请求大约需要 3 秒。这很好,正如预期的那样(因为它在幕后进行了一些奇特的网络)。 但是,如果我在选项卡(在 firefox 中)中打开相同的 url,然后同时重新加
我对 MongoDB 的读锁定有点困惑。单个集合可以支持多少个并发读取操作? 最佳答案 如 tk 给出的链接中所写:http://www.mongodb.org/pages/viewpage.acti
如果有四个并发的 CUDA 应用程序在一个 GPU 中竞争资源会发生什么这样他们就可以将工作卸载到图形卡上了? Cuda Programming Guide 3.1 提到那里 某些方法是异步的: 内核
👊上次的百度面试遇到了关于spark的并发数的问题,今天我们就来将这些问题都一并解决一下,图画的的有点丑,还行大家见谅,百度实习的问题我放在了下面的链接👇: 链接: 2022百度大数据开发工程师实
我对 Groovy 线程有疑问。 我的任务是以某种方式翻译给定目录中的每个文件 并将生成的输出放在其他目录中的文件中。 我编写了以下代码,该代码有效: static def translateDir(
Java中的同步和锁定有什么区别? 最佳答案 synchronized是语言关键字;锁是对象。 当一个方法或代码块被标记为同步时,您是说该方法或代码块必须先获得某个锁对象(可以在同步的语法中指定)才能
我需要创建一个能够同时处理来自客户端的多个请求的并发 RPC 服务器。 使用 rpcgen linux编译器(基于sun RPC),不支持-A为并发服务器创建 stub 的选项。 (-A 选项在 so
System.out.println("Enter the number of what you would like to do"); System.out.println("1 = Manuall
我正在将我的应用程序移植到 iOS 8.0 并注意到 UIAlertView 已被弃用。 所以我改变了使用 UIAlertController 的方法。这在大多数情况下都有效。 除了,当我的应用程序打
我正在逐行同时读取两个文本文件。 我特别想做的是当lineCount在每个线程上都是相同的我想看看扫描仪当前正在读取的字符串。 我环顾四周寻找可以实现的某些模式,例如 Compare and Swap
我正在阅读 Java Concurrency in Practice .在章节中断政策部分 取消和关闭 它提到 A task should not assume anything about the
我正在尝试学习线程,互斥等的基础知识。遵循here的文档和示例。在下面的代码中,我得到预期的输出。问题: 想确认我是否有任何陷阱?我们如何改善下面的代码? 我的线程在哪一行尝试获取互斥锁或正在等待互斥
并发是指两个任务在不同的线程上并行运行。但是,异步方法并行运行,但在同一个线程上。这是如何实现的?另外,并行性怎么样? 这三个概念有什么区别? 最佳答案 并发和并行实际上与您正确推测的原理相同,两者都
以此ConcurrentDouble类定义为例: public class ConcurrentDouble { public double num = 0; public void subt
在得知并发确实增加了许多人的吞吐量后,我一直计划在项目中使用并发。现在我在多线程或并发方面还没有做太多工作,因此决定在实际项目中使用它之前学习并进行简单的概念验证。 以下是我尝试过的两个示例: 1.
我是一名优秀的程序员,十分优秀!