2013-03-25 2 views
16

Для WebApplicationContext, должен ли я помещать аннотации @Transactional в контроллер или в службы? Весенние документы меня немного смутили.Для веб-приложения MVC Spring должно @Transactional перейти на контроллер или услугу?

Вот моя 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> 

Вот мое приложение-context.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: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 выше, чтобы оно включало пакеты услуг, означает, что сервисы являются частью контекста и поэтому будут «исследованы» для аннотаций транзакций. Это точно?

Вот весна документация реклама, которая заставила меня беспорядочное.

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

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

ответ

4

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

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

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

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

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

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

+0

Вы знаете, почему документация Spring ссылалась только на чтение комментариев контроллера? Рассматривается ли это без сканирования компонентов? Вот ссылка на самые последние документы, где я нашел это: http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/transaction.html#transaction-declarative-annotations – user2208384

+0

@ user2208384: Это просто предупреждает вас, что сканирование будет находить вещи только в одном и том же контексте приложения. поэтому, если вы хотите аннотировать услуги, поместите эту конфигурацию в контекст приложения, который управляет службами. –

+0

Как вы помещаете службы в один и тот же WebApplicationContext? Я думаю, что проверка компонентов делает это, если вы включаете свои пакеты услуг, но хотите убедиться. Другими словами, мои файлы примеров еще не сделали этого? – user2208384

17

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

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

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

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

@EnableTransactionManagement ищет @Transactional в зернах в пакетах, объявленных в @ComponentScan аннотации, но только в контексте (@Configuration) они определены. Так что если у вас есть WebApplicationContext для вашего DispatcherServlet (это контекст сервлета), то @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 
} 

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

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

+0

Спасибо! Это действительно заводит меня домой. – user2208384

+0

Добро пожаловать. Мне жаль, что это не было яснее для меня, когда я начал Spring MVC. –

0

Иногда очень удобно иметь методы @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 для каждого из этих методов при запуске.

 Смежные вопросы

  • Нет связанных вопросов^_^