【发布时间】:2020-06-16 11:30:01
【问题描述】:
我遇到了 Spring Data JPA 插入问题。我有一个一周后运行的调度程序。它根据特定条件从源表中选择 n 条记录,然后检查目标表 x 是否存在。如果不是,则创建表并插入所选数据。成功插入目标表的数据从源表中删除。
@Component
public class ArchiveService {
// Some dependencies here!!!
@Transactional
public int archiveSmsEntries(Month month, Year year) {
// ...
this.createNewTable(suffix);
// ...
// LocalDateTime start;
// LocalDateTime end;
int size = 200;
boolean running = true;
while (running) {
Page<SmsEntry> entries = smsService.findAllForArchiving(start, end, PageRequest.of(0, size, Sort.Direction.ASC, "created"));
result += this.saveAndDelete(entries.getContent());
running = entries.getTotalElements() > 0;
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createNewTable(String suffix) {
if (repo.tableExists(ArchiverUtils.table + suffix)) {
log.warn("Archive already exists.");
} else {
Session session = em.unwrap(Session.class);
session.doWork(connection -> {
//// ...
// Create table
//// ...
});
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int saveAndDelete(List<SmsEntry> entries) {
if (entries.isEmpty())
return 0;
AtomicInteger r = new AtomicInteger(0);
try {
Session session = em.unwrap(Session.class);
session.doWork(connection -> {
boolean autoCommit = connection.getAutoCommit();
connection.setAutoCommit(true);
PreparedStatement ps = connection.prepareStatement("INSERT STATEMENT");
for (SmsEntry entry : entries) {
ps.setInt(1, entry.getId());
ps.setDouble(2, entry.getCost());
ps.addBatch();
}
r.set(Arrays.stream(ps.executeBatch()).sum());
if (r.get() > 0) {
entryService.deleteAllById(entries.stream().map(SmsEntry::getId).collect(Collectors.toList()));
}
ps.close();
connection.setAutoCommit(autoCommit);
});
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return r.get();
}
}
上面调用时立即运行良好,但过一会就抛出这个异常:
org.springframework.dao.InvalidDataAccessApiUsageException: No transactional EntityManager available; nested exception is java.lang.IllegalStateException: No transactional EntityManager available
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:371)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:257)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
...
所以我真的不知道为什么会抛出这个问题以及如何解决它。我在网上看到过类似的问题,例如Spring - PersistenceContext - No transactional EntityManager available 和No transactional EntityManager available Error。我的主要任务是获取 200 条记录,立即操作和保存(立即提交到数据库)。 我将非常感谢任何帮助。谢谢。
【问题讨论】:
-
有一点你没有考虑到,spring 代理。与您的情况一样,大多数 likley CGlib 都在做您的对象代理,因为您没有基于接口声明您的服务。您正在使用事务性方法,但事务性方法只有在它是公共的并且调用是在类的“外部”进行时才会成为事务性方法。为什么?因为 spring 代理了你的类,它调用你的方法,但是如果你在事务内部有方法调用,spring 不能知道该方法是否真的是事务性的。在另一个类中编写创建表方法,然后重试。
-
感谢您的回复。我刚刚实现了您上面提到的内容。我将 saveAndDelete() 和 createNewTable() 方法提取到一个新组件中。我会进行测试,如果我会分享结果。谢谢。
-
嗨@Guardian 我想通知你它有效。谢谢。
标签: java spring spring-boot hibernate spring-data-jpa