web-dev-qa-db-fra.com

TransactionRequiredException: Exécution d'une requête de mise à jour/suppression

J'ai du mal à trouver une solution à mon problème.
J'ai une classe de service, qui contient une méthode pour définir un indicateur de vérification lors de la connexion.

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

Mon référentiel est un SpringData CrudRepository, et verifyUser est quelque chose comme

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

Lorsque vous appelez le code directement dans un test unitaire, tout fonctionne correctement. Lorsque je l'appelle de mon fournisseur d'authentification via l'application, j'obtiens l'exception suivante:

javax.persistence.TransactionRequiredException: Exécution d'une requête de mise à jour/suppression

La classe Service est injectée dans my Unit Test et le fournisseur d'authentification à l'aide de l'annotation @Autowired. Le test lui-même n'a pas d'annotations intéressantes, pas plus que le fournisseur d'authentification.

Je suis à court d'idées, donc si quelqu'un a un indice, je vous en serais très reconnaissant.

EDIT: Au lieu d'appeler le script de mise à jour verifyUser, je récupère maintenant tous les utilisateurs non vérifiés, définit l'indicateur vérifié et utilise la méthode save () du référentiel. Cela fonctionne, mais est très moche, donc je suis ouvert à de meilleures suggestions.

EDIT2:

Par requête, voici la partie persistance de la configuration, je suppose que c'est le plus pertinent, le reste ne concerne que l'authentification. Cette configuration est utilisée à la fois dans les tests unitaires et les applications Web. La seule différence est que les sources de données sont intégrées à la base de données H2 pour les tests unitaires et à mysql pour les applications 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>

De plus, j'ai une config qui est seulement dans l'application web, pas les tests unitaires:

<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>
11
pushy

Je pense que si vous déplacez le <tx:annotation-driven/> dans le contexte contenant <context:annotation-config/>, alors Spring récupérera votre @Transactional. Le <tx:annotation-driven/> est un post-processeur qui décore uniquement les beans dans le contexte d'application dans lequel il est défini. Voir ma réponse ici pour plus d'explications.

9
MarkOfHall

- Votre classe de service ne doit pas également être un référentiel

- Voici à quoi devrait ressembler votre applicationContext.xml:

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

- Voici comment définir vos classes de tests unitaires

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

- NOTEZ l'utilisation de applicationContext-test-override.xml Ceci est utilisé pour remplacer tous les paramètres de votre contexte pour les tests. En procédant de cette façon, vous testez le contexte réel de l'application. Par conséquent, si vous faites des erreurs, elles apparaîtront dans vos tests. Il devrait être situé dans src/test/resources. C'est tout ce dont vous avez besoin, espérons-le:

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

- (optionnel) pour utiliser le mode = aspectj

Ajoutez ce qui suit aux plugins maven. Il intègre des aspects lors de la compilation, au lieu de l'exécution (mode = proxy).

        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.4</version>
            <dependencies>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjrt</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjtools</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
            </dependencies>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                    <!-- NB: force aspect compile before normal compile, required for 1.3+ 
                        see: MASPECTJ-13, MASPECTJ-92 -->
                    <phase>process-sources</phase>
                </execution>
            </executions>
            <configuration>
                <outxml>true</outxml>
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-aspects</artifactId>
                    </aspectLibrary>
                </aspectLibraries>
                <source>${Java.version}</source>
                <target>${Java.version}</target>
            </configuration>
        </plugin>
4
Solubris

J'ai également fait face au même problème et résolu en ajoutant les annotations @Transactional (propagation = Propagation.REQUIRED, rollbackFor = Exception.class, readOnly = false)

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

J'ai eu le problème et je l'ai résolu en ajoutant simplement l'annotation @Transactional sur la méthode de service qui effectue la suppression ou la mise à jour.

1
Salim Hamidi