java - 当与 Spring Data JPA 结合使用时,事务注释并没有使我的代码变得原子。请建议

标签 java spring-boot spring-data-jpa

我在 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/

相关文章:

java - 从父实体中删除时,Spring data JPA @PreRemove ConcurrentModificationException

java - 我们可以在 Drools 中动态设置事实类型和事实字段吗

java - 如何折叠搜索结果中的重复项

java - spring springdoc-openapi : swagger-ui/index. 找不到html status=404

java - 我无法将 css 文件包含到我的 Spring BOOT

java - 如何编写具有多个连接的 Spring Data JPA 规范?

Spring Data Jpa无法创建存储库bean

正在释放的 JAVA 指针未分配

java - 当子类对象被指定为父类(super class)对象时会发生什么

java - ONVIF wsdl 文件