Announcement Announcement Module
Collapse
No announcement yet.
Spring @Transaction doesn't work Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring @Transaction doesn't work

    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.

  • #2
    Works as expected... I suggest a read of the AOP chapter of the spring reference guide.

    In short spring uses proxies to apply AOP which results in only method calls INTO the object are intercepted, internal method calls, like yours, aren't intercepted.

    Comment


    • #3
      Could you be more concrete?

      Comment


      • #4
        Can anybody help to solve issue?

        Comment


        • #5
          I already gave you the solution, or at least the answer that leads you to the solution...

          Spring cannot intercept internal method calls, either place your annotation somewhere else or make it an external method call. And as mentioned in my original reply use the forum search and read the reference guide.

          Comment

          Working...
          X