We are migrating an application from IBM WebSphere Traditiona 8.5 to OpenLiberty 19.0.0.12 (Both with IBM JDK 8).
In the process we want to get rid of XA transactions, are there is only one method that is actually using two different data sources, but only write to second data source (first one is used only for reading).
So imagine something like that :
@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 {
...
}
}
}
Keep in mind that there are various read (from ds1) and write (to ds2) but they are mixed in a complex method, so splitting the transaction would need a deep refactoring that we would avoid right now.
But we get this error :
J2CA0030E: Method enlist caught java.lang.IllegalStateException: Illegal attempt to enlist multiple 1PC XAResources
Is there any way to telle transaction manager 2PC is not needed without any major refactoring of the code?
Thanks in advance.
Edit after solution was found (2020-01-11):
We created before a little POC of what we needed and then we tried to solution of just changing transaction management :
@TransactionManagement(TransactionManagementType.BEAN)
Then we managed to apply it to the real usecase and seems all is warking properly. We post the POC in case could be useful to some for testing :
@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;
}
}