以前在项目中使用spring管理hibernate,配置spring为hibernate提供的事务,注入sessionFactory,开启事务驱动,在类或Service上加入@Transactional(propagation = Propagation.REQUIRED)注解即可,现在在一个项目中,数据访问没使用hibernate,使用的jdbc加连接池,刚开始的时候逻辑比较简单,未使用spring的事务管理,连接池配置如下:
<!-- 数据源配置,使用应用内的DBCP数据库连接池 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <!-- Connection Info --> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!-- Connection Pooling Info --> <property name="maxIdle" value="${dbcp.maxIdle}"/> <property name="maxActive" value="${dbcp.maxActive}"/> <property name="defaultAutoCommit" value="false"/> <property name="timeBetweenEvictionRunsMillis" value="3600000"/> <property name="minEvictableIdleTimeMillis" value="3600000"/> </bean>
此时查询都是正常的,在执行insert,update,delete时执行了sql但是数据库记录没有改变,经查证是一个配置项有问题:
<property name="defaultAutoCommit" value="false"/>
在代码中我们的Connection没有手动提交,所以不能持久化到数据库,为了省事就在配置文件中配置如下:
<property name="defaultAutoCommit" value="true"/>
后来随着逻辑的复杂,需要引入事务,所以使用spring的dataSource事务管理,根据spring的官方文档,加入如下配置:
<!-- a PlatformTransactionManager is still required --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- (this dependency is defined somewhere else) --> <property name="dataSource" ref="dataSource"/> </bean> <!-- enable the configuration of transactional behavior based on annotations --> <tx:annotation-driven transaction-manager="txManager"/>
在代码中的Service方法上加入注解:@Transactional(propagation = Propagation.REQUIRED)
测试代码如下:
@Transactional(propagation = Propagation.REQUIRED) public void saveLoginInfo(String userName, String dateString, String result) { String sql = "INSERT INTO prc_mbl_usr_usg (slsprs_id, lgn_dtm, lgn_sts ) VALUES (" + "'" + userName + "'," + "'" + dateString + "'," + "'" + result + "')"; logger.info(sql); toolsDao.insertUtils(sql); int m = 1; if (m == 1) { throw new RuntimeException(); } toolsDao.insertUtils(sql); }
可是在测试后,发现事务不能回滚,第一次的插入数据每次测试都能插入,不能回滚,
当时很纳闷,究竟是什么原因导致事务不能生效呢?
起初怀疑是事务配置有问题,看了官方文档,google好了好多,发现配置没问题,
在后来耐着性子从头到尾看一下配置文件,并思考一下整个测试方法的执行过程,
然后恍然大悟,发现连接池的配置:
<property name="defaultAutoCommit" value="true"/>
也就是说jdbc虽然在事务里,但是自动提交了,所以spring事务无法让数据库回滚,把true改为false后,经测试数据库正常回滚!
===============================================================================================================================================================================
分割线以上是第一次写这篇文章,下面是后来的修改!
上面的文章是错误的,把配置defaultAutoCommit改为false不是数据库回滚了,而是Connection根本没有提交,发现insert,update语句执行之后根本持久化不到数据库;下面是toolsDao类insertUtils的方法:
//执行插入操作 public String insertUtils(String sql) { { Connection con = null; try { con = getConnection(); } catch (SQLException e) { logger.error("connect failed", e); } Statement stmt = null; if (con != null) { try { stmt = con.createStatement(); } catch (SQLException e3) { logger.error("createStatement create failed", e3); } } assert stmt != null; //(2)发送SQL语句到数据库中 try { stmt.executeUpdate(sql); return "success"; } catch (SQLException e4) { logger.error("sql error,or no resSet", e4); } finally { close(null, stmt, con); } } return "failed"; } //获取Connection public Connection getConnection() throws SQLException { basicDataSource.getInitialSize(); return basicDataSource.getConnection(); }
如上是我执行插入的操作,这里直接使用连接池,从连接池里获取连接,本意是想使用spring的事务进行事务管理,正确的配置了事务,但是没有生效,为什么?
下面是我个人的分析,如有不正确,请指教:
在真正的插入操作时,使用的是Connection,通过Connection获取的Statement,connection的默认提交方式是true,也就是connection会自动提交的,从上面的代码看,每当执行完插入操作后,会关闭Connection,这里不是真正的关闭,起始这里得到的Connection是一个代理对象,这里的close()方法也就是把Connection还给连接池,标记为空闲,供其他请求使用,再还给连接池之前,connection把数据提交到数据库了,spring的事务是无法回滚的;
我尝试着在上面的代码获取到connection后加了一行代码,con.setAutoCommit(false);让connection不自动提交,让spring管理,可是测试结果是数据根本无法持久化到数据库,所以我感觉要使用spring的事务管理数据源,这种代码实现方式是行不通的,所以我在代码中引入了JdbcTemplate:
从新定义一个dao层的基类(测试代码,就不定义dao接口层了,并且在基类里只实现了一个insert方法,如有需要可以自己实现):
public class BaseDao extends JdbcDaoSupport { public void insert(String sql) { this.getJdbcTemplate().execute(sql); } }
子类dao:
public class BaseDaoImipl extends BaseDao { }
在spring配置文件中的配置:
<context:annotation-config /> <!-- 使用annotation 自动注册bean,并保证@Required,@Autowired的属性被注入 --> <context:component-scan base-package="com.intel.store"/> <!-- enable the configuration of transactional behavior based on annotations --> <tx:annotation-driven transaction-manager="txManager"/> <!-- a PlatformTransactionManager is still required --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- (this dependency is defined somewhere else) --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 被事务管理的数据源 --> <bean id="baseDao" class="com.intel.store.dao.BaseDao" abstract="true"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置继承 --> <bean id="baseDaoImpl" class="com.intel.store.dao.BaseDaoImipl" parent="baseDao"> </bean>
这里使用基类baseDao是为了提取公共操作,把重复的CRUD封装到基类中,子类只要调用即可;
测试代码:
//junit测试代码 @Test public void testTransaction() { System.out.println(AopUtils.isAopProxy(loginServiceImpl)); System.out.println(loginServiceImpl.getClass().getName()); loginServiceImpl.saveLoginInfo("2000040", "2013-09-10 00:00:00.000", "2000040|张美霞test"); } //service方法中的业务方法 @Transactional(propagation = Propagation.REQUIRED) public void saveLoginInfo(String userName, String dateString, String result) { String sql = "INSERT INTO prc_mbl_usr_usg (slsprs_id, lgn_dtm, lgn_sts ) VALUES (" + "'" + userName + "'," + "'" + dateString + "'," + "'" + result + "')"; logger.info(sql); this.baseDaoImpl.insert(sql); int m = 0; // if (m == 0) { // throw new RuntimeException("出错了!"); // } this.baseDaoImpl.insert(sql); }
放开注释,第一次插入的数据正常回滚,不放开注释,两条数据顺利插入到数据库!