2012-09-07 10 views
10

Ho difficoltà a trovare una soluzione al mio problema.
Ho una classe di servizio, che contiene un metodo per impostare un flag di verifica al momento dell'accesso.TransactionRequiredException: Esecuzione di una query di aggiornamento/eliminazione

@Service("userRolesService") 
@Repository 
@Transactional 
public class UserRolesService { 
    public void verify() { 
     repository.verifyUser(); 
    } 
} 

mio repository è uno SpringData CrudRepository e verifyUser è qualcosa come

@Modifying 
@Query("UPDATE user SET (verified = 1 WHERE verified=0)") 
public void verifyUser(); 

Quando si chiama il codice direttamente in un test di unità, tutto funziona bene. Quando si chiama dal mio provider di autenticazione attraverso l'applicazione ottengo la seguente eccezione:

javax.persistence.TransactionRequiredException: Esecuzione di un aggiornamento/query di eliminazione

la classe di servizio viene iniettato in entrambi la mia prova di unità e il provider di autenticazione usando l'annotazione @Autowired. Il test in sé non ha annotazioni interessanti, né il provider di autenticazione.

Sono fresco di idee, quindi se qualcuno ha la minima idea, sarei molto grato.

MODIFICA: Invece di chiamare lo script di aggiornamento di verifyUser, ora richiama tutti gli utenti non verificati, imposta il flag verificato e utilizza il metodo save() del repository. Funziona, ma è molto brutto, quindi sono aperto a suggerimenti migliori.

EDIT2:

Per richiesta qui è la parte persistenza della configurazione, credo che questo è più rilevante, il resto si occupa solo con l'autenticazione. Questa configurazione è utilizzata sia nel test unitario che nell'applicazione web, l'unica differenza è che le origini dati sono incorporate in H2 DB per le unit test e mysql per l'app web.

<beans [..]> 

    <bean id="entityManagerFactory" 
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" 
      depends-on="persistenceInitializer"> 
     <property name="dataSource" ref="dataSource"/> 
     <property name="persistenceUnitName" value="jpa"/> 
     <property name="packagesToScan"> 
      <list> 
       <value>com.example.model</value> 
      </list> 
     </property> 
     <property name="jpaVendorAdapter"> 
      <bean class="com.example.persistence.adapter.ConfigurationRetainingHibernateJpaVendorAdapter"> 
       <property name="database" value="${spring.hibernate.database}"/> 
       <property name="generateDdl" value="${spring.hibernate.generateDdl}"/> 
      </bean> 
     </property> 
     <property name="jpaProperties"> 
      <props> 
       <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.DefaultComponentSafeNamingStrategy 
       </prop> 
      </props> 
     </property> 
    </bean> 

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> 
     <property name="entityManagerFactory" ref="entityManagerFactory"/> 
    </bean> 

    <jpa:repositories base-package="com.example.persistence.repository"/> 

    <tx:annotation-driven/> 

    <bean id="persistenceInitializer" class="com.example.persistence.init.NoOpInitializer"/> 

</beans> 

Inoltre ho una configurazione che è solo nella web app, non i test di unità:

<beans [..]> 

    <bean id="propertyConfigurer" 
      class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
     <property name="locations"> 
      <list> 
       <value>classpath:application.properties</value> 
      </list> 
     </property> 
    </bean> 

    <mvc:annotation-driven/> 

    <mvc:default-servlet-handler/> 

    <context:annotation-config/> 

</beans> 
+0

Volete pubblicare il tuo Primavera config? Sembra che il tuo post processor di Transaction non stia trovando il bean? – MarkOfHall

+0

Certo, ho aggiunto due parti di configurazione. Config è diviso in diversi file, ma la maggior parte di essi non dovrebbe essere rilevante per questo problema. – pushy

risposta

8

Penso che se si sposta il <tx:annotation-driven/> nel contesto contenente <context:annotation-config/>, poi primavera prenderà il tuo @Transactional. Il <tx:annotation-driven/> è un post processore che decora solo i bean nel contesto dell'applicazione in cui è definito. Vedere la mia risposta here per ulteriori spiegazioni.

+0

Funziona, sembra che sia stato un mio errore. Alla fine ho usato entrambe le risposte, ma questo sembrava essere il dettaglio che ha funzionato, grazie mille :-) – pushy

4

- La classe di servizio non dovrebbe essere anche un repository

- Ecco come il vostro applicationContext.xml dovrebbe apparire:

<bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager"> 
    <property name="entityManagerFactory" ref="entityManagerFactory"/> 
</bean> 
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/> 
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory"> 
    <property name="persistenceUnitName" value="persistenceUnit"/> 
    <property name="dataSource" ref="dataSource"/> 
</bean> 

<bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource"> 
    <property name="driverClassName" value="${database.driverClassName}"/> 
    <property name="url" value="${database.url}"/> 
    <property name="username" value="${database.username}"/> 
    <property name="password" value="${database.password}"/> 
    <property name="testOnBorrow" value="true"/> 
    <property name="testOnReturn" value="true"/> 
    <property name="testWhileIdle" value="true"/> 
    <property name="timeBetweenEvictionRunsMillis" value="1800000"/> 
    <property name="numTestsPerEvictionRun" value="3"/> 
    <property name="minEvictableIdleTimeMillis" value="1800000"/> 
    <property name="validationQuery" value="SELECT 1"/> 
    <property name="initialSize" value="1"/> 
    <property name="minIdle" value="1"/> 
    <property name="maxActive" value="10"/> 
    <property name="poolPreparedStatements" value="true"/> 
    <property name="maxOpenPreparedStatements" value="20"/> 
</bean> 

- Ecco come le vostre classi test unità dovrebbe essere definito

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(loader=WebContextLoader.class, locations = {"classpath:/META-INF/spring/applicationContext.xml", "classpath:/META-INF/spring/applicationContext-test-override.xml"}) 
public class MyTest { 

- NOTA l'uso di applicationContext-test-override.xml Viene utilizzato per sovrascrivere qualsiasi impostazione nel proprio contesto per il test.Farlo in questo modo significa testare il contesto reale dell'applicazione, quindi se fai degli errori lì, verrà fuori nei tuoi test. Dovrebbe trovarsi in src/test/risorse. Questo è tutto ciò che serve spera:

<bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource"> 
    <property name="url" value="${database-test.url}"/> 
</bean> 

- (opzionale) per utilizzare la modalità = aspectj

Aggiungere quanto segue i plugin Maven. Incorpora aspetti al momento della compilazione, invece del runtime (mode = proxy).

+0

Non appena aggiungo mode = "aspettoj" transaction-manager = "transactionManager" al mio tx: annotation -drive tag, ricevo anche l'errore nei miei test unitari. A parte questo, le modifiche all'applcationContext.xml hanno molto senso, le ho introdotte. Tuttavia, l'eccezione continua a spuntare. – pushy

+0

Aggiunto punto extra sull'uso della modalità = aspettoj alla risposta – Solubris

+0

Funziona ora, grazie mille. La risposta di Steve si è rivelata la soluzione, ma la rilavorazione dei miei file di configurazione è valsa la pena, quindi grazie per l'input. Non ha funzionato la modalità di annotazione aspectj, ma funziona perfettamente con la modalità proxy. – pushy

1

Ho avuto qualche problema e l'ho risolto aggiungendo semplicemente l'annotazione @Transactional sul metodo di servizio che esegue l'eliminazione o l'aggiornamento.

0

Ho anche affrontato lo stesso problema e risolto aggiungendo le annotazioni @Transactional (propagazione = Propagation.REQUIRED, rollbackFor = Exception.class, readOnly = false)

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, readOnly = false) 
public class UserRolesService{ 
.......... 
} 
Problemi correlati