如果在一个事务中我们只写一个数据源,是否可以避免 2PC 或手动处理提交? (J2CA0030E)

If in a transaction we do write only on one data source, is it possibile to avoid 2PC or handle commint manually? (J2CA0030E)

我们正在将应用程序从 IBM WebSphere Traditiona 8.5 迁移到 OpenLiberty 19.0.0.12(均使用 IBM JDK 8)。

在我们想要摆脱XA事务的过程中,是否只有一种方法实际上使用了两个不同的数据源,但只写入第二个数据源(第一个仅用于读取)。

想象一下这样的事情:

@Path("/path_to_my_service")
@Stateless
public class MyService extends ByBaseService {

  private static final long serialVersionUID = 3470660101451196317L;

  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  public Response create(Model model) {
    try ( Connection connectionFromDataSourceOne = ...;
         Connection connectionFromDataSourceTwo = ... ) {
    // performs some reading from connectionFromDataSourceOne
    // try to performe writing on connectionFromDataSourceTwo
    ) catch () {
        ...
    } finally {
        ...
    }
  }

}

请记住,有多种读取(从 ds1)和写入(到 ds2),但它们混合在一个复杂的方法中,因此拆分事务需要进行深度重构,而我们现在将避免这种重构。

但是我们得到这个错误:

J2CA0030E:捕获方法登记java.lang.IllegalStateException:非法尝试登记多个 1PC XAResources

如果不对代码进行任何重大重构,有什么方法可以告诉我们不需要事务管理器 2PC?

提前致谢。

找到解决方案后编辑(2020-01-11):

我们之前创建了一些我们需要的 POC,然后我们尝试解决仅更改事务管理的问题:

@TransactionManagement(TransactionManagementType.BEAN)

然后我们设法将它应用到实际用例中,看起来一切正常。我们 post POC 以防对某些测试有用:

@Stateless
@Path("/test/transaction")
@TransactionManagement(TransactionManagementType.BEAN)
public class TransactionTestRest implements Serializable {

private static final long serialVersionUID = -2963030487875284408L;

private static final Logger logger = LoggerFactory.getLogger( TransactionTestRest.class );

@Resource(lookup = DataConsts.DS1_JNDI, name = DataConsts.DS1_NAME )
private DataSource ds1;

@Resource(lookup = DataConsts.DS2_JNDI, name = DataConsts.DS2_NAME )
private DataSource ds2;

private Properties baseProperties() {
    Properties props = new Properties();
    props.setProperty( "testVersion" , "3" );
    return props;
}

@GET
@Path("/conntest_all")
@Produces(MediaType.APPLICATION_JSON)
public Response testAll() {
    Response res = null;
    try ( Connection conn1 = this.ds1.getConnection();
            Connection conn2 = this.ds2.getConnection() ) {
        Properties props = this.baseProperties();
        props.setProperty( "testConn1" , conn1.getMetaData().getUserName() );
        props.setProperty( "testConn2" , conn2.getMetaData().getUserName() );
        conn2.setAutoCommit( false );   
        try ( Statement stm1 = conn1.createStatement();
                ResultSet rs1 = stm1.executeQuery( "SELECT * FROM test_ds1" );
                PreparedStatement pstm2 = conn2.prepareStatement( "INSERT INTO test_ds2 ( ID ) VALUES ( ? )" ) ) {
            while ( rs1.next() ) {
                BigDecimal id = rs1.getBigDecimal( "ID" );
                pstm2.setBigDecimal( 1 , id );
                pstm2.executeUpdate();
            }
            conn2.commit();
            props.setProperty( "result" , "OK!");
        } catch (Exception ie)  {
            props.setProperty( "result" , "Error:"+ie );
            conn2.rollback();
        } finally {
            conn2.setAutoCommit( true );
        }
        res =  Response.ok( props ).build();
    } catch (Exception e) {
        logger.error( "Error on conntest_all "+e, e );
        res = Response.status( Response.Status.INTERNAL_SERVER_ERROR ).build();
    }

    return res;
}

@GET
@Path("/conntest_1")
@Produces(MediaType.APPLICATION_JSON)
public Response test1() {
    Response res = null;
    try ( Connection conn1 = this.ds1.getConnection();) {
        Properties props = this.baseProperties();
        props.setProperty( "testConn1" , conn1.getMetaData().getUserName() );
        res =  Response.ok( props ).build();
    } catch (Exception e) {
        logger.error( "Error on conntest_1 "+e, e );
        res = Response.status( Response.Status.INTERNAL_SERVER_ERROR ).build();
    }
    return res;
}

@GET
@Path("/conntest_2")
@Produces(MediaType.APPLICATION_JSON)
public Response test2() {
    Response res = null;
    try ( Connection conn2 = this.ds2.getConnection();) {
        Properties props = this.baseProperties();
        props.setProperty( "testConn2" , conn2.getMetaData().getUserName() );
        res =  Response.ok( props ).build();
    } catch (Exception e) {
        logger.error( "Error on conntest_2 "+e, e );
        res = Response.status( Response.Status.INTERNAL_SERVER_ERROR ).build();
    }
    return res;
}   

}

根据 @Stateless 注释,您似乎正在使用无状态会话 EJB,并且可能以围绕两个连接的容器管理事务结束,导致单个事务中有两个资源,这需要两阶段提交。

如果不希望在同一个事务中拥有这些连接,请考虑从容器管理的事务切换到 bean 管理的事务,在这种情况下,您的应用程序可以决定何时何地 begin/commit。

import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
...
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class MyService extends ByBaseService {
...

完成此操作后,其余代码可能就没问题了(依赖于 JDBC 驱动程序的自动提交),或者您可能希望根据应用程序代码中的需要决定在何处手动开始和结束事务 (未显示)。