`
roverll
  • 浏览: 13809 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

Spring 3集成 mybatis3 声明式事务,事务不能回滚后的思考

    博客分类:
  • java
阅读更多
之前为了练练手自己用spring3 集成 mybaits3,采用spring的声明式事务,发现数据的插入没有问题,但是异常时不能回滚,一开始的代码如下(是按mybatis-spring-1.0.2-reference.pdf):

dao层代码 mybatis 版本的:
public void insertBlog(Blog blog) {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
		blogMapper.insertBlog(blog);
	}
public void addTag(long blogId, Tag tag) {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
		Long tagId = blogMapper.findTagId(tag.getTagName());
		if (tagId != null )
			blogMapper.addTagForBlog(new Tuple<Long, Long>(blogId, tagId));
		else {
			blogMapper.insertTag(tag);
			blogMapper
					.addTagForBlog(new Tuple<Long, Long>(blogId, tag.getId()));
		}
	}

dao层 jdbc版本的实例,代码太长,不罗列了:
@Override
	public void insertBlog(Blog blog) throws Exception {
		Connection connection = dataSource.getConnection();
		String sql = "insert into blog (title," + "create_date, last_modify)"
				+ " values(?,now(),now())";
		PreparedStatement a = connection.prepareStatement(sql);
		a.setString(1, blog.getTitle());
		a.execute();
		String getBlogId = "select LAST_INSERT_ID() as id";
		ResultSet rsid = a.executeQuery(getBlogId);
		if(rsid.first())
			blog.setBlogId(rsid.getLong(1));
	}

bean配置:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:common/MybatisConfig.xml"></property>
	</bean>


	<bean id="blogService" class="service.impl.BlogService">
		<property name="blogDao" ref="blogDaoMybatis" />
	</bean>

	<bean id="blogDaoMybatis" class="dao.BlogDaoWithMybatis">
		<property name="sqlSessionFactory" ref="sqlSessionFactory" />
	</bean>

事务配置:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/test" />
		<property name="username" value="root" />
		<property name="password" value="****" />
	</bean>

	<!-- init the spring transactionManager -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="*Tsc" propagation="REQUIRED"
				rollback-for="java.lang.Exception" />
			<tx:method name="*" propagation="SUPPORTS" />
		</tx:attributes>
	</tx:advice>

service方法:
public void insertBlog(Blog blog) throws Exception {
		blogDao.insertBlog(blog);
	}
public void addTagToBlog(long blogId, Tag tag) throws Exception {
		blogDao.addTag(blogId, tag);
		// throw new RuntimeException("hhh");
	}
public void addBlogTestTsc(Blog blog, Tag tag) throws Exception {
		insertBlog(blog);
		addTagToBlog(blog.getBlogId(), tag);
	}

blog能正常插入,误以为事务工作正常,同样的配置,自己尝试了在service方法addTagToBlog里面抛出异常,发现blog和标签都正常插入了……后来自己又用纯jdbc写了一个插入问题依旧(后面会提到),在网上一直搜寻答案都未果(这也是为什么在这里写出来的原因)
问题的关键不是事务配置错误
首先datasource属性的问题,dbcp默人行为是自动提交,所以插入完数据库就自动提交了,于是改配置为:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/test" />
		<property name="username" value="root" />
		<property name="password" value="****" />
		<property name="defaultAutoCommit" value="false"></property>
	</bean>

发现使用上面的代码后数据怎么都插不进去了,看过spring官方文档事务部分,里面的dbcp数据源配置没有这句:
<property name="defaultAutoCommit" value="false">
。mybatis集成spring的文档也就是强调datasource要和spring事务里面配置的datasource一致,这时候思维就混乱了。


昨天又看着自己的jdbc版本的代码,就想:如果是我用spring的aop来实现事务,我怎么能能在动态生成的代理对象中获取到被代理对象方法里面获取的那个connection对象再去commit和rollback呢?脑袋愚钝,一下子没有想明白。后来就在spring的DataSourceTransactionManager代码里面doCommit方法和doRollBack方法上各打了一个断点,发现不抛异常时执行了doCommit,抛异常时执行了doRollBack。这回可以肯定的是我的配置没有错,也证明我怀疑是对的,spring在代理类中获取的connection和我在dao层代码中获取的不是一个。看了下spring的org.springframework.jdbc.[版本].jar中的类,发现了DataSourceUtils这个类有一个static方法
public static Connection doGetConnection(DataSource dataSource) throws SQLException

顿时豁然开朗,改掉自己jdbc版本的dao层代码获取connection的代码为:
Connection connection = DataSourceUtils.getConnection(dataSource);

再测试,一切ok,搞定。


个人理解:使用DataSourceUtils的getConnection方法时把该获取到的这个链接在当前线程环境中注册到spring事务的context中去了,这样在生成的代理对象中就可以获取到这个connection然后commit或者rollback之(这其中还是有不是很清楚的地方:整么区分一个线程环境中多个地方获取connection,暂时没有敢去看spring的源代码)

如法炮制,在mybatis-spring-1.0.2.jar包中找到了SqlSessionUtils,换取所有获取mybatis SqlSession代码为:
SqlSession sqlSession = SqlSessionUtils.getSqlSession(sqlSessionFactory)

经过测试也正常。
分享到:
评论
2 楼 roverll 2012-04-19  
发现该博客还是有人在看,有必要说明下
文章最后写的使用
SqlSession sqlSession = SqlSessionUtils.getSqlSession(sqlSessionFactory)
这个单单的写测试代码事务是没有问题,我来我直接简单的servlet整合spring把系统跑起来,返现页面第二次做数据访问报错:链接已关闭,有些天在忙其他的,忘记报什么错了,是mybatis-spring-1.0.2.jar本来就有的bug,官网上也有,大家可以查阅。
推荐使用mapper直接注入,一下是配置:
<bean id="blogMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
		<property name="mapperInterface" value="mappers.BlogMapper" />
<!-- value 是类路径(也就是接口) -->
		<property name="sqlSessionFactory" ref="sqlSessionFactory" />
	</bean>

<bean id="blogDaoMybatis" class="dao.BlogDaoWithMybatis">
		<property name="blogMapper" ref="blogMapper" />
	</bean>

java代码就不用贴了

另外,偶尔发现,mybatis返回的list数据集是通过cglib处理过的,这点表示疑惑。
按理是用cglib或者jdk proxy 生成一个 blogMapper接口对象即可,数据没有必要用
cglib处理。发现过程:gson 这个 java 处理json的框架不认cglib处理过的对象,jackson可以
1 楼 roverll 2012-03-15  
另外,<property name="defaultAutoCommit" value="false">
这个配置去掉也行(defaultAutoCommit = true),如果异常,事务会回滚,
再次说明事务不能回滚的原因与此无关

相关推荐

Global site tag (gtag.js) - Google Analytics