I'm developing 2-in-1 application: Web Service and Web UI for administration. I use the following in my application:
- Spring Framework 3.1
- MyBatis-Spring integration
- Apache CXF for Web Services
- AspectJ and SLF4J over LOG4J for logging
Different servlet mappings are mapped to CXFServlet and DispatcherServlet.
main-servlet.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
<mvc:annotation-driven>
...
</mvc:annotation-driven>
<context:component-scan base-package="com.llth.paymentgateway.web"/>
<context:annotation-config/>
<bean id="aopLogger" class="com.llth.paymentgateway.aspects.AopLogger" />
<aop:aspectj-autoproxy/>
<mvc:interceptors>
...
</mvc:interceptors>
...
</beans>
applicationContext.xml
Code:
<beans...>
<context:component-scan base-package="com.llth.paymentgateway.service"/>
<bean class="com.llth.paymentgateway.service.GeneralServiceImpl" name="generalService"/>
<context:property-placeholder properties-ref="config" />
<context:annotation-config/>
<aop:aspectj-autoproxy/>
<bean id="aopLogger" class="com.llth.paymentgateway.aspects.AopLogger" />
...
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceAzis"/>
</bean>
<tx:annotation-driven/>
<bean id="sqlSessionFactoryAzis" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSourceAzis"/>
<property name="typeAliasesPackage" value="com.llth.paymentgateway.domain"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.llth.paymentgateway.dao"/>
<property name="annotationClass" value="com.llth.paymentgateway.repositories.AzisRepository"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryAzis"/>
</bean>
<beans profile="dev">
<jee:jndi-lookup id="dataSourceAzis" jndi-name="java:comp/env/jdbc/ApsuserAtAzistst" resource-ref="true" />
<bean id="config" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="/WEB-INF/applicationConfig-dev.properties"/>
</bean>
</beans>
...
</beans>
Web Service method implementation class placed in "com.llth.paymentgateway.webservice" and calls method from GeneralService
Code:
@WebService(endpointInterface = "com.llth.paymentgateway.webservice.PaymentGatewayWebService")
public class PaymentGatewayWebServiceImpl implements PaymentGatewayWebService {
@Autowired
private GeneralService generalService;
//...
@Override
public WebServiceResponse makeAdvancePayment(@WebParam(name = WebParamNames.REQUEST_ID) String requestId,
@WebParam(name = WebParamNames.MSISDN) String msisdn,
@WebParam(name = WebParamNames.AMOUNT) Integer amount,
@WebParam(name = WebParamNames.RESERVED) String reserved) {
return generalService.makePayment(...);
}
//...
}
GeneralServiceImpl (at "com.llth.paymentgateway.service")
Code:
public class GeneralServiceImpl implements GeneralService {
@Autowired
private PaymentService paymentService;
@Autowired
private RequestOperationService requestOperationService;
@Autowired
private ValidatorService validatorService;
public WebServiceResponse webServiceMethod(ValidateAndProcess validateAndProcessContainer, RequestType requestType, String requestId, User currentUser, Object... args) {
WebServiceResponse response = new WebServiceResponse();
Integer responseCode = Constants.SUCCESS_RESPONSE_CODE;
final String methodName = requestType.getMethodName();
Integer localRequestId = null;
try {
this.requestOperationService.logToPlsqlLog(PlsqlLogTypes.INFO, this, methodName, requestId, args);
validateAndProcessContainer.checkMethodAndBillingStatus();
validateAndProcessContainer.validate();
localRequestId = this.requestOperationService.insertWebServiceRequest(requestType, requestId, currentUser);
validateAndProcessContainer.process(response);
this.requestOperationService.changeRequestStatus(localRequestId, RequestStatusType.SUCCESS);
} catch (Exception e) {
responseCode = getResponseCodeOfException(e);
if (responseCode == ExceptionConstants.INVALID_RECORDS_IN_FILE) {
response.setLineNumbersWithInvalidRecords(e.getMessage());
}
this.requestOperationService.logToPlsqlLogExt(PlsqlLogTypes.ERROR, this, methodName,
Constants.EXCEPTION_OCCURRED + "\n" + e.getMessage(), requestId, args);
changeRequestStatusOnException(localRequestId, responseCode);
}
response.setResponseCode(responseCode);
this.requestOperationService.logToPlsqlLogExt(PlsqlLogTypes.INFO, this, methodName, Constants.END_OF_METHOD, requestId, args, response);
return response;
}
@Override
public WebServiceResponse makePayment(final String requestId, final String msisdn, final BigDecimal amount, final PaymentType paymentType,
final User currentUser) {
ValidateAndProcess validateAndProcess = new ValidateAndProcess() {
@Override
public void checkMethodAndBillingStatus() {
...
}
@Override
public void validate() {
GeneralServiceImpl.this.validatorService.validateInsertIntoRbpQueueParameters(requestId, msisdn, amount);
}
@Override
public void process(WebServiceResponse response) {
...
insertIntoRbpQueue(requestId, currentUser, rbpQueueItem, paymentValidator);
}
};
return webServiceMethod(validateAndProcess, RequestType.MAKE_PAYMENT, requestId, currentUser, requestId, msisdn, amount, paymentType, currentUser);
}
// problem is in this method
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED, readOnly = false)
public void insertIntoRbpQueue(String requestId, User currentUser, RbpQueueItem rbpQueueItem, PaymentValidator paymentValidator) {
validatorService.validatePayment(paymentValidator); // just executes procedure which calls only SELECT statements
final int queueId = paymentService.insertIntoRbpQueue(rbpQueueItem); // inserts into payment queue table
requestOperationService.updateWebServiceRequestRbpQueueIdById(requestId, currentUser.getBankId(), queueId); // updates table with request data
}
...
}
validatorService, paymentService and requestOperationService have same architecture and placed in "com.llth.paymentgateway.service". They are interfaces and their implementations which execute DAO (placed in "com.llth.paymentgateway.dao") methods. I'll discribe only one of them:
Code:
public interface RequestOperationService {
void updateWebServiceRequestRbpQueueIdById(String requestId, int bankId, int queueId);
//...
}
and its implementation:
Code:
@Service
@Transactional
public class RequestOperationServiceImpl implements RequestOperationService {
@Autowired
private RequestOperationDaoMapper requestOperationDaoMapper;
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED, readOnly = false)
public void updateWebServiceRequestRbpQueueIdById(String requestId, int bankId, int queueId) {
WebServiceRequest request = new WebServiceRequest();
request.setSrcRequestId(requestId);
request.setSrcBankId(bankId);
request.setRbpQueueId(queueId);
if (requestId != null) {
throw new RuntimeException(); // temporary, for testing of @Transaction annotation
}
this.requestOperationDaoMapper.updateWebServiceRequestRbpQueueIdById(request);
}
}
RequestOperationDaoMapper interface in "com.llth.paymentgateway.dao"
Code:
@AzisRepository
public interface RequestOperationDaoMapper {
@Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY, readOnly = false)
void updateWebServiceRequestRbpQueueIdById(WebServiceRequest request);
}
RequestOperationDaoMapper.xml MyBatis XML mapper file in "com.llth.paymentgateway.dao"
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.llth.paymentgateway.dao.RequestOperationDaoMapper">
<update id="updateWebServiceRequestRbpQueueIdById" parameterType="WebServiceRequest">
UPDATE rbp_ws_requests
SET rbp_queue_id = #{rbpQueueId, javaType=Integer, jdbcType=NUMERIC}
WHERE src_request_id = #{srcRequestId, javaType=String, jdbcType=VARCHAR}
AND src_bank_id = #{srcBankId, javaType=Integer, jdbcType=NUMERIC}
</update>
<!--...-->
</mapper>
Problem is inside "insertIntoRbpQueue" method of GeneralServiceImpl. When "requestOperationService.updateWebServiceRequestRb pQueueIdById" raises an exception Spring Transaction annotation doesn't rollback record inserted by "paymentService.insertIntoRbpQueue". Please help to solve this problem.