关于Spring JpaRepository方法线程安全
Regarding Spring JpaRepository method thread safety
我很好奇 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 [n/a];约束["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 并保存在同步块中,一切正常,但这不是我的意图,如果存储库方法是线程安全的,则不需要。
专家们,有什么帮助吗?
您的保存任务不是原子性的 - 两个线程可能会在其中一个线程保存新实体之前获取相同的最大 ID。
然后,即使存储库的保存方法是线程安全的,也无济于事。
maxId是线程安全的,save是线程安全的,但是你在每个线程的runnable里面的方法不是线程安全的。
问题出在如何使用 findFormID() 检索最后一个 ID,它在并发上下文中不起作用。
如果两个线程同时请求一个 ID 怎么办?他们将检索相同的 ID 并创建两个具有相同 ID 的对象。这是你的问题。
一些用于生成 ID 的集成解决方案已经存在,除非您知道自己在做什么,否则不应尝试实施自己的解决方案。
简而言之,是的,它是线程安全的,但您的数据库也是有状态的(显然),为了保持完整性,您可能需要诸如锁定策略之类的东西(持有锁以使事情同步,或使用乐观策略并在需要时重试)。正如有人在另一个答案中指出的那样,如果您只是使用不同的方法生成 ID(查看 SUID),您的代码就可以正常工作。
我很好奇 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 [n/a];约束["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 并保存在同步块中,一切正常,但这不是我的意图,如果存储库方法是线程安全的,则不需要。
专家们,有什么帮助吗?
您的保存任务不是原子性的 - 两个线程可能会在其中一个线程保存新实体之前获取相同的最大 ID。
然后,即使存储库的保存方法是线程安全的,也无济于事。
maxId是线程安全的,save是线程安全的,但是你在每个线程的runnable里面的方法不是线程安全的。
问题出在如何使用 findFormID() 检索最后一个 ID,它在并发上下文中不起作用。
如果两个线程同时请求一个 ID 怎么办?他们将检索相同的 ID 并创建两个具有相同 ID 的对象。这是你的问题。
一些用于生成 ID 的集成解决方案已经存在,除非您知道自己在做什么,否则不应尝试实施自己的解决方案。
简而言之,是的,它是线程安全的,但您的数据库也是有状态的(显然),为了保持完整性,您可能需要诸如锁定策略之类的东西(持有锁以使事情同步,或使用乐观策略并在需要时重试)。正如有人在另一个答案中指出的那样,如果您只是使用不同的方法生成 ID(查看 SUID),您的代码就可以正常工作。