关于Spring JpaRepository方法的线程安全性

时间:2016-05-24 12:33:34

标签: java multithreading hibernate spring-data spring-data-jpa

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

以下是参考代码: -

@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 [不适用];约束[“PRUBLARY KEY ON PUBLIC.TBLDYNAMICFORM(FORMID)”; SQL语句: 插入到Tbldynamicform(clientid,copyfromexisting,creationdate,formdesc,formmode,formname,formtemplate,formtitle,procutype,status,formid)值(?,?,?,?,...

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

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

专家,请帮忙吗?

3 个答案:

答案 0 :(得分:3)

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

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

maxId是线程安全的,保存是线程安全的,但是你在每个线程的runnable中的方法都不是线程安全的。

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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

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

已生成一些已生成ID的集成解决方案,除非您知道自己的操作,否则不应尝试自己实施。