背景
唯一性约束是一个经常出现的业务逻辑,刚开始我觉得非常简单,不过深入考虑后,发现实现起来还不是那么简单,下面就让我们分析一下。
两种场景下的唯一性约束
第一种场景:聚合根的某个属性的唯一性约束
示例:用户的用户名必须唯一。
第一种实现思路:后验证+不用数据库索引,在插入用户名和修改用户名之后执行一次验证,这个验证逻辑执行的事务隔离级别必须处于“读未提交”级别。
1 public volid Insert(User user) 2 { 3 using(var ts1 = new TransactionScope("读已提交")) 4 { 5 DoInsert(user); 6 using(var ts2 = new TransactionScope("读未提交")) 7 { 8 //如果违背约束,抛出异常。 9 }10 ts1.Complete();11 }12 }
第二种实现思路:前验证+不用数据库索引,在插入用户名和修改用户名之前执行一次验证,整个事务运行在“串行化”隔离级别。
1 public volid Insert(User user)2 {3 using(var ts = new TransactionScope("串行化"))4 { 5 //如果违背约束,抛出异常。6 DoInsert(user);7 ts.Complete();8 }9 }
第三种实现思路:前验证+数据库索引,在插入用户名和修改用户名之前执行一次验证,整个事务运行在“读已提交”隔离级别。
1 public volid Insert(User user)2 {3 using(var ts = new TransactionScope("读已提交"))4 { 5 //如果违背约束,抛出异常。6 DoInsert(user);7 ts.Complete();8 }9 }
第四实现思路种:内存锁。
有朋友会想,为啥不直接用数据库索引呢?失败了就跑出异常,因为我们需要收集到友好的异常信息显示给UI,所以才需要在程序里判定唯一性,然后抛出友好的异常信息。
总体来说我觉得第三种思路在现实中比较方便。
第二种场景:聚合内某个实体的属性的唯一性约束
示例:订单的订单项的产品必须唯一。
第一种实现思路:聚合根的乐观锁+聚合自身必须保证这种约束。
我觉得这个场景下只有这一种实现是比较合理的,就不介绍其他思路了。
备注
在健身房仓促写就,大家多提意见。