Для веб-MVC Spring приложение должно @Transactional перейти на контроллер или услугу?

Для 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 диспетчера:

<?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 на beans в том же контексте приложения они определяется в. Это означает, что если вы добавили аннотации конфигурации в WebApplicationContext для DispatcherServlet, это проверяет только @Transactional beans на ваших контроллерах, а не на вашем услуги.

Кроме того, есть ли какие-либо последствия для производительности или "плохость", если я определяю метод контроллера как транзакционный, и он вызывает транзакционный метод в другом классе? Моей догадки нет, основываясь на документации, но она хотела бы получить подтверждение.

Ответ 1

Нет необходимости в том, должна ли аннотация @Transactional идти на контроллер или на службу, но обычно она будет работать на службе, которая будет выполнять логику для одного запроса, который логически должен выполняться в рамках одной транзакции ACID.

В типичном приложении Spring MVC у вас будет минимально два контекста: контекст приложения и контекст сервлета. Контекст - это своего рода конфигурация. Контекст приложения содержит конфигурацию, которая подходит для всего вашего приложения, тогда как контекст сервлета содержит конфигурацию, относящуюся только к вашим сервлетам. Таким образом, контекст сервлета является дочерним элементом контекста приложения и может ссылаться на любой объект в контексте приложения. Обратное неверно.

В вашей цитате,

@EnableTransactionManagement и ищет только @Transactional на beans в том же контексте приложения, в котором они определены. Это означает, что если вы поместите аннотированную конфигурацию в WebApplicationContext для DispatcherServlet, она проверяет только на @Transactional beans в ваших контроллерах, а не в ваших сервисах.

@EnableTransactionManagement ищет @Transactional в beans в пакетах, объявленных в аннотации @ComponentScan, но только в контексте (@Configuration), в котором они определены. Итак, если у вас есть WebApplicationContext для DispatcherServlet (это контекст сервлета), тогда @EnableTransactionManagement будет искать @Transactional в классах, которые вы сказали ему, для проверки компонентов в этом контексте (@Configuration class).

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

Используйте конфигурацию контекста приложения с конфигурацией ContextLoaderListener и вашей конфигурацией сервлета при создании экземпляра DispatcherServlet. (См. javadoc для полной конфигурации на основе java вместо xml, если вы этого не делаете.)

Приложение @EnableTransactionManagement имеет такое же поведение, как <tx:annotation-driven /> в конфигурации java. Проверить здесь для использования ContextLoaderListener с XML.

Ответ 2

Служба - лучшее место для размещения транзакционных демаркаций. Служба должна поддерживать поведение варианта использования на уровне детализации для взаимодействия с пользователем, то есть вещи, которые логически объединяются в транзакции. Также таким образом обеспечивается разделение между кодом клея веб-приложений и бизнес-логикой.

Существует много приложений CRUD, которые не имеют существенной бизнес-логики, поскольку они имеют уровень обслуживания, который просто передает данные между контроллерами и объектами доступа к данным, не является полезным. В таких случаях вы можете уйти с помещением аннотации транзакций на объекты доступа к данным.

Помещение аннотации транзакций к контроллеру может вызвать проблемы, см. [документация по Spring MVC] [1], 17.3.2:

Общая ошибка при работе с классами аннотированных контроллеров происходит при применении функциональных возможностей, требующих создания прокси-сервера для объект контроллера (например, методы @Transactional). Обычно вы будете ввести интерфейс для контроллера, чтобы использовать динамику JDK прокси. Чтобы сделать эту работу, вы должны переместить @RequestMapping аннотации, а также любые другие аннотации типа и метода (например, @ModelAttribute, @InitBinder) на интерфейс, а также механизм отображения может "видеть" интерфейс, открытый через прокси. Кроме того, вы можете активировать прокси-target- class= "true" в конфигурация для функциональности, применяемой к контроллеру (в нашем сценарий транзакции). Это означает, что что прокси-классы подкласса на основе CGLIB следует использовать вместо на основе интерфейса JDK-прокси. Для получения дополнительной информации о различных прокси-серверах механизмы см. в разделе 9.6 "Механизмы проксирования".

Действия по распространению транзакций, которые вы настроили на атрибуты, определяют, что происходит, когда транзакционный метод вызывает другой метод транзакций. Вы можете настроить его так, чтобы вызванный метод использовал одну и ту же транзакцию или чтобы она всегда использовала новую транзакцию.

Имея несколько обращений к вашей службе в примере кода, вы побеждаете транзакционную цель службы. Различные вызовы вашей услуги будут выполняться в разных транзакциях, если вы поместите транзакционные аннотации в службу.

Ответ 3

Иногда очень удобно иметь методы @Transactional controller, особенно при выполнении тривиальных операций с использованием Hibernate. Чтобы включить это с помощью конфигурации XML, добавьте это в свой диспетчер-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>

Цель прокси-целевого класса - использовать прокси CGLIB, которые необходимы для работы АОП на контроллерах. Если вы не добавите это, вы получите сообщение об ошибке при запуске. Кроме того, если у вас есть какие-либо конечные методы в контроллерах, обратите внимание, что они не могут быть проксированы (в частности, сделаны транзакционными), и вы также получите предупреждение от CGLIB для каждого из этих методов при запуске.