Spring Junit4 Test中JDBC事务回滚抛异常Connection is null

Spring Junit 对 JDBC Test的支持, 如下设置可以方便的对数据库操作进行回滚,以免扰乱数据库数据。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-context.xml")
@Transactional(value = "txManager", rollbackFor = { Exception.class })
public class StudentDaoImplTest {

    @Autowired
    StudentDao studentDao;
    /**
     * Test method for
     * {@link com.chengmaoning.jroad.jdbc.dao.impl.StudentDaoImpl#findAllStudents()}.
     */
    @Test
    public void testFindAllStudents() {
        List<Student> students = studentDao.findAllStudents();
        System.out.println(students);
        Assert.isTrue(true, null);
    }
...

所有test方法默认都是@Rollback(true),可以在类定义前或者方法定义前通过@Commit改变回滚策略。

ps:事务回滚除了需要指定事务管理器txManager,还需要StudentDao studentDao的实现类是基于JdbcTemplate的,更严格的说,JDBC操作所在的数据库连接Connection是关联到事务的,或者说被txManager管理的()Low-level synchronization approach

所以,如果我们的代码是直接使用the traditional JDBC approach of calling the getConnection() method on the DataSource,那么这部分代码对数据库的操作是不会被回滚的,因为数据库连接没有被事务管理器管理,怎么才能得到被事务管理器管理起来的数据库连接呢?you instead use Spring’s org.springframework.jdbc.datasource.DataSourceUtils class as follows:

Connection conn = DataSourceUtils.getConnection(dataSource);

如下例子,批量插入并返回批量id列表(因为JdbcTemplate 不支持批量插入并批量返回id列表 :disappointed:):

public List<Long> batchInsert(List<Student> students) {
        List<Long> ids = new ArrayList<>();

        Connection connection = null;
        try {
            // 这种方式才能做事务,关联到txManager
            connection = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
            PreparedStatement statement = connection.prepareStatement("insert into student(name, age) values (?, ?)",
                    new String[] { "id" });
            for (Student student : students) {
                statement.setString(1, student.getName());
                statement.setInt(2, student.getAge());
                statement.addBatch();
            }
            statement.executeBatch();
            ResultSet rs = statement.getGeneratedKeys();
            while (rs.next()) {
                ids.add(rs.getLong(1));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            /**
             * 不必手动关闭connection,测试框架会在扫尾工作中关闭connection
             */
            // if (connection != null) {
            // try {
            // connection.close();
            // } catch (SQLException e) {
            // e.printStackTrace();
            // }
            // }
        }
        return ids;
    }

注意代码最后的注释部分,如果在这里手动关闭连接(不注释)的话,在测试用例中就会报错:

    @Test
    public void testBatchInsert() {
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 2; i++) {
            Student student = new Student();
            student.setName("student_" + i);
            student.setId(8 + i);
            students.add(student);
        }
        List<Long> ids = studentDao.batchInsert(students);
        System.out.println(ids);
    }

异常堆栈如下,但是查看数据库确实最终没有写入,也就是说事务是回滚了的。

Aug 07, 2017 12:08:11 AM org.springframework.test.context.TestContextManager afterTestMethod
WARNING: Caught exception while allowing TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener@2d6e8792] to process 'after' execution for test: method [public void com.chengmaoning.jroad.jdbc.dao.impl.StudentDaoImplTest.testBatchInsert()], instance [com.chengmaoning.jroad.jdbc.dao.impl.StudentDaoImplTest@1b2abca6], exception [null]
org.springframework.transaction.TransactionSystemException: Could not roll back JDBC transaction; nested exception is java.sql.SQLException: Connection is null.
    at org.springframework.jdbc.datasource.DataSourceTransactionManager.doRollback(DataSourceTransactionManager.java:331)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:853)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager.java:830)
    at org.springframework.test.context.transaction.TransactionContext.endTransaction(TransactionContext.java:125)
    at org.springframework.test.context.transaction.TransactionalTestExecutionListener.afterTestMethod(TransactionalTestExecutionListener.java:227)
    at org.springframework.test.context.TestContextManager.afterTestMethod(TestContextManager.java:319)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:94)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.sql.SQLException: Connection is null.
    at org.apache.commons.dbcp2.DelegatingConnection.checkOpen(DelegatingConnection.java:612)
    at org.apache.commons.dbcp2.DelegatingConnection.rollback(DelegatingConnection.java:490)
    at org.springframework.jdbc.datasource.DataSourceTransactionManager.doRollback(DataSourceTransactionManager.java:328)
    ... 25 more

根据异常信息,只需要把最后手动关闭数据库连接的代码注释即可。但是问题又来了,如果不考虑测试用例,正常代码执行逻辑中,connection不是应该在finally中释放么?
这可能是测试框架的一个bug?未来得及深入debug跟进,大神们怎么看?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值