2013-03-25 9 views
16

Per WebApplicationContext, dovrei inserire annotazioni @Transactional nel controller o nei servizi? I documenti di primavera mi hanno un po 'confuso.Per l'applicazione web MVC Spring dovrebbe @Transactional andare su controller o servizio?

Ecco il mio 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> 

Qui è la mia domanda-context.xml definizione di una servlet primavera dispatcher:

<?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> 

Ecco un'interfaccia di servizio:

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

Ecco un'implementazione del servizio:

@Service 
public class LayerServiceImpl implements LayerService { 

    @Autowired 
    public LayerDAO layerDAO; 

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

Ed ecco il mio controller:

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

La documentazione Primavera mi ha un po 'confuso. Sembra indicare che l'utilizzo di WebApplicationContext significa che solo i controller verranno esaminati per le annotazioni @Transactional e non per i servizi. Nel frattempo vedo tonnellate di raccomandazioni per rendere i servizi transazionali e non i controllori. Sto pensando che usare <context:component-scan base-package="com..." /> nel nostro servlet.xml di primavera sopra in modo che includa i pacchetti di servizi significa che i servizi fanno parte del contesto, e quindi saranno "studiati" per le annotazioni transazionali. È accurato?

Ecco la fascetta pubblicitaria documentazione di primavera che mi sono confuso:.

@EnableTransactionManagement e guarda solo per @Transactional su fagioli nello stesso contesto applicativo sono definito Questo significa che, se si mette l'annotazione guidato configurazione in un WebApplicationContext per DispatcherServlet, è controlla solo i bean @Transactional nei controller e non i servizi .

Inoltre, ci sono implicazioni di prestazioni o "cattiveria" se definisco un metodo di controllo come transazionale e chiama un metodo transazionale in un'altra classe? La mia impressione è no, basata sulla documentazione, ma mi piacerebbe la convalida su questo.

risposta

4

Il servizio è il posto migliore per inserire le demarcazioni transazionali. Il servizio dovrebbe contenere il comportamento del caso d'uso a livello di dettaglio per un'interazione con l'utente, ovvero roba che logicamente andrebbe insieme in una transazione. Anche in questo modo viene mantenuta una separazione tra il codice della colla dell'applicazione Web e la logica aziendale.

Ci sono molte applicazioni CRUD che non hanno una logica di business significativa, perché avere un livello di servizio che passa semplicemente attraverso i controller e gli oggetti di accesso ai dati non è utile. In questi casi è possibile superare l'annotazione della transazione sugli oggetti di accesso ai dati.

L'annotazione transazionale sul controller può causare problemi, vedere [documentazione Spring MVC] [1], 17.3.2:

Un errore comune quando si lavora con classi controller annotate succede quando si applica funzionalità che richiede la creazione di un proxy per l'oggetto regolatore (ad esempio metodi @Transactional). In genere, l'utente introduce un'interfaccia per il controller per utilizzare i proxy dinamici JDK . Per rendere questo lavoro è necessario spostare le annotazioni @RequestMapping , così come qualsiasi altra annotazione a livello di metodo e metodo (ad esempio @ModelAttribute, @InitBinder) all'interfaccia così come il meccanismo di mappatura può solo "vedere" l'interfaccia esposto dal proxy. In alternativa, è possibile attivare proxy-target-class = "true" nella configurazione per la funzionalità applicata al controller (nel nostro scenario di transazione in). Ciò indica che i proxy di sottoclassi basati su CGLIB devono essere utilizzati al posto dei proxy JDK basati su interfaccia . Per ulteriori informazioni su vari meccanismi di proxy dinamici , vedere la Sezione 9.6, "Meccanismi di proxy".

I comportamenti di propagazione delle transazioni impostati sugli attributi decidono cosa accade quando un metodo transazionale chiama un altro metodo transazionale. È possibile configurarlo in modo che il metodo chiamato utilizzi la stessa transazione o che utilizzi sempre una nuova transazione.

Avendo più chiamate al servizio nel codice di esempio, si sta vanificando lo scopo della transazione del servizio. Le diverse chiamate al servizio verranno eseguite in diverse transazioni se si inseriscono le annotazioni transazionali sul servizio.

+0

Sapete perché la documentazione di Spring si riferiva solo alle annotazioni del controllore in lettura? Non sta considerando nessuna scansione dei componenti? Ecco un link ai documenti più recenti in cui ho trovato questo: http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/transaction.html#transaction-declarative-annotations – user2208384

+0

@ user2208384: Ti avverte solo che la scansione troverà solo le cose all'interno dello stesso contesto applicativo. quindi se si desidera annotare servizi, inserire tale configurazione nel contesto dell'applicazione che gestisce i servizi. –

+0

Come si mettono i servizi nello stesso WebApplicationContext? Penso che la scansione dei componenti lo faccia se si includono i pacchetti di servizi, ma si vuole essere sicuri. In altre parole, i miei file di esempio non lo fanno già? – user2208384

17

Non è necessario specificare se l'annotazione @Transactional debba essere impostata su un controller o su un servizio, ma in genere dovrebbe essere eseguita su un servizio che eseguirà la logica per una richiesta che dovrebbe essere eseguita logicamente all'interno di una transazione ACID.

In una tipica applicazione Spring MVC, si avrebbero, in minima parte, due contesti: il contesto dell'applicazione e il contesto servlet. Un contesto è una sorta di configurazione. Il contesto dell'applicazione contiene la configurazione rilevante per l'intera applicazione, mentre il contesto servlet contiene la configurazione pertinente solo ai propri servlet. In quanto tale, il contesto servlet è figlio del contesto dell'applicazione e può fare riferimento a qualsiasi entità nel contesto dell'applicazione. Il contrario non è vero.

Nel tuo preventivo,

@EnableTransactionManagement e cerca solo per @Transactional su fagioli nello stesso contesto di applicazione sono definiti in. Ciò significa che, se si mette l'annotazione di configurazione guidata in un WebApplicationContext per un DispatcherServlet , controlla solo i bean @Transactional nei tuoi controller e non i tuoi servizi.

@EnableTransactionManagement cerca i @Transactional in fagioli in pacchetti dichiarati nel @ComponentScan di annotazione, ma solo nel contesto (@Configuration) sono definiti in. Quindi, se si dispone di un WebApplicationContext per il vostro DispatcherServlet (questo è un contesto servlet), poi @EnableTransactionManagement cercherà lo @Transactional nelle classi che gli hai detto di eseguire la scansione dei componenti in quel contesto (classe @Configuration).

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

Dal momento che i @Service classi sono parte del contesto di applicazione, se si vuole fare quelle transazionali, allora avete bisogno di annotare la classe @Configuration per il contesto applicativo con @EnableTransactionManagement.

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

Usa la tua configurazione di contesto di applicazione con una propria configurazione ContextLoaderListener e Servlet Context quando si crea un'istanza tuo DispatcherServlet. (See the javadoc per una configurazione base di Java completa, invece di XML, se non lo state facendo già.)

Addendum:@EnableTransactionManagement ha lo stesso comportamento <tx:annotation-driven /> in una configurazione di Java. Check here per l'utilizzo di ContextLoaderListener con XML.

+0

Grazie! Questo lo porta davvero a casa per me. – user2208384

+0

Prego. Vorrei che questo fosse più chiaro anche a me quando ho avviato Spring MVC. –

0

A volte è molto conveniente avere i metodi di controllo @Transactional, specialmente quando si eseguono operazioni banali usando Hibernate. Per attivare questa configurazione XML usando, aggiungere questo al vostro dispaccio-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> 

Lo scopo di proxy-bersaglio di classe è quello di utilizzare i proxy CGLIB che sono necessari per AOP su controller a lavorare. Se non lo aggiungi, riceverai un errore all'avvio. Inoltre, se si dispone di metodi finali nei controller, si noti che non possono essere inviati tramite proxy (in particolare, resi transazionali), e all'avvio verrà visualizzato un avviso da CGLIB per ciascuno di questi metodi.

Problemi correlati