关于 Spring JpaRepository 方法线程安全

Posted

技术标签:

【中文标题】关于 Spring JpaRepository 方法线程安全【英文标题】:Regarding Spring JpaRepository method thread safety 【发布时间】:2016-05-24 12:33:33 【问题描述】:

我很好奇 spring jparepository 方法是否是线程安全的,然后我阅读了 stackflow 文章 (Is a Spring Data (JPA) Repository thread-safe? (aka is SimpleJpaRepository thread safe))。从那里,我了解到存储库方法是线程安全的,然后我制作了一个 POC 来测试线程安全性。我创建了一个存储库,说 FormRepository 为“表单”实体执行 CRUD 操作,即扩展 JpaRepository。从 DAO 中,我简单地调用了 100 个线程来创建表单对象并手动设置其 ID,然后保存“表单”对象。

以下是参考代码:-

@Repository
public interface FormRepository extends JpaRepository<Tbldynamicform, Long>     

Tbldynamicform save(Tbldynamicform tblform);

@Query("SELECT max(tblform.formid) FROM Tbldynamicform tblform")
Optional<Integer> findMaxId();


......End of Repository above and start of DAO below...

@Component
public class DynamicFormDAO implements DynamicFormDAO 

@Inject
private FormRepository formRepository;

public void testThreadSafety() throws Exception 
    List<Callable<Integer>> tasks = new ArrayList<>(100);
    for (int i = 0; i < 100; i++) 

        tasks.add(() -> 
            try 

                Tbldynamicform tbldynamicform = new Tbldynamicform();//Set  all the required fields for form
                if (tbldynamicform.getFormid() == null)
                    tbldynamicform.setFormid(findFormID());
                Tbldynamicform form = formRepository.save(tbldynamicform);
                return form.getFormid();
             catch (Exception e) 
                e.printStackTrace();
            
            return null;
        );
    
    ExecutorService executor = Executors.newFixedThreadPool(100);
    executor.invokeAll(tasks);



private int findFormID() throws Exception 
    Optional<Integer> id = formRepository.findMaxId();
    if (id != null && id.isPresent() && id.get() != null) 
        int generatedId = id.get().intValue();
        return ++generatedId;
    
    return 0;


当我这样做时,我假设事情必须正常工作,因为表单存储库方法是线程安全的,但不知何故,我在日志中多次收到 sql dataintegrityviolationexception,导致插入多条记录失败。以下错误供参考:-

org.springframework.dao.DataIntegrityViolationException:无法执行语句; SQL [不适用];约束 ["PRIMARY KEY ON PUBLIC.TBLDYNAMICFORM(FORMID)"; SQL 语句: 插入 Tbldynamicform(clientid、copyfromexisting、creationdate、formdesc、formmode、formname、formtemplate、formtitle、procutype、status、formid)值(?、?、?、?、...

这让我想到这是线程安全问题还是其他问题?据我了解,我在 dao 中创建的所有“tbldynamicform”对象都将保留在线程堆栈中。只有 formRepository 将在堆存储中,如果 formrepository 方法是线程安全的,则必须在数据库中插入 100 条记录,没有任何问题。

如果我执行 setId 并保存在同步块中,一切正常,但这不是我的意图,如果存储库方法是线程安全的,则不需要。

专家,有什么帮助吗?

【问题讨论】:

【参考方案1】:

您的保存任务不是原子的 - 两个线程可能会在其中一个线程保存新实体之前获取相同的最大 id。

然后,即使存储库的保存方法是线程安全的,它也无济于事。

maxId是线程安全的,save是线程安全的,但是你在每个线程的runnable里面的方法不是线程安全的。

【讨论】:

是的,我也有同样的想法,因为我也提出了我的问题,如果我将 save 和 findformId 放在同步块中,一切正常..【参考方案2】:

简单地说,是的,它是线程安全的,但是您的数据库也是有状态的(显然),为了保持完整性,您可能需要锁定策略(保持锁定以使事情同步,或使用乐观策略并重试)需要时)。正如有人在另一个答案中指出的那样,如果您只是使用不同的方法来生成 ID(查看 SUID),那么您的代码就可以正常工作。

【讨论】:

【参考方案3】:

问题在于您如何使用 findFormID() 检索最后一个 ID,它在并发上下文中不起作用。

如果两个线程同时询问一个 ID 怎么办?他们将检索相同的 ID 并创建两个具有相同 ID 的对象。这是你的问题。

已经存在一些用于生成 ID 的集成解决方案,除非您知道自己在做什么,否则不应尝试实施自己的解决方案。

【讨论】:

"如果两个线程同时询问一个ID怎么办" 因为findFormID是同步的,所以只有一个线程可以得到这个id。但问题是 save 和 setformid 不是原子的。

以上是关于关于 Spring JpaRepository 方法线程安全的主要内容,如果未能解决你的问题,请参考以下文章

spring使用 hibernate jpa JpaRepository

Spring JpaRepository - 分离和附加实体

Spring JPA no @Transnational 保存 JpaRepository

Spring Data JPA - JpaRepository 中的自定义排序

将 JpaRepository 与 Spring 数据和 Hibernate 一起使用

如何在 Spring 中将 OrderBy 与 JPARepository 一起使用