如何在Groovy中回滚事务

4

我在我的测试用例中无法使用@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是我一直在寻找的内容。
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();

这将不起任何作用。

那么,如果您必须从应用程序服务器获取DB连接,因为您有组织标准要求您从由服务器管理员定义的应用程序服务器管理的连接池中获取它们,该怎么办呢?例如,对于为诸如IBM WAS等常见应用程序服务器开发应用程序的人来说,这种情况经常发生。因此,不能使用基本的JDBC代码创建具有硬编码引用的连接,而是可以仅使用一个硬编码或更好地存储在属性文件或数据库中的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对象是由一个连接创建的,那么这个方法就会提交连接。如果这个SQL对象是从数据源创建的,则此方法不执行任何操作。 Throws: java.sql.SQLException-如果发生数据库访问错误

rollback public void rollback() throws java.sql.SQLException 如果这个SQL对象是由一个连接创建的,那么这个方法将回滚连接。如果这个SQL对象是从数据源创建的,则此方法不执行任何操作。 Throws: java.sql.SQLException-如果发生数据库访问错误

人生中另一个需要思考的谜题...希望这可以帮到你。


0

我发现 Matt 给出的答案是可行的。唯一需要注意的是,Sql.closeResources(Connection) 在执行方法以及查询调用之后都会被调用,并且将关闭连接,除非你在 Sql.withTransactionSql.cacheConnection 中。

这意味着你确实可以获得事务感知,但仅限于在运行在 Sql.withTransactionSql.cacheConnection 内部的闭包中。我正在尝试使用带有完整 Web 服务线程的事务感知 DAO,在使用 groovy.sql.Sql 的过程中进行协调。

如果我有一个 Jersey 端点和组件,则理想情况下,我不应该将事务范围限制在 DAO 层特定块中,因为那里是我们 groovy.sql.Sql 实例的所在地;因此,我认为依赖于 Sql.withTransaction 是无法满足事务感知/回滚能力的不足解决方案。

参考: Github Sql.java source 你会注意到很多方法,比如执行后会调用closeResources,它只会跳过关闭连接if (cacheConnection)。因此,只有通过withTransaction或者cacheTransaction执行闭包块才能使用commit()rollback(),这是作者的意图。虽然这样做没问题,但是存在启动和结束事务与整个 Web 服务请求作用域匹配的限制。因此,我的解决方案是覆盖closeResources,因为该类作者们慷慨地提供了这种覆盖功能。覆盖它以不基于事务活动关闭连接。

换句话说,如果你已经在其他地方实现了事务管理处理这个连接,请使用无操作实现来实现closeResources;否则,不要覆盖实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接