0

We have a Spring application version 4.3.3.RELEASE using Hibernate 5.1.0.Final and Java 8 The application has an rmi interface which exposes services to a Java thick client application. The application also exposes Spring Restful Webservices. The service causing us a problemis exposed as an rmi service and a restful ws. Calling the service as RMI works just great but calling the restful ws gives an javax.persistence.TransactionRequiredException as seen below.

I thought I found an answer to this matter here but I can't seem to get things right: Spring @Transactional not working

We'we tried many different things, like:

@Transactional(propagation = Propagation.REQUIRES_NEW)

But I guess the issue here is that I have two spring-config files but not defining the right stuff in my springrest-config.xml.

Does anyone know how to solve this?

Here is the dao method:

@Override
    public void deleteFormReplyForInvitation(Long id) {

        try {
            entityManager.createQuery("DELETE FROM FormReply where id = " + id).executeUpdate();


        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

Here is the service code:

@Override
@Transactional
public String withdrawConsent(final String username, final Patient patient, final String method) {

try {
    List<Invitation> invitations = invitationService.getInvitationsForPatient(patient);
    for (Invitation invitation : invitations) {
        if (invitation.getFormReply() != null) {
            formReplyService.deleteFormReply(invitation.getFormReply().getId());
        }
        if (!invitation.getReminders().isEmpty()) {
            reminderService.deleteReminders(invitation);
        }
        invitationService.deleteInvitation(invitation.getId());
    }

    eventlogService.deleteEventlog(patient);


    EntryKind entryKind = entryKindService.getEntryKind(1);
    eventlogService.addEventlog(patient, eventlogDataDeleted, entryKind, true, username);

    addConsent(patient, method, username);

    patient.setActive(false);
    patientService.saveOrUpdatePatient(patient);

    return "OK";


} catch (Exception e) {
    logger.error("Feil oppstått i withdrawConsent.", e);
    return "ERROR";

}
}

 @Override
    @Transactional
    public String withdrawConsent(final String birthNumber, final String programCode) {
        String encryptedPNR = encryptionService.encryptDBQ(birthNumber);
        Cancertype cancertype = cancertypeService.getCancertypeByCode(programCode);
        Patient aPatient = patientService.getPatient(encryptedPNR, cancertype);

        return withdrawConsent("WebService", aPatient, "Webservice");
    }

Here is the rest controller:

@RequestMapping(value = "/withdrawconsent", method = RequestMethod.GET)
    @Transactional
    String withdrawConsent(@RequestParam("programcode") final String    programcode, @RequestParam("birthnumber") final String birthnumber) throws CancertypeException {
        return consentService.withdrawConsent(birthnumber, programcode);
    }

Here is the spring-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://camel.apache.org/schema/spring/v2.15"
       xmlns:mvc="http://www.springframework.org/schema/mvc"

       xsi:schemaLocation="http://www.springframework.org/schema/beans
                             http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
                             http://www.springframework.org/schema/context
                             http://www.springframework.org/schema/context/spring-context-4.1.xsd
                             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"
       default-autowire="byName">


    <!-- Root Context: defines shared resources visible to all other web components -->
    <context:component-scan base-package="net.krg.proms" />
    <context:annotation-config />



    <!-- Define all property files here. Values will be available in all other spring config files. -->
    <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:db.properties</value>
                <value>classpath:system.properties</value>
            </list>
        </property>
    </bean>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" name="EntityManagerFactory">
        <property name="packagesToScan" value="net.krg.proms" />
        <property name="persistenceUnitName" value="proms"></property>
        <property name="dataSource" ref="dataSource"></property>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="${db.show_sql}" />
                <property name="generateDdl" value="false" />
                <property name="databasePlatform" value="${db.dialect}" />
            </bean>
        </property>

    </bean>

<!-- Values are defined in db.properties -->
<!--<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${db.driver}" />
    <property name="url" value="${db.url}" />
    <property name="username" value="${db.username}" />
    <property name="password" value="${db.password}" />
</bean>

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="packagesToScan">
        <list>
            <value>net.krg.proms.domain.proms</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${db.dialect}</prop>
            <prop key="hibernate.show_sql">${db.show_sql}</prop>
            <prop key="hibernate.hbm2ddl.auto">${db.hbm2ddl.auto}</prop>
        </props>

    </property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" name="TransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"></property>
    <property name="persistenceUnitName" value="proms"/>
</bean>


<bean id="entityManagerFactory2"    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" name="EntityManagerFactory2">
    <property name="packagesToScan" value="net.krg.proms.domain.hdb" />
    <property name="persistenceUnitName" value="hdb"></property>
    <property name="dataSource" ref="dataSource2"></property>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="showSql" value="${db.hdb.show_sql}" />
            <property name="generateDdl" value="false" />
            <property name="databasePlatform" value="${db.hdb.dialect}" />
        </bean>
    </property>

</bean>
<!-- Values are defined in db.properties -->
<!--<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">-->
<bean id="dataSource2" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${db.hdb.driver}" />
    <property name="url" value="${db.hdb.url}" />
    <property name="username" value="${db.hdb.username}" />
    <property name="password" value="${db.hdb.password}" />
</bean>

<bean id="txManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource2"/>
</bean>

<bean id="sessionFactory2" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource2"/>
    <property name="packagesToScan">
        <list>
            <value>net.krg.proms.domain.hdb</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${db.hdb.dialect}</prop>
            <prop key="hibernate.show_sql">${db.hdb.show_sql}</prop>
           <!-- <prop key="hibernate.hbm2ddl.auto">${db.hdb.hbm2ddl.auto}</prop>-->
        </props>

    </property>
</bean>

<bean id="transactionManager2" class="org.springframework.orm.jpa.JpaTransactionManager" name="TransactionManager2">
    <property name="entityManagerFactory" ref="entityManagerFactory2"></property>
    <property name="persistenceUnitName" value="hdb"/>
</bean>

<tx:annotation-driven />
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />


<!--RMI starts Here-->
<!--this is the only bean that should be use expose all the needed RMI sercices which GUI will consume-->
<bean id="rmiServices" class="net.krg.proms.services.RMIServiceImpl"/>
<bean id="remoting_MessageSpecificationService_Proxy_Exporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
    <property name="service" ref="rmiServices"/>
    <property name="serviceInterface" value="net.krg.proms.rmiInterfaces.RMIServices"/>
    <property name="serviceName" value="rmiServices"/>
    <property name="registryPort" value="10998"/>
    <property name="alwaysCreateRegistry" value="false"/>
</bean>
<!--RMI end here-->


<!--include camel configuraiton here -->
<!--load camel context-->
<import resource="camel-config.xml"/>

Here is the config for the spring-restful ws:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=" http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
       http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">

    <mvc:annotation-driven/>
    <context:component-scan base-package="net.krg.proms.services" />
    <context:annotation-config></context:annotation-config>
</beans>

Here is the web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app metadata-complete="true"
         xmlns="http://java.sun.com/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
           version="2.5">


    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:/spring-config.xml</param-value>
    </context-param>
<!---->

    <!--ws-->
    <servlet>
        <servlet-name>springrest</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/springrest-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springrest</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


</web-app>

The url we use to call the webservice i:s http://xxx-xxx-xxx:8080/proms-services-1.0-SNAPSHOT/withdrawconsent?birthnumber=01016512345&programcode=PR

The stacktrace when calling the rest-ws:

*36860 [http-nio-8080-exec-34] ERROR net.krg.proms.services.impl.ConsentServiceImpl  - Feil oppst?tt i withdrawConsent.
java.lang.RuntimeException: javax.persistence.TransactionRequiredException: Executing an update/delete query
        at net.krg.proms.dao.impl.FormReplyDaoImpl.deleteFormReplyForInvitation(FormReplyDaoImpl.java:61)
        at net.krg.proms.services.impl.FormReplyServiceImpl.deleteFormReply(FormReplyServiceImpl.java:36)
        at net.krg.proms.services.impl.ConsentServiceImpl.withdrawConsent(ConsentServiceImpl.java:72)
        at net.krg.proms.services.impl.ConsentServiceImpl.withdrawConsent(ConsentServiceImpl.java:110)
        at net.krg.proms.services.ws.krgcommon.PatientInfoController.withdrawConsent(PatientInfoController.java:77)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)*
Community
  • 1
  • 1
B. Eklund
  • 1
  • 1
  • You are creating 2 instances of the service due to your component scanning. Your servlet should only scan for web related beans (`@Controller`) and ignore the rest as that will be loaded by the `ContextLoaderListener`. You now have 2 instances of the service the correct one is the one from the parent context (`ContextLoaderListener`) and the wrong one is the on in the `DispatcherServlet`. – M. Deinum Nov 29 '16 at 15:13
  • Thanks @M-Denium, that solved the problem. It led us to a new problem (illegal attempt to associate a collection with two open sessions) That we solved by using merge instead of update of an entity. – B. Eklund Nov 30 '16 at 10:37

1 Answers1

0

I'm not sure it will help, but as far as I know, transactional management belongs only to service layer, nor persistence neither controller. Maybe it causes some kind of conflict while exposing the @Transaction method via rmi.

Rubasace
  • 939
  • 8
  • 18