我在 Spring Boot 中使用 Spring Data Jpa,并且在我的项目中使用多个数据源(不确定这是否有用)。我有一个服务方法,必须将数据插入到 2 个实体中。要求是,这个方法必须是原子的。它必须插入两个实体,否则两个实体都会失败。但是,在我的情况下,当插入第二个实体时出现错误时,插入第一个实体不会回滚。请帮忙!!
注意:无法添加整个代码,因为存在业务逻辑,并且不允许我完整粘贴它。但是如果需要任何重要的代码来理解这个问题。请提出建议
我的存储库
public interface ReturnEntriesHistoryRepo extends PagingAndSortingRepository<ReturnEntryHistory, String>{
Optional<ReturnEntryHistory> findById(String id);
@Transactional(propagation= Propagation.REQUIRED)
@Override
public <S extends ReturnEntryHistory> Iterable<S> saveAll(Iterable<S> entities);
}
public interface ReturnHistoryRepo extends PagingAndSortingRepository<ReturnHistory, String>
{
Optional<ReturnHistory> findById(String id);
@Transactional(propagation= Propagation.REQUIRED)
@Override
public <S extends ReturnHistory> S save(S s);
}
我的服务
@Service
public class ReturnsService
{
@Autowired
LaneHelper laneHelper;
// Method which is supposed to be executed as transaction
@Transactional(rollbackFor= {Exception.class, RuntimeException.class,},propagation = Propagation.MANDATORY)
public void captureCurrentSnapshotFor(ReturnDao dao, Long storeId)
{
if(dao == null || dao.getReturnOrder() == null || CollectionUtils.isEmpty(dao.getReturnEntries()) || storeId == null)
{
throw new RuntimeException("Either invalid Dao or Store is Null when trying to captue current snapshot of the Return");
}
// Fetching the repositories
// The following lines tries to identify the data source whose repository to write to
ReturnHistoryRepo returnHistoryRepo = laneHelper.getReturnHistoryRepository(storeId);
ReturnEntriesHistoryRepo returnEntryHistoryRepo = laneHelper.getReturnEntriesHistoryRepository(storeId);
// Constructing History Records
ReturnHistory returnHistory = convertReturnToReturnHistory(dao.getReturnOrder());
List<ReturnEntryHistory> returnEntriesHistory = dao.getReturnEntries().stream().map(entry -> convertReturnEntryToReturnEntryHistory(entry)).collect(Collectors.toList());
// Persisting History Records
returnHistoryRepo.save(returnHistory);
returnEntryHistoryRepo.saveAll(returnEntriesHistory);
}
}
我的测试
@Component
public class TestsHelper
{
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Transactional(rollbackFor= {Exception.class, RuntimeException.class})
public void runCaptureCurrentSnapshotForAtomicityNegativeCase(String returnOrderUuid, Long storeId, ReturnsService returnsService )
{
// Creating Return Order
Returns returnOrder = new Returns();
returnOrder.setId(returnOrderUuid);
returnOrder.setReturnId("FC-12345");
returnOrder.setFcOrderId("FC-ORD-12345");
returnOrder.setOrderId(Long.valueOf("12345"));
returnOrder.setRefundAmount(Double.valueOf("500"));
returnOrder.setShippingRefundAmount(Double.valueOf("0"));
returnOrder.setStoreId(storeId);
returnOrder.setStatus(Long.valueOf("12"));
returnOrder.setIsFullOrderReturn("FALSE");
returnOrder.setCreatedBy("TEST_CASE");
returnOrder.setCreatedDate(new Date());
// Creating Return Entries
List<ReturnEntry> returnEntries = new ArrayList<ReturnEntry>();
// Entry-1
ReturnEntry entry = new ReturnEntry();
entry.setId("TEST-123451");
entry.setReturnId(returnOrderUuid);
entry.setStoreId(storeId);
entry.setLineNumber(1);
entry.setProductId(Long.valueOf("12345"));
entry.setProductSkuId(Long.valueOf("12345"));
entry.setPrimaryReason("TEST-PRIMARY-REASON");
entry.setSecondaryReason("TEST-SECONDARY-REASON");
entry.setMrp(Double.valueOf("300"));
entry.setRefundAmount(Double.valueOf("300"));
entry.setStatus(Long.valueOf("12"));
entry.setExpectedQuantity(Double.valueOf("1"));
entry.setUnitOfMeasure("pieces");
entry.setCreatedBy("TEST_CASE");
entry.setCreatedDate(new Date());
// Entry-2
// Creating The second one with NULL id to ensure it fails
ReturnEntry entry1 = new ReturnEntry();
// entry1.setId("TEST-123451");
entry1.setReturnId(returnOrderUuid);
entry1.setStoreId(storeId);
entry1.setLineNumber(1);
entry1.setProductId(Long.valueOf("12345"));
entry1.setProductSkuId(Long.valueOf("12345"));
entry1.setPrimaryReason("TEST-PRIMARY-REASON");
entry1.setSecondaryReason("TEST-SECONDARY-REASON");
entry1.setMrp(Double.valueOf("300"));
entry1.setRefundAmount(Double.valueOf("300"));
entry1.setStatus(Long.valueOf("12"));
entry1.setExpectedQuantity(Double.valueOf("1"));
entry1.setUnitOfMeasure("pieces");
entry1.setCreatedBy("TEST_CASE");
entry1.setCreatedDate(new Date());
returnEntries.add(entry);
returnEntries.add(entry1);
ReturnDao dao = ReturnDao.builder().returnOrder(returnOrder).returnEntries(returnEntries).build();
log.info("Capturing Snapshot:start");
returnsService.captureCurrentSnapshotFor(dao, storeId);
log.info("Capturing Snapshot:end ");
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ReturnsServiceTest
{
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Autowired
ReturnsService returnsService;
@Autowired
MerchantLaneHelper merchantLaneHelper;
@Autowired
public TestsHelper testsHelper;
Long storeId;
String returnOrderUuid;
ReturnHistoryRepo returnHistoryRepo;
@Test
public void testCaptureCurrentSnapshotForAtomicityNegativeCase()
{
try
{
testsHelper.runCaptureCurrentSnapshotForAtomicityNegativeCase(returnOrderUuid, storeId, returnsService);
}
catch(Exception e)
{
log.error(e.getMessage());
}
List<ReturnHistory> persistedReturns = returnHistoryRepo.findByReturnId(returnOrderUuid);
assertTrue(CollectionUtils.isEmpty(persistedReturns));
}
}
我的主课
@SpringBootApplication
@ComponentScan(basePackages = { "<base package>"})
@EnableScheduling
@EnableAsync
@EnableWebMvc
@EnableKafka
@EnableCaching
@EnableRetry
@EnableJms
@EnableAutoConfiguration
@EnableTransactionManagement
//@EnableJpaRepositories(basePackages="<repo.package>")
public class MainApplication{
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
当我尝试运行测试用例时,它失败了,因为第一条记录没有回滚
java.lang.AssertionError
at org.junit.Assert.fail(Assert.java:86)
at org.junit.Assert.assertTrue(Assert.java:41)
at org.junit.Assert.assertTrue(Assert.java:52)
at com.ril.jio.integration.tests.returns.ReturnsServiceTest.testCaptureCurrentSnapshotForAtomicityNegativeCase(ReturnsServiceTest.java:294)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
以下是日志中打印的内容
2019-12-13 11:08:33.322 INFO 19233 --- [ main] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
2019-12-13 11:08:33.960 WARN 19233 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1400, SQLState: 23000
2019-12-13 11:08:33.961 ERROR 19233 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : ORA-01400: cannot insert NULL into ("RETURNENTRIES_HISTORY"."UUID")
2019-12-13 11:08:33.964 ERROR 19233 --- [ main] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement]
2019-12-13 11:08:33.998 ERROR 19233 --- [ main] c.r.j.i.t.returns.ReturnsServiceTest : DataIntegrityViolationException
2019-12-13 11:08:33.998 ERROR 19233 --- [ main] c.r.j.i.t.returns.ReturnsServiceTest : could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
2019-12-13 11:08:34.023 INFO 19233 --- [ main] c.r.j.i.t.returns.ReturnsServiceTest :
最佳答案
尝试程序化方法,而不是声明式方法
从要执行原子操作的方法中删除@Transactional
。
然后像这样更新您的服务方法:
import org.springframework.transaction.interceptor.TransactionAspectSupport;
public int aBC(XYZ xyz) {
try {
//transaction 1
//transaction 2
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return -1;
}
return 1;
}
关于java - 当与 Spring Data JPA 结合使用时,事务注释并没有使我的代码变得原子。请建议,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59317812/