繁体   English   中英

如果在事务中我们只在一个数据源上写入,是否可以避免 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)

然后我们设法将它应用到真正的用例中,似乎一切正常。 我们发布 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 管理的事务,在这种情况下,您的应用程序可以决定何时何地开始/提交。

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

执行此操作后,其余代码可能会正常(依赖于 JDBC 驱动程序的自动提交),或者您可能希望根据应用程序代码(未显示)中的需要决定手动开始和结束事务的位置。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM