hbwf / mybatis

Automatically exported from code.google.com/p/mybatis
0 stars 0 forks source link

Mybatis-spring "Cannot change the ExecutorType when there is an existing transaction" #550

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
What version of the MyBatis are you using?
Mybatis:3.0.5
mybatis-spring:1.0.1

Please describe the problem.  Unit tests are best!

        I had a problem when I used Mybatis integrated with  spring.
        I had two dao functions, and they used two different ExecutorType,one was Simple and  another was  Batch.I called these two dao function in a service function ,beacuse I want the two dao function run in one transaction. but when I did some test,it didn't work.It caused a exception "Cannot change the ExecutorType when there is an existing transaction".

These are my codes:
-----Dao------

@Repository("helloDao")
public class HelloDao  extends SqlSessionDaoSupport implements IHelloDao{
    private SqlSession getBatchSqlSession(){
        SqlSessionFactory sqlSessionFactory = ((SqlSessionTemplate)getSqlSession()).getSqlSessionFactory();
        return new SqlSessionTemplate(sqlSessionFactory,ExecutorType.BATCH);
    }

    @Override
    public void insert() {
        long start = System.currentTimeMillis();
        for(int i=0;i<1;i++)
            getSqlSession().insert("HELLO.insert",i);
        System.out.println("simple:"+(System.currentTimeMillis()-start));
    }

    @Override
    public void batchInsert() {
        long start = System.currentTimeMillis();
        for(int i=0;i<1000;i++)
            getBatchSqlSession().insert("HELLO.insert",i);
        System.out.println("batch:"+(System.currentTimeMillis()-start));
    }
}

------Service-------
@Service("helloService")
@Transactional
public class HelloService implements IHelloService {
    @Resource(name="helloDao")
    private IHelloDao helloDao;
    @Override
    public void multiInsert() {
        helloDao.batchInsert();
        helloDao.insert();
        System.out.println("hello");
    }
}

---------TestCase---------

public class BatchTestCase {
    private IHelloService helloService;
    @Before
    public void init(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        helloService = (IHelloService) context.getBean("helloService");
    }

    @Test
    public void testMultiInsert(){
        helloService.multiInsert();
    }
}

What is the expected output? What do you see instead?

I expected  It could run normal  using two different ExecutorType and It can do 
a change of executorType if necessary. but I see the exception below!

Can you provide stack trace, logs, error messages that are displayed?

org.springframework.dao.TransientDataAccessResourceException: Cannot change the 
ExecutorType when there is an existing transaction
    at org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionUtils.java:96)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:333)
    at $Proxy9.insert(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:231)
    at com.deppon.foss.batch.server.dao.impl.HelloDao.insert(HelloDao.java:22)
    at com.deppon.foss.batch.server.service.impl.HelloService.multiInsert(HelloService.java:18)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at $Proxy14.multiInsert(Unknown Source)
    at com.deppon.foss.batch.test.Main.testMultiInsert(Main.java:19)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

Please provide any additional information below.

        After the source code,I realized that I  had found the reason. The key of TransactionSynchronizationManager'resource is sessionFactory,and It use this key to bind SqlSessionHolder  , what's more ,the sessionFactory is a singleton bean . when diffent dao functions are executing,the same sessionFactory is used. of course,It will return the same SqlSessionHolder  and the same SqlSession.
        I have a doubt why can't we set the key of TransactionSynchronizationManager'resource as "sessionFactory+ExecutorType". so,I had did some changes on the source codes where the attachments are. and I also had  did some test.
        Could we did it as the attachments,if not ,tell me the reason please!

Original issue reported on code.google.com by xingba...@gmail.com on 20 Mar 2012 at 3:25

Attachments:

GoogleCodeExporter commented 9 years ago
Issue 549 has been merged into this issue.

Original comment by eduardo.macarron on 20 Mar 2012 at 4:53

GoogleCodeExporter commented 9 years ago

Original comment by eduardo.macarron on 20 Mar 2012 at 4:53

GoogleCodeExporter commented 9 years ago
Looks really good!. I will discuss this change with the other devs to see what 
they think.

Refreshed patch. Not all test pass but will have a deeper look if the change is 
accepted.

Original comment by eduardo.macarron on 20 Mar 2012 at 5:52

Attachments:

GoogleCodeExporter commented 9 years ago
After a discussion we came out with this conclusion.

This fix introduces a bigger problem than it solves because there is a problem 
with the order.

For example:
batch executor: insert data x
simple executor: select data x -> will fail because batch executor is not still 
flushed

Using just one executor will work because select will flush pending statements 
but when using more than one that is not possible. 

So I am afraid this reason is enough not to fix that. 

Thank you anyway for your hard work and the patch. We do really appreciate this 
kind of contributions!

Original comment by eduardo.macarron on 21 Mar 2012 at 5:39

GoogleCodeExporter commented 9 years ago
updated patch

Original comment by eduardo.macarron on 23 Mar 2012 at 6:44

Attachments:

GoogleCodeExporter commented 9 years ago

Original comment by eduardo.macarron on 12 Apr 2012 at 7:54

GoogleCodeExporter commented 9 years ago
Hi you changed the status of this issue, do you plan to allow different 
executor types inside a transaction ?

Original comment by brice.du...@gmail.com on 17 Dec 2012 at 11:32

GoogleCodeExporter commented 9 years ago
There was no agreement in the dev team so I just left the issue in its initial 
status.

Original comment by eduardo.macarron on 17 Dec 2012 at 1:17

GoogleCodeExporter commented 9 years ago
OK, that's fair

So for the record for anyone that stumble across this exception, as an 
alternative, you can configure your context or your mappers with the SQL 
template that uses the BATCh executor.

Original comment by brice.du...@gmail.com on 17 Dec 2012 at 1:25

GoogleCodeExporter commented 9 years ago
Yes, that is the way right now. 

It is also recommended for those who want the feature to star it. If there is 
interest enough we may re-open the discussion.

Original comment by eduardo.macarron on 17 Dec 2012 at 8:15

GoogleCodeExporter commented 9 years ago
Eventually you could have different sessionFactory, one for reading data that 
will use a simple executor, and another that will use a session template 
configured in batch mode.

I believe this is a safe approach as the executors are bound to the transaction.

Original comment by brice.du...@gmail.com on 17 Dec 2012 at 8:19

GoogleCodeExporter commented 9 years ago
i had looked above.  If i have two sessionFactorys, one is for simple and the 
other is for batch. Than, it's yes or no that the initialize will be executed 
twice !?

Original comment by yaoh...@gmail.com on 30 Jan 2013 at 8:31

GoogleCodeExporter commented 9 years ago
Sorry? What do you mean?

Original comment by eduardo.macarron on 30 Jan 2013 at 8:52

GoogleCodeExporter commented 9 years ago
The approach #11 gived for solving batch and simple in one transaction means 
Instantiating sessionFactory two times, yes? That means parsing the sqlMap.xml 
twice,isn't it? 

Original comment by yaoh...@gmail.com on 30 Jan 2013 at 9:26

GoogleCodeExporter commented 9 years ago
@Yao yes, that is the case. But note the sessionFactories should be 
specialized, one for reading and one for writing (in a batch mode).

Note however that you should definitely use this approach with care, as your 
code should avoid writing using the regular session factory. One way to do that 
would be to have dedicated mapper files for reading (queries) and writing 
(insert/update/delete statements), and you'll configure your sessionFactory 
accordingly.

Original comment by brice.du...@gmail.com on 30 Jan 2013 at 9:39

GoogleCodeExporter commented 9 years ago
Issue 772 has been merged into this issue.

Original comment by eduardo.macarron on 8 Feb 2013 at 3:43