4

我不能在我的 TestCase 中使用 @Transactional 注释。我有解决方法 - 直接使用 TransactionalManager。不幸的是,当我基于 SpringContext 中的 DataSource 在 groovy 中创建 Sql 对象然后向数据库中插入一行时,它不会回滚。

@ContextConfiguration(locations = [ "../dao/impl/ibatis/spring-data-context-config.xml"])
@RunWith(SpringJUnit4ClassRunner.class)
public class OrganizationTest {

@Autowired
DataSource dataSource;

@Autowired
DataSourceTransactionManager transactionManager;

private TransactionStatus transactionStatus;

@Before
public void setUp() {
    transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
}
@After
public void tearDown() {
    transactionManager.rollback(transactionStatus);
    transactionStatus = null;
}


@Test
public void shallObtainSequenceNo() throws Exception {

    Connection connection = dataSource.getConnection();
    connection.setAutoCommit(false);
    Sql sql = new Sql(dataSource);

    //given
    Organization organization = new Organization("KongregatzionIX", "bisut000000000000001");
    //when
    organization.insert(sql);
    //then
    assertNotNull(organization.getId());
    }
}

SQL 查询如下所示:

public class Organization {

String name;
String id;
String parentId;

Organization(String name, String parentId){
    this.name = name;
    this.parentId = parentId;
}

public void insert(Sql sql){
    String createdBy = GlobalConstant.SABA_ADMIN_ID.getValue();
    String updatedBy = GlobalConstant.SABA_ADMIN_ID.getValue();
    String companyType = "2";
    String flags = "1000000000";

    id = sql.firstRow( "select 'bisut' || LPAD(TPT_COMPANY_SEQ.NEXTVAL,  15, '0') as id from dual ").id;

    def timeStamp = sql.firstRow("select  to_char(SYSTIMESTAMP, 'YYYYMMDDHH24MISSFF') as ts FROM DUAL ").ts;
    def nameIns = name;
    def today = new java.sql.Date(new Date().getTime());
    sql.executeInsert('''
                INSERT INTO TPT_COMPANY(ID, TIME_STAMP, CREATED_BY, CREATED_ON, UPDATED_BY, UPDATED_ON, CI_NAME, NAME, CI_NAME2, NAME2, COMPANY_TYPE, FLAGS, PARENT_ID)
                VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
                ''' ,
                [id, timeStamp, createdBy, today, updatedBy, today, nameIns.toLowerCase(), nameIns, nameIns.toLowerCase(), nameIns, companyType, flags, parentId]);
 }
}

当然,我想设置跨越所有测试方法的事务。

// 编辑

由于声誉太小,我无法回答,但 TransactionAwareDataSourceProxy 是我一直在寻找的。

4

2 回答 2

15

好的,对于任何读到这篇文章的人来说,他们的头一直在敲击键盘,准备好尖叫吧。这里是:使用 DataSource 创建的 Groovy Sql 对象,如下所示:

DataSource dsobj = null;
Sql sqlobj = null;

try{
  dsobj = (...get your DataSource obj...);
  sqlobj = new Sql(dsobj);
} catch (...) {...}

当然会给你一个有效的 Sql 对象,你可以使用它运行命令等,但你不会得到事务支持。然而...

如果您使用 Connection 对象创建 Sql 对象,您将获得该事务支持。现在,为什么会这样,我不知道,因为您可以使用以下命令访问 DataSource 对象中包含的 Connection 对象:sqlobj.getDataSource().getConnection() 但是,如果您尝试这样做:

sqlobj.getDataSource().getConnection().setAutoCommit(false);

它的影响为零。无论您喜欢与否,您运行的任何命令都将自动提交。同样,调用:

sqlobj.getDataSource().getConnection().rollback();

什么都不做。

那么,如果您必须从应用程序服务器获取数据库连接,因为您有组织标准,例如,坚持要求您从应用程序服务器管理的连接池中获取它们,并且数据源由服务器管理员定义,您会怎么做?对于为 IBM WAS 等常见应用程序服务器开发应用程序的人来说,发生了很多事情。因此,当您只能使用一个硬编码或更好的是,JNDI 数据源的属性文件存储或数据库存储名称。你现在要做什么?您似乎必须使用 DataSource 对象,而不享受任何现代 RDBMS 中最常见和最有价值的特性,即事务。

这是解决方法,在这里你真的需要准备好尖叫,因为它完全没有意义,但它确实有效。您需要像上面一样创建一个 DataSource 对象,然后在 Sql 对象的构造函数中使用其 Connection 对象,而不是 DataSource 对象。尽管这很荒谬,但这是解决办法。例子:

DataSource dsobj = null;
Sql sqlobj = null;

try{
  dsobj = (...get your DataSource obj...);
  sqlobj = new Sql(dsobj.getConnection());
} catch (...) {...}

现在,您将使用 sqlobj 来执行事务敏感的工作。您可以将 AutoCommit 设置为 false [sqlobj.getConnection().setAutoCommit(false)],运行更新,然后根据需要执行 sqlobj.getConnection().commit() 或 sqlobj.getConnection().rollback()。

似乎 dsobj 可以超出 sqlobj 的直接范围,并且 sqlobj 仍将起作用。我推测 VM 足够聪明,只要 sqlobj 在范围内,就可以让 sqlobj 保持活动状态。这是有道理的,因为 dsobj 的 Connection 对象无疑被视为服务器管理的资源,因此任何附加它的对象都会保持活动状态,直到引用封闭订阅对象(即 sqlobj)的所有其他对象最终超出范围. 只是一个猜测。

那么,这个问题的确凿证据在哪里?它在这里:

http://groovy.codehaus.org/api/groovy/sql/Sql.html#commit()

我引用:

commit public void commit() throws java.sql.SQLException 如果此 SQL 对象是使用 Connection 创建的,则此方法提交连接。如果此 SQL 对象是从 DataSource 创建的,则此方法不执行任何操作。 抛出:java.sql.SQLException - 如果发生数据库访问错误

rollback public void rollback() throws java.sql.SQLException 如果此 SQL 对象是使用 Connection 创建的,则此方法会回滚连接。如果此 SQL 对象是从 DataSource 创建的,则此方法不执行任何操作。 抛出:java.sql.SQLException - 如果发生数据库访问错误

只是生活中需要思考的另一个谜团……希望这会有所帮助。

于 2013-05-31T18:12:39.687 回答
0

我发现马特的回答有效。唯一需要注意的是,Sql.closeResources(Connection)它在执行方法和查询调用之后调用,除非您在Sql.withTransactionor中,否则将关闭连接Sql.cacheConnection

这意味着您肯定会获得事务感知,但只能Sql.withTransaction在or内部运行的闭包中Sql.cacheConnection。我正在尝试在groovy.sql.SqlWeb 服务调用的整个线程期间使用事务感知来协调 Web 服务中的 DAO,就像 Spring 或 JEE 事务管理所做的那样。

如果我有一个 jersey 端点和理想下的组件,我不应该将我的事务范围限制在 DAO 层特定的块中,因为那是我们的groovy.sql.Sql实例所在的位置;因此,为什么我发现依赖于Sql.withTransaction交易感知/回滚能力的不足解决方案。

供参考: Github Sql.java 源码 你会注意到很多方法,例如executeUpdate在执行后会调用closeResources只跳过关闭连接的方法if (cacheConnection);因此,只有通过执行闭包块,您才能使用withTransaction或使用,我相信这是作者的意图。这很好,除了启动和结束与 Web 服务请求的整个范围匹配的事务的限制。这是一个警告,我的解决方案是重写,因为这个类的作者慷慨地使它可以重写。覆盖它以不关闭基于事务活动的连接。cacheTransactioncommit()rollback()closeResources

换句话说,如果您有事务管理以某种方式处理此连接,则在其他地方closeResources使用无操作实现;否则,不要覆盖实现。

于 2015-12-11T18:27:49.543 回答