I have a challenge where I need to read "unprocessed" data from an SQL Server database, process the data, then selectively update two to six tables in a DB2 database and then mark that data as processed in the original database on SQL Server. At any point, should anything fail, I want all the updates to rollback. If I have 10 unprocessed items and 9 are good but one fails I still want the 9 good ones to complete and the tenth one to return to it's original state until we can research the problem and make a correction.
The overall architecture is that one input instance may result in inserts into at least 3 DB2 tables and as many as 7 tables. Several of the DB2 tables could end up with multiple inserts from one input. I would have to develop a different writer for each table update and figure out how to pass to each writer the specific data necessary for that table. I need also to utilize 2 data sources for updates to DB2 and SQL Server, respectively.
I am not an experienced Spring Batch developer. And I seldom have a project where I can "read 1, process 1, write 1" and repeat. Usually I need to read several files/databases, process that data, then write to one or more reports, files and/or databases. I see where support is provided for this sort of application but it is more complex and takes more research, with limited examples to be found.
In my attempt to implement a solution I took the easy road. I developed a class that implements Tasklet and wrote the code the way my real-time process works. It fetches the input data from SQL using JDBCTemplate then passes the data to code which processes the data and determines what needs to be updated. I have a Transaction Manager class that implements @Transactional with REQUIRES_NEW and rollbackFor my custom unchecked exception. The Transactional class catches all DataAccessException events and will throw the custom exception. At the moment I am only using the DB2 data source so as not to over-complicate the situation.
In my testing I added code at the end of the update process which throws an unchecked exception. I expected the updates to be rolled back. But it did not happen. If I re-run the process I get 803 errors on DB2.
One last thing. In our shop we are required to use Stored Procedures on DB2 for all access. So I am using SimpleJdbcCall to execute the SP's.
Here is my code:
The main java class for the Tasklet:
public class SynchronizeDB2WithSQL implements Tasklet
{
private static final BatchLogger logger = BatchLogger.getLogger();
private Db2UpdateTranManager tranMgr;
public void setTranMgr(Db2UpdateTranManager tranMgr) {
this.tranMgr = tranMgr;
}
private AccessPaymentIntegrationDAO pmtIntDAO;
public void setPmtIntDAO(AccessPaymentIntegrationDAO pmtIntDAO) {
this.pmtIntDAO = pmtIntDAO;
}
@Override
public RepeatStatus execute(StepContribution arg0, ChunkContext arg1) throws Exception {
logger.logInfoMessage("=============================================");
logger.logInfoMessage(" EB0255IA - Synchronize DB2 with SQL");
logger.logInfoMessage("=============================================");
List<UnprocessedPaymentDataBean> orderList = this.pmtIntDAO.fetchUnprocessedEntries();
if(CollectionUtils.isNotEmpty(orderList)) {
for(UnprocessedPaymentDataBean ent: orderList) {
logger.logDebugMessage(" Processing payment ");
logger.logDebugMessage(ent.toString());
Map<String, List<PaymentTransactionDetailsBean>> paymentDetails = arrangePayments(this.pmtIntDAO.getDetailsByOrder(ent.getOrderNbr()));
try {
this.tranMgr.createNewAuthorizedPayment(ent, paymentDetails);
} catch (DataException e) {
logger.logErrorMessage("Encountered a Data Exception: "+e);
}
}
} else {
logger.logInfoMessage("=============================================");
logger.logInfoMessage("No data was encountered that needed to be processed");
logger.logInfoMessage("=============================================");
}
return RepeatStatus.FINISHED;
}
And the Spring Batch xml:
<job id="EB0255IA" parent="baseJob" job-repository="jobRepository"
xmlns="http://www.springframework.org/schema/batch" restartable="true"
incrementer="parameterIncrementer">
<description>Job to maintain the DB2 updates for payment activity</description>
<step id="SynchronizeDB2WithSQL">
<tasklet ref="synchronizeTasklet" />
</step>
</job>
<bean id="synchronizeTasklet" class="com.ins.pmtint.synchdb2.SynchronizeDB2WithSQL" >
<property name="pmtIntDAO" ref="pmtIntDAO" />
<property name="tranMgr" ref="db2TranMgr" />
</bean>
<bean id="jdbcUpdateDB2" class="com.ins.pmtint.db.JDBCUpdateDB2">
<property name="dataSource" ref="dataSourceBnkDB2" />
</bean>
<bean id="updateDB2DataDAO" class="com.ins.pmtint.db.dao.UpdateDB2DataDAOImpl">
<property name="jdbcUpdateDB2" ref="jdbcUpdateDB2" />
</bean>
<bean id="db2TranMgr" class="com.ins.pmtint.db.tranmgr.Db2UpdateTranManagerImpl">
<property name="updateDB2DataDAO" ref="updateDB2DataDAO" />
</bean>
<bean id="jdbcPaymentIntegration" class="com.ins.pmtint.db.JDBCPaymentIntegration" >
<property name="dataSource" ref="dataSourcePmtIntegration" />
</bean>
<bean id="pmtIntDAO" class="com.ins.pmtint.db.dao.AccessPaymentIntegrationDAOImpl">
<property name="jdbcPaymentIntegration" ref="jdbcPaymentIntegration" />
</bean>
Part of the transaction manager implementation.
public class Db2UpdateTranManagerImpl implements Db2UpdateTranManager, DB2FieldNames {
private static final BatchLogger logger = BatchLogger.getLogger();
UpdateDB2DataDAO updateDB2DataDAO;
public void setUpdateDB2DataDAO(UpdateDB2DataDAO updateDB2DataDAO) {
this.updateDB2DataDAO = updateDB2DataDAO;
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false, rollbackFor = DataException.class)
public void createNewAuthorizedPayment(UnprocessedPaymentDataBean dataBean, Map<String, List<PaymentTransactionDetailsBean>> paymentDetails) {
logger.logDebugMessage("At Db2UpdateTranManagerImpl.createNewAuthorizedPayment(");
logger.logDebugMessage(dataBean.toString());
String orderNbr = String.valueOf(dataBean.getOrderNbr());
String eventCode = TranTypeCode.fromValue(dataBean.getTransactionTypeCode()).getDB2Event();
if(eventCode == null) {
try {
KFBDistBatchEMail.createAndSendMessage("There is no event code for current entry\n\nOrder: "+orderNbr+" Tran type: "+dataBean.getTransactionTypeCode(), "EB0255IA - Database error" ,EnhancedPropertyPlaceholderConfigurer.getEmailFrom(), EnhancedPropertyPlaceholderConfigurer.getEmailTo(), null);
throw new DataException("Update failed: No event code to apply");
} catch (EMailExcpetion e2) {
logger.logErrorMessage("Generating email", e2);
}
}
String orginatingSystemId;
if (dataBean.getPaymentTypeCode().equalsIgnoreCase("EFT"))
orginatingSystemId = "FS";
else
orginatingSystemId = "IN";
try {
if(dataBean.getTransactionTypeCode().equalsIgnoreCase("A")) {
this.updateDB2DataDAO.updatePaymentDetails(orderNbr, DB_INITIAL_EVENT_CODE, "", dataBean.getTransactionAmt(), orginatingSystemId);
}
**** FOR TESTING - AT THE END I HAVE ADDED ****
throw new DataException("I finished processing and backed out. \n\n"+dataBean);
}
And this is part of the JDBC code:
public class JDBCUpdateDB2 extends JdbcDaoSupport
implements DB2FieldNames
{
private static final BatchLogger logger = KFBBatchLogger.getLogger();
public void updatePaymentDetails(String orderNbr, String eventCd, String authnbr, Double amount, String orginatingSystemId) {
SimpleJdbcCall jdbcCall = new SimpleJdbcCall(getDataSource()).withSchemaName(EnhancedPropertyPlaceholderConfigurer.getDB2Schema()).withProcedureName(UPDATE_PAYMENT_TRANSACTION_DB2_PROC);
MapSqlParameterSource sqlIn = new MapSqlParameterSource();
sqlIn.addValue(SP_BNKCRD_PMT_ORD_NBR, orderNbr);
sqlIn.addValue(SP_CLUSTERING_NBR_2, new StringBuilder(orderNbr.substring(Math.max(orderNbr.length() - 2, 0))).reverse().toString());
sqlIn.addValue(SP_BNKCRD_EVNT_CD, eventCd);
sqlIn.addValue(SP_CCTRAN_ERR_CD, "N");
sqlIn.addValue(SP_BNKCRD_PROC_RET_CD, "");
sqlIn.addValue(SP_BNKCRD_AUTH_CD, "G");
sqlIn.addValue(SP_ORIG_SYS_ID_TXT, orginatingSystemId);
sqlIn.addValue(SP_BNKCRD_TRAN_AMT, amount);
try {
jdbcCall.execute(sqlIn);
} catch (DataAccessException e) {
logger.logErrorMessage("Database error in updatePaymentDetails", e);
throw e;
}
}