我有一张桌子products
。在此表中,我需要约束的is_active
-only one row with the same type can be true.
我可以通过检查来保存新的Product
:
@Service
public class ProductServiceImpl implements ProductService {
private final ProductRepository productRepository;
public ProductServiceImpl(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Override
public void save(Product product) {
Product productInDb = productRepository.findOneByTypeAndIsActive(product.getType());
if (productInDb != null)
throw new AlreadyActiveException();
product.setActive(true);
productRepository.saveAndFlush(product);
}
}
当我在几个线程中调用save
方法并尝试检查有效产品时-在两个线程中,findOneByTypeAndIsActive
方法都返回productInDb is null
,因为我在表中没有活动产品。
在每个线程中,我设置product.setActive(true);
并尝试保存到DB中。
如果我在数据库中没有约束-我将两个产品都保存为is_active = true
状态,并且不执行此检查:
if (productInDb != null)
throw new AlreadyActiveException();
我的问题-我可以在不增加数据库约束的情况下解决此问题吗? 上面的检查没用吗?
答案 0 :(得分:1)
您的操作包含2个操作:
从数据库获取实体
保存不存在的新实体
您的问题是很少有线程可以同时启动此操作,并且看不到彼此之间的变化。绝对不是您所需要的。您的操作由几个动作组成,必须是原子的。
如果我理解正确,则您有一条规则,即在数据存储区中仅保留同一active
的1个type
乘积。听起来像是数据一致性要求,应该在应用程序级别上解决。
解决问题的最幼稚的选择是在执行操作之前获取一个锁。可以使用synchronised
或显式锁定来解决:
@Override
public synchronised void save(Product product) {
Product productInDb = productRepository.findOneByTypeAndIsActive(product.getType());
if (productInDb != null)
throw new AlreadyActiveException();
product.setActive(true);
productRepository.saveAndFlush(product);
}
答案 1 :(得分:1)
从我的角度来看,这不是最好的数据库表设计,在记录结构中同时具有is_active
标志和限制,同时只能在表中有一个记录is_active
。 / p>
您必须使用数据库架构约束,或者必须用所有记录锁定整个表。如何锁定整个表以进行修改是特定于数据库的。我认为JPA本身不支持这种锁。
但是您写道:
我可以在不增加数据库约束的情况下解决此问题吗?
不,不可能为所有客户提供严格的保证。
但是,如果只有一个使用此表的应用程序-您可以使用应用程序专用的本地锁,例如,可以在@Service级别创建Read/Write Java锁。