17

对于 WebApplicationContext,我应该将@Transactional注释放在控制器还是服务中?Spring 文档让我有点困惑。

这是我的 web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>Alpha v0.02</display-name>
  <servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>*.htm</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>*.json</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

这是我的 application-context.xml 定义了一个 Spring Dispatcher servlet:

<?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:mvc="http://www.springframework.org/schema/mvc"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/beans    
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc 
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:annotation-config />
    <mvc:annotation-driven />
    <tx:annotation-driven />

    <context:component-scan base-package="com.visitrend" />

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

     <bean id="dataSource"  class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="org.postgresql.Driver" />
        <property name="jdbcUrl" value="jdbc:postgresql://localhost:5432/postgres" />
        <property name="user" value="someuser" />
        <property name="password" value="somepasswd" />
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:test.hibernate.cfg.xml" />
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
      <property name="dataSource" ref="dataSource" />
      <property name="sessionFactory" ref="sessionFactory" />
    </bean>    
</beans>

这是一个服务接口:

public interface LayerService {
    public void createLayer(Integer layerListID, Layer layer);
}

这是一个服务实现:

@Service
public class LayerServiceImpl implements LayerService {

    @Autowired
    public LayerDAO layerDAO;

    @Transactional
    @Override
    public void createLayer(Integer layerListID, Layer layer) {
        layerDAO.createLayer(layerListID, layer);
    }
}

这是我的控制器:

@Controller
public class MainController {

    @Autowired
    private LayerService layerService;

    @RequestMapping(value = "/addLayer.json", method = RequestMethod.POST)
    public @ResponseBody
    LayerListSetGroup addLayer(@RequestBody JSONLayerFactory request) {
        layerService.createLayer(request.getListId(), request.buildLayer());
        return layerService.readLayerListSetGroup(llsgID);
    }
}

Spring 文档让我有点困惑。这似乎表明使用 WebApplicationContext 意味着只有控制器将被调查 @Transactional 注释而不是服务。同时,我看到大量建议使服务具有事务性,而不是控制器。我在想, <context:component-scan base-package="com..." />在我们上面的 spring-servlet.xml 中使用它来包含服务包意味着服务是上下文的一部分,因此将被“调查”以获取事务注释。这是准确的吗?

这是让我感到困惑的 Spring 文档简介:

@EnableTransactionManagement 并且仅在定义它们的同一应用程序上下文中查找 @Transactional bean。这意味着,如果您将注释驱动的配置放在 DispatcherServlet 的 WebApplicationContext 中,它只会检查控制器中的 @Transactional bean,而不是您的服务。

此外,如果我将控制器方法定义为事务性的,并且它在另一个类中调用事务性方法,是否有任何性能影响或“坏处”?根据文档,我的预感是否定的,但我希望对此进行验证。

4

3 回答 3

18

对于注解应该在 Controller 上还是在 Service 上没有要求@Transactional,但通常它会在 Service 上执行,该 Service 将为一个逻辑上应该在一个 ACID 事务中执行的请求执行逻辑。

在典型的 Spring MVC 应用程序中,您至少会有两个上下文:应用程序上下文和 servlet 上下文。上下文是一种配置。应用程序上下文包含与您的整个应用程序相关的配置,而 servlet 上下文包含仅与您的 servlet 相关的配置。因此,servlet 上下文是应用程序上下文的子项,可以引用应用程序上下文中的任何实体。反过来是不正确的。

在你的报价中,

@EnableTransactionManagement 并且仅在定义它们的同一应用程序上下文中查找 @Transactional bean。这意味着,如果您将注释驱动的配置放在 DispatcherServlet 的 WebApplicationContext 中,它只会检查控制器中的 @Transactional bean,而不是您的服务。

@EnableTransactionManagement在注解中声明的包中查找@Transactionalbean,但仅在定义它们的上下文 () 中查找。因此,如果您有一个for your (这是一个 servlet 上下文),那么将在您告诉它的类中查找组件扫描那个上下文(类)。@ComponentScan@ConfigurationWebApplicationContextDispatcherServlet@EnableTransactionManagement@Transactional@Configuration

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "my.servlet.package")
public class ServletContextConfiguration {
    // this will only find @Transactional annotations on classes in my.servlet.package package
}

由于您的@Service类是应用程序上下文的一部分,因此如果您想使这些成为事务性的,那么您需要@Configuration为应用程序上下文注释您的类@EnableTransactionManagement

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "my.package.services")
public class ApplicationContextConfiguration {
    // now this will scan your my.package.services package for @Transactional
}

实例化 DispatcherServlet 时,将 Application Context 配置与 aContextLoaderListener和 Servlet Context 配置一起使用。(如果您还没有这样做,请参阅 javadoc以获得完整的基于 java 的配置,而不是 xml。)

附录: @EnableTransactionManagement具有与 java 配置相同的行为<tx:annotation-driven />在这里检查ContextLoaderListener与 XML 一起使用。

于 2013-03-25T20:41:24.997 回答
5

该服务是放置事务分界的最佳位置。服务应该保存用户交互的细节级别的用例行为,这意味着在事务中逻辑上可以组合在一起的东西。这样,Web 应用程序胶水代码和业务逻辑之间也保持了分离。

有很多 CRUD 应用程序没有任何重要的业务逻辑,因为它们有一个服务层,只是在控制器和数据访问对象之间传递东西是没有用的。在这些情况下,您可以将事务注释放在数据访问对象上。

将事务注释放在控制器上可能会导致问题,请参阅[Spring MVC 文档][1],17.3.2:

当应用需要为控制器对象创建代理的功能(例如@Transactional 方法)时,使用带注释的控制器类时会发生一个常见的陷阱。通常您会为控制器引入一个接口,以便使用 JDK 动态代理。要完成这项工作,您必须将@RequestMapping 注释以及任何其他类型和方法级别的注释(例如@ModelAttribute、@InitBinder)移动到接口以及映射机制只能“看到”由代理人。或者,您可以在配置中为应用于控制器的功能激活 proxy-target-class="true"(在我们的事务场景中)。这样做表明应该使用基于 CGLIB 的子类代理而不是基于接口的 JDK 代理。

您在属性上设置的事务传播行为决定了当一个事务方法调用另一个事务方法时会发生什么。您可以对其进行配置,以便调用的方法使用相同的事务,或者使其始终使用新事务。

通过在示例代码中多次调用您的服务,您违背了服务的事务性目的。如果您将事务注释放在服务上,对服务的不同调用将在不同的事务中执行。

于 2013-03-25T19:42:58.480 回答
0

有时拥有@Transactional 控制器方法非常方便,尤其是在使用Hibernate 执行琐碎操作时。要使用 XML 配置启用此功能,请将其添加到您的 dispatch-servlet.xml:

<beans ...
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="...
 http://www.springframework.org/schema/tx
 http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">    
  <tx:annotation-driven transaction-manager="transactionManager"
   proxy-target-class="true" />
  ..
</beans>

proxy-target-class 的目的是使用控制器上的 AOP 工作所需的 CGLIB 代理。如果不添加,启动时会报错。此外,如果您的控制器中有任何最终方法,请注意它们不能被代理(特别是事务性的),并且在启动时,您还将收到来自 CGLIB 的每种方法的警告。

于 2015-04-26T06:22:05.753 回答