【问题标题】:Spring rmi service works but not as an restful wsSpring rmi 服务可以工作,但不能作为一个安静的 ws
【发布时间】:2016-11-29 14:53:29
【问题描述】:

我们有一个使用 Hibernate 5.1.0.Final 和 Java 8 的 Spring 应用程序版本 4.3.3.RELEASE 该应用程序有一个 rmi 接口,它向 Java 胖客户端应用程序公开服务。该应用程序还公开了 Spring Restful Web 服务。 导致我们出现问题的服务被暴露为一个 rmi 服务一个 restful ws。将服务调用为 RMI 效果很好,但调用 restful ws 会产生 javax.persistence.TransactionRequiredException,如下所示。

我以为我在这里找到了这个问题的答案,但我似乎无法解决问题: Spring @Transactional not working

我们尝试了许多不同的方法,例如:

@Transactional(propagation = Propagation.REQUIRES_NEW)

但我想这里的问题是我有两个 spring-config 文件,但没有在我的 springrest-config.xml 中定义正确的东西。

有人知道怎么解决吗?

这里是dao方法:

@Override
    public void deleteFormReplyForInvitation(Long id) {

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


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

这里是服务代码:

@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");
    }

这是其余控制器:

@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);
    }

这里是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"/>

这是 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>

这里是 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>

我们用来调用webservice的url i:s http://xxx-xxx-xxx:8080/proms-services-1.0-SNAPSHOT/withdrawconsent?birthnumber=01016512345&programcode=PR

调用rest-ws时的stacktrace:

*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)*

【问题讨论】:

  • 由于组件扫描,您正在创建 2 个服务实例。您的 servlet 应该只扫描与 Web 相关的 bean (@Controller) 并忽略其余部分,因为它们将由 ContextLoaderListener 加载。您现在有 2 个服务实例,正确的一个是来自父上下文 (ContextLoaderListener) 的一个,错误的一个是 DispatcherServlet 中的 on。
  • 感谢@M-Denium,解决了这个问题。它导致我们遇到了一个新问题(非法尝试将一个集合与两个打开的会话相关联),我们通过使用合并而不是更新实体来解决这个问题。

标签: java spring rest transactional


【解决方案1】:

我不确定它是否会有所帮助,但据我所知,事务管理只属于服务层,持久性也不属于控制器。在通过 rmi 暴露 @Transaction 方法时,可能会导致某种冲突。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    • 2019-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多