Why does a new thread not see test data prepared by main thread in a transactional Spring JUnit test?

surviorzZZ :

I wrote a Junit test with The Spring-boot-test, in a test method, i firstly prepared some test data which should be saved to the MySQL DB, then i invoked the target method which should be tested in 100 sub-threads to test whether target method works well in concurrency . This test method looks like this:

public class SysCodeRuleServiceImplTest extends BaseServiceTest {

    @Autowired
    private SysCodeRuleService sysCodeRuleService;

    @Autowired
    private SysCodeRuleDtlService sysCodeRuleDtlService;

    private final String codeRuleNo = "sdkfjks443";

        @Test
    public void testCreateSheetIdWithoutUniformedSerial_2() throws InterruptedException {
                //------ prepare test data start-----------
        SysCodeRule sysCodeRule = new SysCodeRule();
        sysCodeRule.setCodeRuleNo(codeRuleNo);
        sysCodeRule.setIfDateCode(1);
        sysCodeRule.setPadChar("0");
        sysCodeRule.setSerialDigits(6);
        sysCodeRule.setResetMode(1);
        sysCodeRule.setIfUniteSerial(0);
        sysCodeRule.setIfCache(0);
        sysCodeRule.setConstValue("PETREL");
        sysCodeRule.setStatus(1);
        sysCodeRule.setName(codeRuleNo);
        sysCodeRule.setCurSerialNo("0");
        sysCodeRule.setCurSerialDate(new Date());
        sysCodeRule.setCreateTime(new Date());
        sysCodeRule.setCreator("自动");
        sysCodeRule.setDateCutBeginPosition(3);
        sysCodeRule.setDateCutEndPosition(8);
        boolean insertSysCodeRuleSucc = sysCodeRuleService.insert(sysCodeRule);
        assertThat(TestMessageConstants.PREPARE_TEST_DATA_FAILED, insertSysCodeRuleSucc);
        assertThat("", sysCodeRule.getId(), notNullValue());

        SysCodeRuleDtl sysCodeRuleDtl1 = new SysCodeRuleDtl();
        sysCodeRuleDtl1.setSysCodeId(sysCodeRule.getId() + "");
        sysCodeRuleDtl1.setOrderNo(1);
        sysCodeRuleDtl1.setFieldValue("locno");
        sysCodeRuleDtl1.setCutEndPosition(0);
        sysCodeRuleDtl1.setCutBeginPosition(0);
        sysCodeRuleDtl1.setCreateTime(new Date());
        sysCodeRuleDtl1.setCreator("自动");
        boolean insertDtl1Succ = sysCodeRuleDtlService.insert(sysCodeRuleDtl1);
        assertThat("", insertDtl1Succ);

        SysCodeRuleDtl sysCodeRuleDtl2 = new SysCodeRuleDtl();
        sysCodeRuleDtl2.setSysCodeId(sysCodeRule.getId() + "");
        sysCodeRuleDtl2.setOrderNo(2);
        sysCodeRuleDtl2.setFieldValue("fieldName1");
        sysCodeRuleDtl2.setCutBeginPosition(1);
        sysCodeRuleDtl2.setCutEndPosition(3);
        sysCodeRuleDtl2.setCreateTime(new Date());
        sysCodeRuleDtl2.setCreator("自动");
        boolean insertDtl1Succ2 = sysCodeRuleDtlService.insert(sysCodeRuleDtl2);
        assertThat("", insertDtl1Succ2);
                //------prepare test data end------------------------

                //startLatch used to make sure all task threads start after 
                //prepared test data done
        CountDownLatch startLatch = new CountDownLatch(1);
                //parameters needed by the target method
        Map<String, String> fieldValueMap = new HashMap<>(2);
        fieldValueMap.put("locno", "cangku1");
        fieldValueMap.put("fieldName1", "ABCDEFGH");
                //doneLatch used to make sure all task threads done before the
                //the transaction which started in main thread roll back
        CountDownLatch doneLatch = new CountDownLatch(100);
        for(int i = 0; i < 100; i++) {
            new Thread(() -> {
                try {
                    startLatch.await();
                //this is the target method which i want to test
                    String result = sysCodeRuleService.createSheetIdWithoutUniformedSerial(codeRuleNo, JsonUtils.writeValueAsString(fieldValueMap));
                    if(CommonUtil.isNotNull(result)) {
                        logger.debug(">>>>>>>>>>>>>" + result);
                    }
                } catch(InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    doneLatch.countDown();
                }
            }).start();
        }

                //guarantee the test data truly saved before all task treads 
                //start
        EntityWrapper<SysCodeRule> ew = new EntityWrapper<>();
        ew.eq("code_rule_no", codeRuleNo);
        SysCodeRule codeRule = sysCodeRuleService.selectOne(ew);
        if(codeRule != null) {
            startLatch.countDown();
        }
                //main thread keep waiting until all task threads done their 
                //work
        doneLatch.await();
    }

The BaseServiceTest looks like this :

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
@Transactional
@Rollback
public class BaseServiceTest {
    protected  Logger logger = LoggerFactory.getLogger(BaseServiceTest.class);
}

And the target method's signature looks like this:

public synchronized String createSheetIdWithoutUniformedSerial(String codeRuleNo, String fieldValuesJson)

in the target method, it query the data which saved by the "prepare test data" codes block then do some business logic codes then return the result. By the way, the target method which wrote in the "Business Service Layer" has transaction managed by Spring AOP, and the transaction config file looks like this:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="doReweight" propagation="REQUIRES_NEW"/>
            <tx:method name="doClear*" propagation="REQUIRES_NEW"/>
            <tx:method name="doSend*" propagation="REQUIRES_NEW"/>
            <tx:method name="doBatchSave*" propagation="REQUIRES_NEW"/>

            <tx:method name="get*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="count*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="find*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="list*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <aop:config expose-proxy="true" proxy-target-class="true">
        <aop:pointcut id="txPointcut"
                      expression="execution(* com.xxx..service..*+.*(..))"/>
        <aop:advisor id="txAdvisor" advice-ref="txAdvice"
                     pointcut-ref="txPointcut"/>
    </aop:config>
</beans>

My expected result is the syncronized target method works well

But! In each sub task thread, the target method can not query out the prepared test data which prepared in the main thread!

So, i was confused, and can not figure it out what was wrong?Need some help or tips, I really appreciate some help from you guys, thanks in advance!

ps: the spring-boot verison is:1.5.10.RELEASE, the Juite Version is:4.12

Sam Brannen :

The initial interaction with the database via your SysCodeRuleService inserts test data within the test-managed transaction which is not committed to the database.

The invocation of createSheetIdWithoutUniformedSerial() on your SysCodeRuleService then executes in a new thread; however, Spring does not propagate transactions to newly spawned threads. Thus, the invocation of createSheetIdWithoutUniformedSerial() runs in a different thread which cannot see the uncommitted test data in the suspended test-managed transaction.

In order to allow createSheetIdWithoutUniformedSerial() to see such test data in the database in a new thread, you will have to commit the test data to the database before spawning any new threads.

There are several options for achieving this.

If you're looking for a very low-level technique, you can use Spring's TransactionTemplate to programmatically commit to the database. This should even work with a test-managed transaction in place (i.e., via @Transactional on the test class or test method).

If you would like to perform database setup specific to the current @Test method, another option is to use the TestTransaction API. See Programmatic Transaction Management in the Spring Framework Reference Manual for details.

If you want to perform the same database setup for all test methods in the current class, you can introduce a @BeforeTransaction method that inserts the test data into the database and an @AfterTransaction method that deletes the test data from the database. See Running Code Outside of a Transaction.

If you are willing to or interested in moving your test data setup to SQL insert statements (potentially in an external file), you can use Spring's @Sql support.

As a side note, you can safely remove the @Rollback declaration since you are effectively overriding the default semantics with the default semantics.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=90447&siteId=1