Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for iBatis 3 [SPR-5991] #10659

Closed
spring-issuemaster opened this issue Aug 9, 2009 · 73 comments
Closed

Add support for iBatis 3 [SPR-5991] #10659

spring-issuemaster opened this issue Aug 9, 2009 · 73 comments

Comments

@spring-issuemaster
Copy link
Collaborator

@spring-issuemaster spring-issuemaster commented Aug 9, 2009

Gabriel Axel opened SPR-5991 and commented

iBatis 3 beta 1 introduced API changes which break the existing Spring support.


Affects: 3.0 M3

Attachments:

Issue Links:

  • #10803 Add support for iBatis 3 ("is duplicated by")
  • DATAJDBC-2 Add support for MyBatis 3

50 votes, 55 watchers

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Aug 14, 2009

Gabriel Axel commented

I implemented and attached a very simple factory bean that creates an SqlSession instance.
If you want to use spring-managed transactions (as you probably should), use TransactionAwareDataSourceProxy, since my implementation doesn't work with any transaction manager.

Configuration example:

<bean id="sqlSessionFactory" class="org.springframework.orm.SqlSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="MapperConfig.xml"/>

and in your DAO simply use an wired field:

@Autowired private SqlSession sqlSession;

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Aug 23, 2009

Marino A. Jonsson commented

The old SqlMapClient was thread safe, but the new SqlSession is not - so I'm thinking it might not be a good idea to reuse the same SqlSession instance :)

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Aug 30, 2009

AngerClown commented

Here is a more thorough, but probably still incomplete implementation that also deals with transaction management. This is nowhere near production ready and it's barely tested, but it does work in the usual case. See the attached for all code and a simple HSQLDB test. Test code is in the com.example package; framework code in com.example.springibatis3.

The SpringSqlSessionFactoryBuilder , based on the earlier attachment to this issue, creates DefaultSqlSessionFactory objects, using the standard Ibatis config file. It ignores any Environments set up in the config file, instead setting it null so that a ManagedTransactionFactory (no-op) will be used (see DefaultSqlSessionFactory). This builder class could certainly be enhanced so that the Ibatis config file is not needed, but that seems like overkill to me especially given the amount of code that would be needed to do this in a way that mimics Ibatis - see org.apache.ibatis.builder.xml.XMLConfigBuilder.

There is a subclass of SqlSession, SpringSqlSession that carries references to the current DataSource and Connection but otherwise delegates to the usual DefaultSqlSession while wrapping the IbatisExceptions sql based exceptions in Spring's exception hierarchy.

Transaction management is handled by Ibatis3TransactionManager, which subclasses DataSourceTransactionManager. At the appropriate places, it just makes sure the SqlSession is created and then cleans up after itself.

Finally, there is an Ibatis3DaoSupport class that DAOs can subclass.

Note that I made a design decision with this cod. Personally, I never like the SqlMapTemplate approach used with Ibatis 2. I know it's not much, if any, of a performance hit in practice, but the all the callbacks, along with opening and closing a SqlMapSession had a 'code smell' to me. In Ibatis3, the SqlSession is a more first class object, so I think it's even more important that new ones are only created as needed.

For this reason, I didn't create a SqlSessionTemplate class. The assumption is that all access must be transactional. There is an execute() method in Ibatis3DaoSupport that will support a callback, but this was meant for one-off kinds of operations. I however, don't yet understand the underlying Spring tx code enough to figure out how or if this could be enforced, or how a DAO should know it's running transactionally or not. I will leave that up to the Spring designers.
Some of this code could also be cleaned up / simplified if things were changed in the Ibatis 3 code. I have filed 2 bugs for this against ibatis:
https://issues.apache.org/jira/browse/IBATIS-631
https://issues.apache.org/jira/browse/IBATIS-634

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Nov 29, 2009

Putthibong Boonbong commented

Hello,

I implemented the full support of iBatis3 and updated most of javadoc.

File attachment contains:
org.springframework.orm.ibatis3.support.SqlSessionDaoSupport
org.springframework.orm.ibatis3.SqlSessionCallback
org.springframework.orm.ibatis3.SqlSessionFactoryBean
org.springframework.orm.ibatis3.SqlSessionOperations
org.springframework.orm.ibatis3.SqlSessionTemplate

Please revise source code and javadoc. It would be nice if you can release Spring 3.0 Final with iBatis3 support.


Best regards,

Putthibong Boonbong

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 6, 2009

AngerClown commented

The latest code submitted is much better than what I previously submitted. But, there are still a few issues that I think I have corrected.

First, the easy stuff in SqlSessionFactoryBean:
-minor javadoc updates - the biggest was to make clear that the environment is just a name, it's not really useful as more than a place holder
-setDataSource does NOT need to be a TransactionAwareDataSourceProxy for any case (see below); I also removed the flag for allowing this
-sqlSessionFactoryProperties was being added to the Configuration too late, I changed it so the Configuration is built with the properties (in the constructor for XMLConfigBuilder
-changed the mapperLocations to an array of (Spring) Resources as well as the handling for these in buildSqlSessionFactory()

The major change was to add transaction synchronization support. This is in the SqlSessionSynchronization class (part of SqlSessionUtils). The idea here is to make sure that if synchronization is active (from DataSourceTransactionManager), the SqlSession cleans up after itself. In addition, only one SqlSession should be created per set of calls to the template (or transaction). SqlSessionUtils has get and close methods to support this. This is instead of creating a separate iBATIS transaction manager. In most cases, users will not need to deal with commit or rollback, but SqlSessionUtils has methods for this that make sure the underlying JDBC connection is dealt with.

Here's how things should work:
-running SqlSessionFactory outside of the SqlSessionTemplate: works just like normal; user needs to call close
-running SqlSessionTemplate outside of a TransactionManager: template closes SqlSession & JDBC connection, no commits or rollbacks
-running SqlSessionTemplate in a TxMgr with SUPPORTS: synchronization closes SqlSession & JDBC connection, no commits or rollbacks
-running SqlSessionTemplate in a TxMgr with REQUIRED, etc: TxManager closes JDBC Connection, synchronization closes SqlSession, commits & rollbacks from TxMgr and synchronization

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 10, 2009

Matt Moriarity commented

An important note that I think should be made is that when using this with Mapper interfaces without transactions, SqlSessions do not get closed properly.

I was running into issues where I would get IO errors from the database because my connections weren't being closed. Adding @Transactional annotations to my services caused synchronization to kick in and close the connections for me, making these errors disappear.

Just note in case someone else runs into a similar issue.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 13, 2009

Matt Moriarity commented

Ok nevermind, that doesn't fix anything. I'm still getting IO errors. I'm not sure if it's related to this or not.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 13, 2009

Matt Moriarity commented

After some diagnosis, it seems that somewhere in the process of getting Connections, they are being closed before I actually use them. I'm using C3P0 for pooling and have it testing connections on checkout, which is succeeding. However, after that I get exceptions that my connection is already closed. Here is the full stack trace:

java.lang.NullPointerException
 at com.mchange.v2.c3p0.impl.NewProxyConnection.prepareStatement(NewProxyConnection.java:186)
 at sun.reflect.GeneratedMethodAccessor38.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at org.apache.ibatis.logging.jdbc.ConnectionLogger.invoke(ConnectionLogger.java:35)
 at $Proxy29.prepareStatement(Unknown Source)
 at org.apache.ibatis.executor.statement.PreparedStatementHandler.instantiateStatement(PreparedStatementHandler.java:50)
 at org.apache.ibatis.executor.statement.BaseStatementHandler.prepare(BaseStatementHandler.java:64)
 at org.apache.ibatis.executor.statement.RoutingStatementHandler.prepare(RoutingStatementHandler.java:39)
 at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:55)
 at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:40)
 at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:82)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:58)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:52)
 at org.apache.ibatis.binding.MapperMethod.executeForList(MapperMethod.java:77)
 at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:58)
 at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:18)
 at $Proxy31.suggest(Unknown Source)
 at net.moriaritys.triumph.dao.TagDaoIbatisImpl.suggest(TagDaoIbatisImpl.java:37)
 at net.moriaritys.triumph.service.TagServiceImpl.suggest(TagServiceImpl.java:41)
 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:307)
 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:107)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
 at $Proxy8.suggest(Unknown Source)
 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 com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:562)
 at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:544)
 at org.spring4gwt.server.SpringGwtRemoteServiceServlet.processCall(SpringGwtRemoteServiceServlet.java:37)
 at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:224)
 at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
 at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
 at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:362)
 at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
 at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
 at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:729)
 at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
 at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
 at org.mortbay.jetty.handler.RequestLogHandler.handle(RequestLogHandler.java:49)
 at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
 at org.mortbay.jetty.Server.handle(Server.java:324)
 at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:505)
 at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:843)
 at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:647)
 at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:211)
 at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:380)
 at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:395)
 at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:488)
ERROR [btpool0-0] (JakartaCommonsLoggingImpl.java:19) - Error calling Connection.prepareStatement:
java.sql.SQLException: You can't operate on a closed Connection!!!
 at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:106)
 at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:65)
 at com.mchange.v2.c3p0.impl.NewProxyConnection.prepareStatement(NewProxyConnection.java:222)
 at sun.reflect.GeneratedMethodAccessor38.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at org.apache.ibatis.logging.jdbc.ConnectionLogger.invoke(ConnectionLogger.java:35)
 at $Proxy29.prepareStatement(Unknown Source)
 at org.apache.ibatis.executor.statement.PreparedStatementHandler.instantiateStatement(PreparedStatementHandler.java:50)
 at org.apache.ibatis.executor.statement.BaseStatementHandler.prepare(BaseStatementHandler.java:64)
 at org.apache.ibatis.executor.statement.RoutingStatementHandler.prepare(RoutingStatementHandler.java:39)
 at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:55)
 at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:40)
 at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:82)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:58)
 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:52)
 at org.apache.ibatis.binding.MapperMethod.executeForList(MapperMethod.java:77)
 at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:58)
 at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:18)
 at $Proxy31.suggest(Unknown Source)
 at net.moriaritys.triumph.dao.TagDaoIbatisImpl.suggest(TagDaoIbatisImpl.java:37)
 at net.moriaritys.triumph.service.TagServiceImpl.suggest(TagServiceImpl.java:41)
 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:307)
 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:107)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
 at $Proxy8.suggest(Unknown Source)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 15, 2009

AngerClown commented

I ran into this issue too. The problem is that the SqlSession.getConnection() method returns a proxy if logging of java.sql is enabled. This causes the close call in DataSourceUtils to fail since connectionEquals always returns false. The solution was to add an unwrapConnection method in SqlSessionUtils. It's questionable if this is the right place - it works now, but a better method may be to have SqlSessionUtils.getSqlSession return a proxy SqlSession that handles this.

Also, I don't know if this performs well since the unwrap method calls Proxy.isProxyClass then Proxy.getInvocationHandler (which calls isProxyClass again). It may be better if iBatis used a marker interface for logging Connections so we could just call instance of to see if we need to unwrap the Connection. This would require a bug fix in iBatis.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 15, 2009

Matt Moriarity commented

I appreciate the effort AngerClown, but I tried your new code and i'm still running into the same issues. The messages saying close() has been called more than once went away but the root problem is still there.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 15, 2009

Putthibong Boonbong commented

Could you please attach your code (implementation and test) that using c3p0?

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 16, 2009

Matt Moriarity commented

My code is available at http://github.com/mjm/triumph/

It's a GWT application using Spring to manage services. All my iBATIS operations happen through the new Mapper interfaces.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 27, 2009

Eduardo Macarron commented

Hi, I think it would be great that Spring was able to inject iBATIS new Mapper Interfaces to bussiness beans.

I have written a really simple class that provides that functionality (it works over Putthibong Boonbong's SqlSessionDaoSupport).

It is just a FactoryBean that holds a JDK proxy that is able to redirect method calls to real iBATIS mappers.

So you can simply define your beans using just interfaces, implementations are not needed anymore.

<bean id="dao" class="org.springframework.orm.ibatis3.support.IBatisMapperFactoryBean">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="mapperInterface" value="org.example.UserMapper" />
</bean>
<bean id="otherBean" class="otherBeansClass">
<property name="userDao" ref="dao" />
</bean>

where UserMapper is:

public interface UserMapper {
User getUser(User user);
List<User> getUsers();
}

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 27, 2009

Eduardo Macarron commented

After some tests with transactions I have also reached to the connection closed issue:

DEBUG (SqlSessionUtils.java:55) - Fetching SqlSession from current transaction
ERROR (JakartaCommonsLoggingImpl.java:19) - Error calling Connection.prepareStatement:
java.sql.SQLException: Connection is closed.
at org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper.checkOpen(PoolingDataSource.java:175)
at org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper.prepareStatement(PoolingDataSource.java:301)

It fails when executing two transactions within the same Thread. It seemed that SqlSession was not properly cleaned in threadlocal. So I debugged step by step the transaction closing code to see what was happening.

In SqlSessionUtils.afterCompletion() the code reads this:
finally {
if (!holder.isOpen()) {
logger.debug("Transaction synchronization closing SqlSession");
TransactionSynchronizationManager.unbindResource(sessionFactory);
holder.getSqlSession().close();
}
}

changed it to
if (holder.isOpen()) {
...

And it seems to work fine now.

I hope you don't mind if I upload again the test code with the fix (please remove my two previous uploads)

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 27, 2009

Eduardo Macarron commented

It seems that holder reference count is always two.

I suppose that SqlSession getSqlSession has a bug because it calls twice to hoder.requested()
Anyway removing one of them leaves hoder reference count in one, and isOpen is still true.

The problem seems to be related to Mappers.

getMapper calls SqlSessionUtils.getSqlSession that adds one to reference count but mappers dont call to SqlSessionUtils.closeSqlSession that removes it.

So transaction closes propertly the session but counter is not set to zero.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 27, 2009

Eduardo Macarron commented

The only solution I can imagine is to wrap Mapper calls. The JDK proxy I submitted helps in that purpose:

This code, fixes the problem with the holder counter and the code runs fine keeping the original code if (!holder.isOpen())

public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    SqlSession sqlSession = SqlSessionUtils.getSqlSession(getSqlSessionFactory());

    try {
        return method.invoke(getSqlSessionTemplate().getSqlSessionFactory().getConfiguration().getMapper(mapperInterface, sqlSession), args);
        
        // TODO exceptions should be translated
    } finally {
        SqlSessionUtils.closeSqlSession(sqlSession, getSqlSessionFactory());
    }

}

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 27, 2009

Eduardo Macarron commented

Please have a look to the MapperFactoryBean.tar.gz I have uploaded.

It seems to work fine with Mappers (with and without transaction context).

I have made two changes to Anger's code, but please have a look at them because I may be wrong.

In SQlSessionUtils getSqlSession, I have removed one "holder.requested();"

And have added the same line (holder.requested();) at the end, when a new holder is created.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 29, 2009

AngerClown commented

I think the call in SqlSessionSynchronization.afterCompletion() should still be if(!holder.isOpen()). The sqlSession should only be closed if there are no other references to it. Changing to just if(holder.isOpen()) would close the session every time even if it is still being used by another TX / thread, which is not correct.

The duplicate call to holder.requested() is certainly a bug though. Not sure how I missed that.

The idea of the MapperFactoryBean as a proxy could be useful, but I think it needs to be expanded to deal with transactions. The way Eduardo has it coded now explicitly closes the session. I have an updated class in the attached that calls SqlSessionTemplate.getMapper directly, but haven't tested it. Eduardo's exception handling in the MapperFactory also inspired me to update the exception handling in SqlSessionTemplate (see the wrapException method).

I think the attached code also fixes Matt's issues. The problem was that the getMapper method on SqlSessionTemplate needs to return a mapper proxy that is TX aware. I have updated that method to return a different proxy if there is an existing transaction.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 29, 2009

Eduardo Macarron commented

Sorry Anger, I just was trying to point out that the problem was related to that line (!holder.isOpen()) but of course the solution is not changing it to the opposite. I think I have been more clear in my following post.

The code you submitted still fails with the connection close error, but I think its just because the duplicated holder.requested() is still there.

I think mappers should not have any transactional special dealing because all transactional stuff is being handled inside SqlSessionUtils.

And I am afraid this code is the root of the connection closed problem:

final SqlSession sqlSession = SqlSessionUtils.getSqlSession(sqlSessionFactory, getDataSource());



if (SqlSessionUtils.isSqlSessionTransactional(sqlSession, sqlSessionFactory)) {

     return sqlSessionFactory.getConfiguration().getMapper(type, sqlSession);

because the bean that uses the mapper should call SqlSession.close() and it cant, and if it could it should not because it did not open the SqlSession. In fact this is exactly the problem with Matts code. He uses the mapper but does not close sessions so the Holder counter is always in wrong state so the SqlSession survives and so the closed connection inside it.

@Override
public List<Tag> getAll() {
    return getMapper().selectAll();
}

Anyway Matt's should be almost the right use. I mean that mappers let us to provide an implementation that releases the application to have direct calls to ibatis api. Have a look at context.xml, the "daos" are declared this way:

<bean id="dao"
	class="org.springframework.orm.ibatis3.support.MapperFactoryBean">
	<property name="sqlSessionFactory" ref="sqlSessionFactory" />
	<property name="mapperInterface" value="org.example.UserMapper" />
</bean>

In the case of Matts code, he has a TagDaoIbatisImpl, TagDao and TagMapper. He should inject directly the TagMapper and get rid of the other objects.

Regarding the new code, MapperFactoryBean could be even simpler because it does not need to create a proxy it can use directly the one created in SqlSessionTemplete but just for performance maybe it good be good to get rid from the creation of a new dynamic proxy when mapper is called from MapperFactoryBean, it would be better to hold the proxy on MapperFactoryBean (it should be one MapperFactoryBean for each mapper) and reuse it.

I have done some little changes. It works with and without transactions in my simple test. It would be great if Matt tries it in his project.
These are the changes:

  • removed the duplicated holder.requested() in getSqlSession
  • added holder.requested() to the block that creates the Holder
  • new method invokeMapper to be called from MapperFactoryBean
  • getMapper uses invokeMapper()

Have a look at MapperFactoryBean_20091229.tar.gz

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 29, 2009

Eduardo Macarron commented

I have tried to know if there is any performance impact for creating that proxy in each call and.. if there is any it is almost zero. So I think your code is cleaner and is better to remove the proxy from MapperFactoryBean and have it inside SqlSessionTemplate. An also, we dont have that awful invokeMapper public method on SqlSessionTemplate.

So let me upload a new code again. This one with a really trivial MapperFactory, holder.requested() fix and transaction handling inside SqlUtils.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 29, 2009

Ivan Hui commented

Dear Eduardo,
Are you sure MapperFactoryBean_20091229-2.tar.gz work?I give it a try without change,but i get below issues:

com.ying.yppc.orm.ibatis3.IbatisSystemException: SqlSession operation; nested exception is java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for java.lang.Object.toString
at com.ying.yppc.orm.ibatis3.SqlSessionTemplate.wrapException(SqlSessionTemplate.java:254)
at com.ying.yppc.orm.ibatis3.SqlSessionTemplate$7.invoke(SqlSessionTemplate.java:229)
at $Proxy5.toString(Unknown Source)
at org.apache.log4j.or.DefaultRenderer.doRender(DefaultRenderer.java:36)
at org.apache.log4j.or.RendererMap.findAndRender(RendererMap.java:80)
at org.apache.log4j.spi.LoggingEvent.getRenderedMessage(LoggingEvent.java:362)
at org.apache.log4j.helpers.PatternParser$BasicPatternConverter.convert(PatternParser.java:403)
at org.apache.log4j.helpers.PatternConverter.format(PatternConverter.java:65)
at org.apache.log4j.PatternLayout.format(PatternLayout.java:502)
at org.apache.log4j.WriterAppender.subAppend(WriterAppender.java:302)
at org.apache.log4j.WriterAppender.append(WriterAppender.java:160)
at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)
at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:66)
at org.apache.log4j.Category.callAppenders(Category.java:206)
at org.apache.log4j.Category.forcedLog(Category.java:391)
at org.apache.log4j.Category.info(Category.java:666)
at com.ying.yppc.logging.Logging.after(Logging.java:13)
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.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:627)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:609)
at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:45)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at $Proxy6.selectCompany(Unknown Source)
at com.ying.yppc.service.impl.CompanyServiceImpl.getCompany(CompanyServiceImpl.java:34)
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:307)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at $Proxy7.getCompany(Unknown Source)
at com.ying.yppc.test.Testing.testing(Testing.java:27)
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.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:46)
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)
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for java.lang.Object.toString
at org.apache.ibatis.session.Configuration$StrictMap.get(Configuration.java:366)
at org.apache.ibatis.session.Configuration.getMappedStatement(Configuration.java:312)
at org.apache.ibatis.binding.MapperMethod.setupCommandType(MapperMethod.java:135)
at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:44)
at com.ying.yppc.orm.ibatis3.SqlSessionTemplate$7.invoke(SqlSessionTemplate.java:226)
... 64 more

it works fine if i discard MapperFactoryBean,enclosed article is for your reference:
http://issues.apache.org/jira/browse/IBATIS-655

thanks a lot more for your help.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 29, 2009

Eduardo Macarron commented

Ivan Xu, I would say that you are trying to execute a toString over a mapper...

"Mapped Statements collection does not contain value for java.lang.Object.toString"

If this is que case, the IllegalArgumentException is right.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 29, 2009

Ivan Hui commented

sorry to my stupid mistake,Spring version 2.5.6 cause the problem,I upgrade my Spring to version 3.0 and the issue fixed already,thanks everyone.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 29, 2009

Ivan Hui commented

Dear Eduardo,I do nothing,you can try MapperFactoryBean_20091229-2.tar.gz with Spring 2.5.6,then you can get the issue.
Many thanks your help.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 29, 2009

Eduardo Macarron commented

Ivan Xu, it works for me with 2.5.6. All you have to do is replacing FactoryBean<?> with FactoryBean because in 2.5.6 FactoryBean is not generic.

But the problem you posted is related to the use of toString over a Mapper. If I code this

private UserMapper userMapper;

public void getUser() {
	userMapper.toString();
}

I get exactly the same error:

org.springframework.orm.ibatis3.IbatisSystemException: SqlSession operation; nested exception is java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for java.lang.Object.toString
	at org.springframework.orm.ibatis3.SqlSessionTemplate.wrapException(SqlSessionTemplate.java:254)
	at org.springframework.orm.ibatis3.SqlSessionTemplate$7.invoke(SqlSessionTemplate.java:229)
	at $Proxy1.toString(Unknown Source)
	at org.example.Service.getUser(Service.java:22)
	at org.example.Service.exec(Service.java:33)
	...
@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 29, 2009

Eduardo Macarron commented

Have a look at your applicationContext.xml, you have an aop interceptor that calls com.ying.yppc.logging.Logging.after(), it seems there is the problem (maybe some bean is receving a mapper as an argument, and it is being logged automatically.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 29, 2009

Ivan Hui commented

Dear Eduardo,yes it's all that,it works fine after I remove the interceptor,thank you very much.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 30, 2009

Matt Moriarity commented

Eduardo,

I'm currently modifying my project to use the MapperFactoryBeans to get my mappers. I will comment when I'm done to see if there was success inside of transactions.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 30, 2009

Matt Moriarity commented

Things are working now. I assume this is due to the other fixes included in Eduardo's code, not just the use of MapperFactoryBean, seeing as it is quite basic in the most recent code.

Regardless, I'm no longer having issues with closed connections. Thank you all.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Dec 30, 2009

Eduardo Macarron commented

Good to hear that!

Anger what do you think about this?. getMapper method could be even more simpler and cleaner this way

public <T> T getMapper(Class<T> type) {
    // mimic iBATIS MapperProxy
    // maybe iBATIS should have a way to replace the MapperProxy in the
    // Configuration?
    return (T) java.lang.reflect.Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type },
            new InvocationHandler() {
                @Override
                public Object invoke(final Object proxy, final Method method, final Object[] args)
                        throws Throwable {
                    return execute(new SqlSessionCallback<T>() {
                                                 // MapperMethod.execute declares "throw SQLException" but it does throw just iBatisExceptions
                        @Override
                        public T doInSqlSession(SqlSession sqlSession) throws SQLException {
                                                         return (T) new MapperMethod(method, sqlSession).execute(args);
                        }
                    });
                }
            });
}

The problem is that MapperMethod.execute declares SQLException and I think this is an error in iBatis code. If the finally fix it, this code wont compile.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented May 16, 2010

Robert Novotny commented

There is a slight problem when programmatically configuring MapperFactoryBean while using JdbcTransactionFactory. Since we are not using TransactionSychronizationManager, the holder in SqlSessionUtils.getSqlSession() is null, thus throwing an NPE in the holder.getSqlSession():

if (!usesManagedTransactions(sessionFactory.getConfiguration())
  && DataSourceUtils.isConnectionTransactional(unwrapConnection(holder.getSqlSession()), dataSource)) {

This occurs when programmatically configuring the MapperBeanFactory

PooledDataSource dataSource = new PooledDataSource("org.hsqldb.jdbcDriver", "jdbc:hsqldb:hsql://localhost/test", "SA", "");

TransactionFactory transactionFactory = new JdbcTransactionFactory();

Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(VenueMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

MapperFactoryBean mapperFactory = new MapperFactoryBean();
mapperFactory.setSqlSessionFactory(sqlSessionFactory);
mapperFactory.setMapperInterface(VenueMapper.class);

VenueMapper venueMapper = (VenueMapper) mapperFactory.getObject();
Venue venue = venueMapper.findVenue(1L);

I suppose that instead of NPE, it should throw the "Pre-bound JDBC Connection found!" exception.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented May 26, 2010

AngerClown commented

It's been a while since I have looked at the code, but I think this will fix Robert's issue. Replace the the original if check with this:

if (!usesManagedTransactions(sessionFactory.getConfiguration())) {
  org.springframework.jdbc.datasource.ConnectionHolder connectionHolder = (org.springframework.jdbc.datasource.ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);

  if ((connectionHolder != null) && (connectionHolder.getConnectionHandle() != null)) {
    if(connectionHolder.getConnectionHandle().getConnection() == null) {
      throw new IllegalTransactionStateException("Pre-bound JDBC Connection found! iBATIS SqlSession does not support " + "running within DataSourceTransactionManager if told to manage the DataSource itself.");
    }
  }
}

Looking at the code again, the holder will always be null at this point so that was a bug. What I think we really care about is if there is a current Connection active. (The nested if's are there to avoid Assert errors on nulls.)

This should allow SqlSessions not managed by Spring to pass through as long as there is no other active Connection. There could be an active Spring transaction that's not using a Connection or a suspended Spring tx - should be output a warning message in those cases?

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented May 26, 2010

AngerClown commented

Oops, hit submit right as I noticed an error. The innermost if in my last comment should be
if(connectionHolder.getConnectionHandle().getConnection() != null) {

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jun 6, 2010

AngerClown commented

For anyone trying to use Mybatis instead of iBatis, note that the current code will not work properly. The code attached will work for Mybatis. It should also work for iBatis GA (not any of the betas) except that the MybatisSystemException and SqlSessionTemplate classes will need to reference IbatisException instead of PersistenceException (IbatisException is deprecated in Mybatis). The necessary code is commented out in the attched classes.

Please test this code out and let me know if there are any issues. I have made some major changes to SqlSessionUtils but not any significant API changes. This should be a drop in replacement.

Also note that the org.springframework.orm.ibatis3.test package is not needed for running the code and introduces some other dependencies. If you don't care about the internals or test cases, there's no need to include this.

h5.Reasons for the changes:
Per http://code.google.com/p/mybatis/issues/detail?id=25, ManagedTransactionFactory now closes the JDBC Connection. See the mailing list discussion referenced in this issue, but the bottom line is that this change is not going to be backed out. I guess we can't really complain though since iBatis 2 did this and it wasn't a problem for the Spring integration (mostly because the Spring implementation tore apart major parts of the iBatis code). Trying to fix this got me thinking about all the code paths in SqlSessionUtils and I realized that having a Spring based TransactionManager for iBatis would be much easier.

h5.What Changed:
For those interested, in the details, I was able to simplify SqlSessionUtils by creating an subclass of iBatis Transaction, SpringManagedTransaction (and an associated factory). SqlSessionFactoryBean will use this as the default. This class simply tests if there is an existing Spring transaction. If not, it works like JDBCTransaction and acts on the Connection. If there is a Spring tx, it no-ops.
Because of this change, a DataAccessException will now be thrown in SqlSessionUtils.getSqlSession() if the SqlSession is using JDBCTransactionFactory or ManagedTransactionFactory and there is an existing Spring transaction in progress on the given Connection. This is because those Transactions will not behave correctly with Spring tx and I think it's better to fail fast and loudly. Note that users would have to explicit set these base iBatis TransactionFactories or create an SqlSessionFactory outside of our SqlSessionFactoryBean to have this error occur.

h5.Other Changes:
SqlSessionFactoryBean

  • renamed sqlSessionFactoryProperties to configurationProperties to make the puropse of this value clearer
  • changed to allow only 1 configLocation since there is no support for merging configurations in iBatis
  • allow configLocation to be null - the default (Configuration() constructor) will be used instead
  • allow setting a differnt SqlSessionFactoryBuilder for test purposes
  • changed default TransactionFactory to SpringManagedTransactionFactory
  • minor code and doc cleanup

SqlSessionUtils

  • major cleanup due to use of SpringManagedTransactionFactory in SqlSessionFactoryBean
  • moved commit to afterCompletion since that seemed more in line with ConnectionSynchronization / DSTXMgr
  • Session synchronization now only commits if there is a actual transaction active - this is in line with PROPAGATION_SUPPORTS, which creates a tx synch but does not commit the JDBC Connection

SqlSessionDaoSupport

  • cleaned up javadoc
  • added support for SqlSessionTemplate constructor - allows a truely shared SqlSessionTemplate for all DAOs; there's no need to create 1 for each

SqlSessionTemplate

  • IbatisException changed to PersistenceException for Mybatis - swap the commented out portions for iBatis GA

SqlSessionHolder

  • removed DataSource property since it is not needed

h5.Tests:
I have implemented a number of Junit 4 tests for this new code in the org.springframework.orm.ibatis3.test package. I think I have all the basic use cases covered and they pass both with iBatis and Mybatis code. Please let me know if there's a need for other test cases.
As an implementation note, I needed a quick and dirty set of mock JDBC classes and used the code from Mockrunner (mockrunner.sourceforge.net). This package also needs Jakarta ORO to run. Unfortunately, mockrunner's POM in the default Maven repository seems to be broken so I had to download it manully. If anyone has any better suggestions for a mock framework, I would be glad to change the test code. I looked at EasyMock but it seemed tedious and fragile to have to lay out all the JDBC methods the iBatis code would call.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jun 7, 2010

Eduardo Macarron commented

Hi Anger. I've run my simple tests and everything works fine.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jun 9, 2010

Putthibong Boonbong commented

Revise from AngerClown's code on 06/Jun/10.

  • MapperFactoryBean is a Factory which return mapper interfaces. It should not extends SqlSessionDaoSupport because it doesn't have IS-A relationship to SqlSessionDaoSupport.
public class MapperFactoryBean <T> implements FactoryBean<T>, InitializingBean {

    private Class<T> mapperInterface;

    private SqlSessionTemplate sqlSessionTemplate;

    public MapperFactoryBean() {
        super();
    }

    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(mapperInterface, "Property 'MapperInterface' is required");
        Assert.notNull(sqlSessionTemplate, "Property 'sqlSessionTemplate' is required");
    }

    @Override
    public T getObject() throws Exception {
        return sqlSessionTemplate.getMapper(mapperInterface);
    }

    @Override
    public Class<T> getObjectType() {
        return mapperInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

}
  • Remove checkDaoConfig() from SqlSessionDaoSupport because to the first change.

  • Remove unused constructor MybatisSystemException(String msg, IbatisException cause) from MybatisSystemException.java.

  • Use maven structure.

  • Reformat code and small fixes.

Furthermore, mybatis version 3.0.x remain using package name 'org.apache.ibatis' and Clinton Begin will rename package to others in version 4.0.x. Renaming spring package to 'org.springframework.orm.mybatis' should postpone until version 4.0.x was released.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jun 9, 2010

Eduardo Macarron commented

Hi Putthibong

I think we should keep the possibility to build mappers the way it was done before, just injecting a SqlSessionFactory in a MapperFactoryBean. This way:

	<bean id="sqlSessionFactory" class="org.springframework.orm.ibatis3.SqlSessionFactoryBean">
		<property name="configLocation" value="classpath:org/example/ibatis-config.xml" />
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="userMapper"
		class="org.springframework.orm.ibatis3.support.MapperFactoryBean">
		<property name="sqlSessionFactory" ref="sqlSessionFactory" />
		<property name="mapperInterface" value="org.example.UserMapper" />
	</bean>

But now if a MapperFactoryBean needs a SqlSessionTemplate instead of a SqlSessionFactory the configuration will be bigger (create a SqlSessionFactory + create a SqlSessionTemplate + create Mappers) but I think we won´t get any advantage and would prefer the easier way.

Maybe a MapperFactoryBean could get a SqlSessionFactory instead of a SqlSessionTemplate or maybe both. Just adding this method.

public final void setSqlSessionFactory(SqlSessionFactory sessionFactory) {
         this.sqlSessionTemplate = new SqlSessionTemplate(sessionFactory);
}

I think this change is needed.

And if we also want to be able to set a datasource then what we get is that we have SqlSessionDaoSupport code fully replicated on MapperFactoryBean and in that case maybe extending is not so bad. At least something that "makes" DAOs is going to have similar config option that a DAO.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jun 9, 2010

Putthibong Boonbong commented

Sorry to remove checkDaoConfig, this method should declare here in SqlSessionDaoSupport to prevent subclass from this declaration.

And what about this MapperFactoryBean?

public class MapperFactoryBean <T> implements FactoryBean<T>, InitializingBean {

    private DataSource dataSource;

    private Class<T> mapperInterface;

    private SqlSessionFactory sqlSessionFactory;

    private SqlSessionTemplate sqlSessionTemplate;

    public MapperFactoryBean() {
        super();
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(mapperInterface, "Property 'MapperInterface' is required");

        if (sqlSessionFactory == null && sqlSessionTemplate == null) {
            throw new IllegalArgumentException("Property 'sqlSessionFactory' is required");

        } else if (sqlSessionTemplate == null) {
            sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
        }

        if (dataSource != null) {
            sqlSessionTemplate.setDataSource(dataSource);
        }
    }

    @Override
    public T getObject() throws Exception {
        return sqlSessionTemplate.getMapper(mapperInterface);
    }

    @Override
    public Class<T> getObjectType() {
        return mapperInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}
@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jun 9, 2010

Eduardo Macarron commented

Hi Putthibong. That version looks perfect to me :)

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jun 15, 2010

Eduardo Macarron commented

Just a note. Now that MapperFactoryBean does not depend on SqlSessionDaoSupport it should be moved to the parent package because it is not supposed to be used from application beans.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 4, 2010

arpana gupta commented

Hi,

Is there any final spring-orm-ibatis3 jar file available, that can be used directly?

Thanks,
Arpana

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 5, 2010

Eduardo Macarron commented

Hi Arpana. I am sure you wont have any problem in building a working jar by your own. Just take org.springframework.orm.ibatis3-201006100756.tar.gz + spring 3.0.3 + commons logging + mybatis-3.0.1 and compile it.
Just be aware that all these are community contributed sources and may change a lot on final 3.1 release.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 10, 2010

Jonathan Komorek commented

For what it is worth, I have added the following method to the MapperFactoryBean in my own project

 
public void addMapperInterfaceToConfig() {
  if (!this.getSqlSessionFactory().getConfiguration().hasMapper(mapperInterface)) {
    this.getSqlSessionFactory().getConfiguration().addMapper(mapperInterface);	
    }
}

By referencing this as an init-method within my bean definition it removes the need to maintain a separate list of mappers in the ibatis-config.xml.

<bean id="userMapper"
  class="org.springframework.orm.ibatis3.support.MapperFactoryBean"
  init-method="addMapperInterfaceToConfig">
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
  <property name="mapperInterface" value="org.example.UserMapper" />
</bean>

I think this is pretty handy and thought I would share.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 12, 2010

Jeremy Pyman commented

I attempted to build the jar file according to Eduardo's instructions and ran into some problems. The issue I was experiencing seems to be identical to the one reported at https://issues.apache.org/jira/browse/IBATIS-625 even though it is listed as resolved.

I am using spring 3.0.3-RELEASE and MyBatis-3.0.1.

I got around the problem by renaming the mapper interface from org.springframework.orm.ibatis3.TestDao to org.springframework.orm.ibatis3.TestDaoMapper and updating the mapperInterface value in application-context-test-MapperFactoryBean.xml

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 14, 2010

Bing Lu commented

warnings when compiling 610 source IbatisException is deprecated and the import of org.springframework.util.ClassUtils is never used in SqlSessionTemplate.java, just to let you know

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 18, 2010

Eduardo Macarron commented

Jonathan Komorek, good idea to let mappers register by their own.

Maybe using just another property for it would be cleaner (easier to configure). Something like

<bean id="userMapper"
  class="org.springframework.orm.ibatis3.support.MapperFactoryBean"
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
  <property name="mapperInterface" value="org.example.UserMapper" />
  <property name="addToConfig" value="true" />
</bean>

addToConfig should be optional and default to false.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 23, 2010

Jonathan Komorek commented

Eduardo, I like the idea of the property. That's certainly a cleaner implementation.

That being said, I think it might be better to default to true. I suspect that most developers will opt for this approach as opposed to the configuration file, which would mean that every Mapper bean would be required to define this property - a bit of unnecessary clutter.

In addition, the ```
if (!this.getSqlSessionFactory().getConfiguration().hasMapper(mapperInterface))



Do you agree?
@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 23, 2010

Eduardo Macarron commented

Yep Jonathan, you are completely right.

BTW does anybody know why this issue has been unscheduled?

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 23, 2010

Juergen Hoeller commented

Since iBATIS 3.x / MyBatis is a moving target, it looks like we won't support it in Spring Framework 3.x proper. It's likely that we'll rather cover it within our emerging Spring Data sister project - some time later this year.

Juergen

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 23, 2010

Eduardo Macarron commented

I am sorry to hear that Juergen. I hope that Spring Data comes soon :)

Let me upload some little changes I have on my workspace.

  1. Anger Clown tests did not work for me. The problem is that MyBatis 2.0.1 holds a hashmap with loaded xml files and Spring's ClassPathResource.toString() was returning "classpath resouce [org/....] istead of the raw resource name. I made a small change in SqlSessionFactoryBean to fix that.
  2. I moved MapperFactoryBean to the parent package because it is not a support class as it is not supposed to be extended.
  3. Also added my simple test that is a good sample for understanding how to use injected mappers
  4. Added Jonathan addToConfig property defaulting to true
  5. Full maven structure with a working pom

You can have a look at a sample config on /orm-ibatis3/src/test/java/sample/context.xml

	<bean id="sqlSessionFactory" class="org.springframework.orm.ibatis3.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="userMapper"
		class="org.springframework.orm.ibatis3.MapperFactoryBean">
		<property name="sqlSessionFactory" ref="sqlSessionFactory" />
		<property name="mapperInterface" value="sample.UserMapper" />
	</bean>

Just make a maven install to build it and run tests

Maybe building a MyBatis namespace could made config less verbose.
And even something like a component scan for mappers could be a good idea.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 24, 2010

Eduardo Macarron commented

Sorry my previous submission was wrong, I hit the submit button too fast :)

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Sep 3, 2010

Thomas Risberg commented

The support for MyBatis 3 will be provided as part of the Spring Data project in the datastore-sql component. See DATASQL-1.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Sep 22, 2010

Putthibong Boonbong commented

mybatis-spring-1.0.0-RC1 has released, please try and report bug to mybatis issue tracking. Thank you.

http://code.google.com/p/mybatis/

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 7, 2011

Chris Beams commented

See resolution comments at DATAJDBC-2 for information on the MyBatis-Spring project and why integration need not occur in the core Spring Framework project nor the Spring Data family of projects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
1 participant
You can’t perform that action at this time.