[英]Transactional Annotation is not making my code atomic when used in conjunction with Spring Data JPA. Please suggest
I'm using Spring Data Jpa in spring boot and I'm using multiple datasources in my project(not sure if this is useful info).我在 Spring Boot 中使用 Spring Data Jpa,并且在我的项目中使用了多个数据源(不确定这是否是有用的信息)。 I have a service method which has to insert data into 2 entities.我有一个服务方法,它必须将数据插入到 2 个实体中。 The requirement is that, this method has to be atomic.要求是,这个方法必须是原子的。 It has to insert into both entities or fail for both of them.它必须插入到两个实体中,否则两者都将失败。 However in my case the insert into first entity is not being rolled back when there is an error inserting into second entity.但是,在我的情况下,当插入第二个实体时出错时,不会回滚插入到第一个实体中。 Please help!!请帮忙!!
Note: Cannot add the entire code as there is business logic and I'm not allowed to paste it in entirety.注意:由于存在业务逻辑,因此无法添加整个代码,并且不允许将其完整粘贴。 But if there is any important piece of code needed for understanding the question.但是如果有任何重要的代码需要理解这个问题。 Please suggest请建议
My Repositories我的仓库
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);
}
My Service我的服务
@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);
}
}
My Tests我的测试
@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));
}
}
My Main Class我的主班
@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);
}
}
When I'm trying to run the test case it's failing as the first record is not getting rolled back当我尝试运行测试用例时,它失败了,因为第一条记录没有回滚
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)
The following is what is printed in logs以下是日志中打印的内容
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 :
Instead of Declarative approach try Programmatic appraoach而不是声明式方法尝试程序化方法
Remove @Transactional
from method where you want to perform the operations that are atomic.从要执行原子操作的方法中删除@Transactional
。
then update your service method like this:然后像这样更新您的服务方法:
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;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.