简体   繁体   English

与 Spring Data JPA 结合使用时,事务注释不会使我的代码原子化。 请建议

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM