Spring 使用 JUnit 4 和 Spring 数据 JPA 进行测试:NoSuchMethodError org.hibernate.engine.spi.SessionFactoryImplementor.getProperties

Spring Test with JUnit 4 and Spring Data JPA: NoSuchMethodError org.hibernate.engine.spi.SessionFactoryImplementor.getProperties

使用 SpringJUnit4ClassRunner、JUnit 4 和 Spring 测试我为 Service 编写了单元测试,它使用 Spring Data JPA Repository 和嵌入式 HSQL 数据库:

@Ignore
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:unitTestFullConfig.xml")
public class InMemoryDBFullTestBaseClass {
}

public final class ActorServiceImplTest extends InMemoryDBFullTestBaseClass {
   @Inject
    private ActorService service;

    @Test
    public final void saveActor () throws Exception {
        service.save(new ActorDTO(null, "testName", "testSurname", new Date(), Collections.emptyList()));

        assertEquals(1, service.getAll().size());
    }
}

我运行测试required javaagent option on VM,配置:

<?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:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.1.xsd

       http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- Configure the data source bean -->
    <jdbc:embedded-database id="dataSource" type="HSQL">
    </jdbc:embedded-database>
    <!-- Enable annotation driven transaction management -->
    <tx:annotation-driven/>
    <mvc:annotation-driven/>
    <context:component-scan base-package="beans"/>

    <!-- Create default configuration for Hibernate -->
    <bean id="hibernateJpaVendorAdapter"
          class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>

    <!-- Configure the entity manager factory bean -->
    <bean id="entityManagerFactory"
          class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/>
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
        <!-- Set JPA properties -->
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
                <prop key="javax.persistence.schema-generation.database.action">none</prop>
                <prop key="hibernate.ejb.use_class_enhancer">true</prop>
                <prop key="hibernate.hbm2ddl.auto">create</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
        <!-- Set base package of your entities -->
        <property name="packagesToScan" value="models"/>
        <!-- Set share cache mode -->
        <property name="sharedCacheMode" value="ENABLE_SELECTIVE"/>
        <!-- Set validation mode -->
        <property name="validationMode" value="NONE"/>
        <property name="persistenceUnitName" value="testJPA" />
    </bean>

    <!-- Configure the transaction manager bean -->
    <bean id="transactionManager"
          class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>



    <!--
      Configure Spring Data JPA and set the base package of the
      repository interfaces
    -->
    <jpa:repositories base-package="beans.repositories"/>
</beans>

但是我得到了:

Error creating bean with name 'entityManagerFactory' defined in URL [file:/E:/workspace/film-site/out/test/main/unitTestFullConfig.xml]: Invocation of init method failed; nested exception is java.lang.NoSuchMethodError: org.hibernate.engine.spi.SessionFactoryImplementor.getProperties()Ljava/util/Properties; at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean...

测试配置和 applicationContext.xml(适用于 Tomcat 应用程序)之间的唯一区别是测试中使用的嵌入式数据库,但即使我使用项目中的 dataSource

<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/film-site"/>
    <property name="user" value="postgres"/>
    <property name="password" value="postgres"/>
    <property name="maxPoolSize" value="10"/>
    <property name="maxStatements" value="0"/>
    <property name="minPoolSize" value="5"/>
</bean>

我仍然面临同样的问题(项目正常运行)。另外,我不认为问题是我没有 hibernate.properties 文件,因为我在这里询问过它:。我使用 Spring 4.3.2.RELEASEHibernate Core 5.2.0.FinalHibernate Entity Manager 5.1.0.FinalSpring Data 1.10.2.RELEASE JPASpring Data Commons 1.12.2.RELEASESpring Data Commons Core 1.4.1.RELEASE。如果有人能帮助我,我会很高兴 - 提前谢谢你。 更新: 我将 jpaProperties 更改为 jpaPropertyMap entityManagerFactory

<property name="jpaPropertyMap">
        <map>
            <entry key="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
            <entry key="javax.persistence.schema-generation.database.action" value="none" />
            <entry key="hibernate.ejb.use_class_enhancer" value="true" />
            <entry key="hibernate.hbm2ddl.auto" value="create" />
            <entry key="hibernate.show_sql" value="true" />
        </map>
    </property>

hibernate-entitymanager的评论依赖,但它仍然不起作用。当我切换到 Hibernate 5.1 时,我也遇到了同样的问题 更新 2: 我创建了一个 Java 配置版本,也许它会帮助别人看到我在哪里犯了错误:

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

import javax.persistence.EntityManagerFactory;
import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class HibernateConfig {
    @Bean
    public DataSource dataSource () {
        return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build();
    }

//    Create default configuration for Hibernate
    @Bean
    public JpaVendorAdapter jpaVendorAdapter () {
        return new HibernateJpaVendorAdapter();
    }

//    Configure the entity manager factory bean
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory () {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();

        factory.setDataSource(dataSource());
        factory.setJpaVendorAdapter(jpaVendorAdapter());
        factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
        factory.setJpaPropertyMap(createJpaProperties());
        factory.setPackagesToScan("models");
        factory.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);
        factory.setValidationMode(ValidationMode.NONE);
        factory.setPersistenceUnitName("testJPA");

        return factory;
    }

    @Bean
    public JpaTransactionManager transactionManager () {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory((EntityManagerFactory) entityManagerFactory());
        return transactionManager;
    }

    private Map<String, ?> createJpaProperties () {
        Map<String, Object> propertyMap = new HashMap();
        propertyMap.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        propertyMap.put("javax.persistence.schema-generation.database.action", "none");
        propertyMap.put("hibernate.ejb.use_class_enhancer", true);
        propertyMap.put("hibernate.hbm2ddl.auto", "create");
        propertyMap.put("hibernate.show_sql", true);

        return propertyMap;
    }
}

更新 2016-10-04:我创建了 Github repository which shows problem,在那里你会看到应用程序本身运行良好(只需在表单中添加 Actorindex,jsp 文件中,但它不适用于测试)。 P.S。我从 Intellij IDEA("Run" 按钮)将 war:exploded 部署到 Tomcat 8.0.24 本地实例。

在 hibernate 社区中修复此错误时,SessionFactoryImplementor.getProperties() 的签名在 5.2 中更改为 return Map 而不是 Properties. reference
因此,您应该使用 Map.

您使用的是hibernate 5.2.0 Final,在新版本hibernate 5.2.3 Final社区中整合了几个hibernate -entitymanager 问题。 download link。请尝试使用此版本。

建议:

1) 使用下面的 hibernate-core 和 hibernate-entitymanager 版本代替 5.2.0.Final5.1.0.Final个版本。

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.2.1.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>5.2.1.Final</version>
</dependency>

2) 恢复到 Hibernate 5.1.x 发布(我想你应该对此没有问题。 )
3) 如果第一个和第二个建议没有奏效,那么继续发布 6.0.1.GA,它与 Hibernate 兼容5.2. community discussion
4) 而不是下面的配置(只是为了试错法。)

<!-- Configure the entity manager factory bean -->
<bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
<!-- Set JPA properties -->
<property name="jpaPropertyMap">
    <map>
     ...
    </map>
</property>
...
</bean>

使用此代码:

<!-- Configure the entity manager factory bean -->
<bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
 ...
  <property name="jpaPropertyMap" ref="jpaPropertyMap" />
 ...
</bean>

<util:map id="jpaPropertyMap" map-class="java.util.TreeMap"> <!-- OR <util:map id="jpaPropertyMap">   OR  <util:map id="jpaPropertyMap" map-class="java.util.HashMap">-->

  <entry key="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
  ...
  <entry key="hibernate.show_sql" value="true"/>
  ...
</util:map>

使用 Hibernate Core 5.2 Hibernate EntityManager 5.1 很可能会导致此处出现问题。 5.2 将 EntityManager 实现移到了核心模块中,这样您最终会在类路径上得到 2 个 JPA 实现,这可能会导致 Spring 框架无法检测到 bootstrap 的 Hibernate 版本。

确保您使用 Hibernate 5.1 并参考 hibernate-entitymanager 神器 5.2 并且仅引入 hiberante-core.

我在 GitHub 上检查了您的示例,经过一些代码修改后单元测试对我有效。但首先我想说一开始对我来说这只是另一个例外。是这样的:

Caused by: java.lang.ClassCastException: org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean$$EnhancerBySpringCGLIB$$fedd095f cannot be cast to javax.persistence.EntityManagerFactory

JpaTransactionManager HibernateConfig 中的配置需要更改以解决此问题,如下所示:

@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(emf);
    return transactionManager;
}

与原始配置版本不同,在这种情况下 EntityManagerFactory 被注入为 Spring 托管 bean,而不是使用 HibernateConfig.entityManagerFactory() 方法创建的单独实例。

还进行了其他更改,但它们与问题主题没有直接关系。如果你需要我也可以提供。

它可能不合主题,但您的示例项目也可以通过一些额外的更改来改进,以防止将来出现问题。此改进如下:

  1. 为您使用的所有 Maven 插件明确定义版本和配置。至少是 Maven Compiler Plugin:
<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
        <source>${java.source}</source>
        <target>${java.target}</target>
        <encoding>UTF-8</encoding>
    </configuration>
</plugin>
  1. 遵循 Maven 项目结构约定。在你的情况下 test 相关的源代码必须放在 src/test/java 文件夹而不是 src/main/test:
my-app
|-- pom.xml
`-- src
    |-- main
    |   `-- java
    |       `-- com
    |           `-- mycompany
    |               `-- app
    |                   `-- App.java
    `-- test
        `-- java
            `-- com
                `-- mycompany
                    `-- app
                        `-- AppTest.java
  1. 最好定义自己的包名称,而不仅仅是 beansconfig。例如,它可以是 com.pneumokok.mvccom.pneumokok.servicecom.pneumokok.testcom.pneumokok.model。它可以帮助您 component scan 使用 base package 名称。

  2. 正如您正确指出的那样,有必要添加 javax.servlet-api 依赖项,但定义 provided 范围

  3. 很重要
<dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
</dependency>
  1. 最后但并非最不重要的一点 为每个应用程序层和环境使用单独的 Spring 上下文定义。在您的示例中,可以使用多个上下文定义:

src/main/resources/com/pneumokok/service/applicationContext.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:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.pneumokok.service"/>
    <!-- Enable annotation driven transaction management -->
    <tx:annotation-driven/>

    ...

</beans>

src/main/resources/com/pneumokok/mvc/applicationContext.xml

Spring MVC 层的上下文配置

<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/mvc 
    http://www.springframework.org/schema/mvc/spring-mvc.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.pneumokok.mvc"/>
    <!-- Enable annotation driven transaction management -->
    <mvc:annotation-driven/>

    ...

</beans>

src/main/resources/com/pneumokok/applicationContext.xml

Spring Servlet 容器环境的上下文配置

<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/mvc 
    http://www.springframework.org/schema/mvc/spring-mvc.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="com/pneumokok/service/applicationContext.xml"/>
    <import resource="com/pneumokok/mvc/applicationContext.xml"/>
    <import resource="com/pneumokok/dao/applicationContext.xml"/>

    <bean name="dataSource"> 
    //Servlet container DataSource configuration
    </bean>

    ...

</beans>

src/main/resources/com/pneumokok/test/applicationContext.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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/mvc 
    http://www.springframework.org/schema/mvc/spring-mvc.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="com/pneumokok/service/applicationContext.xml"/>
    <import resource="com/pneumokok/mvc/applicationContext.xml"/>
    <import resource="com/pneumokok/dao/applicationContext.xml"/>

    <bean name="dataSource"> 
    //Test DataSource configuration
    </bean>

    ...

</beans>