Java 如何配置 JPA 以在 Maven 中进行测试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/385532/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 14:05:55  来源:igfitidea点击:

How to configure JPA for testing in Maven

javatestingmaven-2jpaintegration-testing

提问by Peter Becker

Is there a way to set up a second persistence.xml file in a Maven project such that it is used for testing instead of the normal one that is used for deployment?

有没有办法在 Maven 项目中设置第二个 persistence.xml 文件,以便它用于测试而不是用于部署的普通文件?

I tried putting a persistence.xml into src/test/resources/META-INF, which gets copied into target/test-classes/META-INF, but it seems target/classes/META-INF (the copy from the src/main/resources) gets preferred, despite mvn -X testlisting the classpath entries in the right order:

我尝试将一个持久性.xml 放入 src/test/resources/META-INF,它被复制到 target/test-classes/META-INF,但它似乎是 target/classes/META-INF(来自 src/main /resources) 获得首选,尽管mvn -X test以正确的顺序列出了类路径条目:

[DEBUG] Test Classpath :
[DEBUG]   /home/uqpbecke/dev/NetBeansProjects/UserManager/target/test-classes
[DEBUG]   /home/uqpbecke/dev/NetBeansProjects/UserManager/target/classes
[DEBUG]   /home/uqpbecke/.m2/repository/junit/junit/4.5/junit-4.5.jar
...

I would like to be able to run tests against a simple hsqldb configuration without having to change the deployment version of the JPA configuration, ideally straight after project checkout without any need for local tweaking.

我希望能够针对简单的 hsqldb 配置运行测试,而无需更改 JPA 配置的部署版本,理想情况下,在项目签出后无需任何本地调整即可直接运行。

回答by TRF

Keep two copies of persistence.xml file. One for testing and another for normal build.

保留两份persistence.xml 文件。一个用于测试,另一个用于正常构建。

The default life cycle copy the build persistence.xml to src/test/resources/META-INF

默认生命周期将构建的persistence.xml复制到src/test/resources/META-INF

Create a separate profile which when run will copy the testing persistence.xml to src/test/resources/META-INF

创建一个单独的配置文件,运行时将测试 persistence.xml 复制到 src/test/resources/META-INF

回答by Peter Becker

It seems multiple persistence.xml files is a general problem with JPA, solved only by classloading tricks.

似乎多个 persistence.xml 文件是 JPA 的一个普遍问题,只能通过类加载技巧来解决。

A workaround that works for me is to define multiple persistence units in one persistence.xml file and then make sure that your deployment and test code use a different binding (in Spring you can set the "persistenceUnitName" property on the entity manager factory). It pollutes your deployment file with the test configuration, but if you don't mind that it works ok.

对我有用的解决方法是在一个 persistence.xml 文件中定义多个持久性单元,然后确保您的部署和测试代码使用不同的绑定(在 Spring 中,您可以在实体管理器工厂中设置“persistenceUnitName”属性)。它会使用测试配置污染您的部署文件,但如果您不介意它可以正常工作。

回答by Peter Becker

Persistence.xml is used as a starting point to search for entity classes unless you list all classes explicitly and add . So if you want to override this file with another one, say from src/test/resources, you have to specify every single entity class in this second persistence.xml otherwise no entity class would be found.

Persistence.xml 用作搜索实体类的起点,除非您明确列出所有类并添加 . 因此,如果您想用另一个文件覆盖此文件,例如来自 src/test/resources,则必须在第二个 persistence.xml 中指定每个实体类,否则将找不到实体类。

Another solution would be to overwrite the file using the maven-resources-plugin ('copy-resources' goal). But then you have to overwrite it twice, once for testing (e.g. phase process-test-classes) and once for the real packaging (phase 'prepare-package').

另一种解决方案是使用 maven-resources-plugin('copy-resources' 目标)覆盖文件。但是随后您必须将其覆盖两次,一次用于测试(例如阶段 process-test-classes),一次用于实际包装(阶段 'prepare-package')。

回答by macbutch

I'm trying to do the same thing. I have a solution that works for me - yours may vary (and you might not love the solution... it's a bit low-level).

我正在尝试做同样的事情。我有一个适合我的解决方案 - 你的可能会有所不同(你可能不喜欢这个解决方案......它有点低级)。

I came across an article on the net where they were using a custom class loader to do something similar which served as inspiration. If anyone can see how to improve then suggestions would be welcome btw. For deployment I rely on container injection of the EntityManager but for testing I create it myself using this code:

我在网上看到一篇文章,他们使用自定义类加载器来做类似的事情,这给了我灵感。如果有人能看到如何改进,那么欢迎提出建议。对于部署,我依赖于 EntityManager 的容器注入,但为了测试,我使用以下代码自己创建它:

final Thread currentThread = Thread.currentThread();
final ClassLoader saveClassLoader = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(new ClassLoaderProxy(saveClassLoader));

EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("test");
em = emFactory.createEntityManager();

Then the ClassLoaderProxy is about as minimal as you can get and just redirects requests for META-INF/persistence.xml to META-INF/test-persist.xml:

然后 ClassLoaderProxy 尽可能小,并且只是将 META-INF/persistence.xml 的请求重定向到 META-INF/test-persist.xml:

public class ClassLoaderProxy extends ClassLoader {

    public ClassLoaderProxy(final ClassLoader parent) {
        super();
    }

    @Override
    public Enumeration<URL> getResources(final String name) throws IOException {
        if (!"META-INF/persistence.xml".equals(name)) {
            return super.getResources(name);
        } else {
            System.out.println("Redirecting persistence.xml to test-persist.xml");
            return super.getResources("META-INF/test-persist.xml");
        }
    }
}

Just to explain this a bit more:

只是为了进一步解释这一点:

  1. There are two persistence.xml files (one named persistence.xml that is used outside testing and one named test-persist.xml that is used for tests).
  2. The custom class loader is onlyactive for unit tests (for deployment everything is normal)
  3. The custom class loader redirects requests for "META-INF/persistence.xml" to the test version ("META-INF/test-persist.xml").
  1. 有两个persistence.xml 文件(一个名为persistence.xml 的文件用于外部测试,一个名为test-persist.xml 的文件用于测试)。
  2. 自定义类加载器对单元测试有效(部署一切正常)
  3. 自定义类加载器将对“META-INF/persistence.xml”的请求重定向到测试版本(“META-INF/test-persist.xml”)。

I was originally hitting some problems because Hibernate will revert back (somehow) to the classloader that was used to load Hibernate (at least I think that is what was going on). I've found that putting the ClassLoader switching code (the first block) as a static block in your Test case it will get loaded before Hibernate but that, depending on your unit test structure you may also need to put the same code in other places (yuck).

我最初遇到了一些问题,因为 Hibernate 会(以某种方式)恢复到用于加载 Hibernate 的类加载器(至少我认为这是发生了什么)。我发现将 ClassLoader 切换代码(第一个块)作为静态块放在您的测试用例中,它将在 Hibernate 之前加载,但是,根据您的单元测试结构,您可能还需要将相同的代码放在其他地方(呸)。

回答by Rich Seller

The following will work for Maven 2.1+ (prior to that there wasn't a phase between test and package that you could bind an execution to).

以下内容适用于 Maven 2.1+(在此之前,测试和包之间没有可以绑定执行的阶段)。

You can use the maven-antrun-plugin to replace the persistence.xml with the test version for the duration of the tests, then restore the proper version before the project is packaged.

您可以在测试期间使用maven-antrun-plugin 将persistence.xml 替换为测试版本,然后在项目打包前恢复正确的版本。

This example assumes the production version is src/main/resources/META-INF/persistence.xml and the test version is src/test/resources/META-INF/persistence.xml, so they will be copied to target/classes/META-INF and target/test-classes/META-INF respectively.

此示例假设生产版本为 src/main/resources/META-INF/persistence.xml,测试版本为 src/test/resources/META-INF/persistence.xml,因此它们将被复制到 target/classes/META -INF 和 target/test-classes/META-INF 分别。

It would be more elegant to encapsulate this into a mojo, but as you're only copying a file, it seems like overkill.

将它封装到一个 mojo 会更优雅,但由于您只是复制一个文件,这似乎有点过头了。

<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.3</version>
  <executions>
    <execution>
      <id>copy-test-persistence</id>
      <phase>process-test-resources</phase>
      <configuration>
        <tasks>
          <!--backup the "proper" persistence.xml-->
          <copy file="${project.build.outputDirectory}/META-INF/persistence.xml" tofile="${project.build.outputDirectory}/META-INF/persistence.xml.proper"/>
          <!--replace the "proper" persistence.xml with the "test" version-->
          <copy file="${project.build.testOutputDirectory}/META-INF/persistence.xml" tofile="${project.build.outputDirectory}/META-INF/persistence.xml"/>
        </tasks>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
    <execution>
      <id>restore-persistence</id>
      <phase>prepare-package</phase>
      <configuration>
        <tasks>
          <!--restore the "proper" persistence.xml-->
          <copy file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml"/>
        </tasks>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
  </executions>
</plugin>

回答by balmaster

put tests in own maven project with its persistence.xml

使用其persistence.xml 将测试放入自己的maven 项目中

回答by d.marzo

I prefer the solution of using different persistence.xml for testing and production as Rich Seller post(thanks!!).

我更喜欢使用不同的 persistence.xml 进行测试和生产作为 Rich Seller帖子的解决方案(谢谢!!)。

But need to change:

但需要改变:

<copy file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml"/>

for:

为了:

<move file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml" overwrite="true"/>

In order persistence.xml.proper not embedded in .jar file

为了persistence.xml.proper没有嵌入到.jar文件中

回答by Arjan

In an EE6/CDI/JPA project, a test src/test/resources/META-INF/persistence.xmlis picked up just fine without any further configuration.

在 EE6/CDI/JPA 项目中,src/test/resources/META-INF/persistence.xml无需任何进一步配置即可正常进行测试。

When using JPA in Spring, the following works in the application context used for testing:

在 Spring 中使用 JPA 时,以下在用于测试的应用程序上下文中起作用:

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <!--
        JPA requires META-INF/persistence.xml, but somehow prefers the one
        in classes/META-INF over the one in test-classes/META-INF. Spring
        to the rescue, as it allows for setting things differently, like by
        referring to "classpath:persistence-TEST.xml". Or, simply referring
        to "META-INF/persistence.xml" makes JPA use the test version too: 
    -->
    <property name="persistenceXmlLocation" value="META-INF/persistence.xml" />

    <!-- As defined in /src/test/resources/META-INF/persistence.xml -->
    <property name="persistenceUnitName" value="myTestPersistenceUnit" />
    <property name="jpaVendorAdapter">
        <bean
            class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        </bean>
    </property>
</bean>

Here, /src/test/resources/META-INF/persistence.xml(copied into target/test-classes) would be preferred over /src/main/resources/META-INF/persistence.xml(copied into target/classes).

在这里,/src/test/resources/META-INF/persistence.xml(复制到target/test-classes)比/src/main/resources/META-INF/persistence.xml(复制到target/classes)更受欢迎。

Unfortunately, the location of the persistence.xmlfile also determines the so-called "persistence unit's root", which then determines which classes are scanned for @Entityannotations. So, using /src/test/resources/META-INF/persistence.xmlwould scan classes in target/test-classes, not classes in target/classes(where the classes that need to be tested would live).

不幸的是,persistence.xml文件的位置也决定了所谓的“持久化单元的根”,然后它决定了扫描哪些类以获取@Entity注释。因此, using/src/test/resources/META-INF/persistence.xml将扫描 中的类target/test-classes,而不是中的类target/classes(需要测试的类所在的位置)。

Hence, for testing, one would need to explicitly add <class>entries to persistence.xml, to avoid java.lang.IllegalArgumentException: Not an entity: class .... The need for <class>entries can be avoided by using a different file name, like persistence-TEST.xml, and put that file in the very same folder as the regular persistence.xmlfile. The Spring context from your test folder can then just refer to <property name="persistenceXmlLocation" value="META-INF/persistence-TEST.xml" />, and Spring will find it for you in src/main.

因此,对于测试,需要显式地向 中添加<class>条目persistence.xml,以避免java.lang.IllegalArgumentException: Not an entity: class ...。需要<class>的条目可以通过使用不同的文件名,如避免persistence-TEST.xml,并把该文件中的非常相同的文件夹常规persistence.xml文件。然后,您的测试文件夹中的 Spring 上下文可以直接引用<property name="persistenceXmlLocation" value="META-INF/persistence-TEST.xml" />,并且 Spring 会在src/main.

As an alternative, one might be able to keep persistence.xmlthe same for the actual application and the tests, and only define one in src/main. Most configuration such as the drivers, dialect and optional credentials can be done in the Spring context instead. Also settings such as hibernate.hbm2ddl.autocan be passed in the context:

作为替代方案,您可以persistence.xml在实际应用程序和测试中保持相同,并且只在src/main. 大多数配置(例如驱动程序、方言和可选凭据)都可以在 Spring 上下文中完成。还hibernate.hbm2ddl.auto可以在上下文中传递诸如此类的设置:

<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <!-- For example: com.mysql.jdbc.Driver or org.h2.Driver -->
    <property name="driverClassName" value="#{myConfig['db.driver']}" />
    <!-- For example: jdbc:mysql://localhost:3306/myDbName or 
        jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 -->
    <property name="url" value="#{myConfig['db.url']}" />
    <!-- Ignored for H2 -->
    <property name="username" value="#{myConfig['db.username']}" />
    <property name="password" value="#{myConfig['db.password']}" />
</bean>

<bean id="jpaAdaptor"
    class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <!-- For example: org.hibernate.dialect.MySQL5Dialect or 
        org.hibernate.dialect.H2Dialect -->
    <property name="databasePlatform" value="#{myConfig['db.dialect']}" />
</bean>

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter" ref="jpaAdapter" />
    <property name="jpaProperties">
        <props>
            <!-- For example: validate, update, create or create-drop -->
            <prop key="hibernate.hbm2ddl.auto">#{myConfig['db.ddl']}</prop>
            <prop key="hibernate.show_sql">#{myConfig['db.showSql']}</prop>
            <prop key="hibernate.format_sql">true</prop>
        </props>
    </property>
</bean>

回答by luzzy

I tried the ClassLoaderProxy approach but had the problem that the JPA annotated classes are not handled as persistent classes by hibernate.

我尝试了 ClassLoaderProxy 方法,但遇到了一个问题,即 JPA 注释的类没有被 hibernate 作为持久类处理。

So decided to try it without using persistence.xml. The advantage is that the maven build and the Eclipse JUnit test will work without modifications.

所以决定尝试不使用persistence.xml。优点是 maven 构建和 Eclipse JUnit 测试无需修改即可工作。

I have a persitent support class for JUnit testing.

我有一个用于 JUnit 测试的持久支持类。

public class PersistenceTestSupport {

    protected EntityManager em;
    protected EntityTransaction et;

    /**
     * Setup the the {@code EntityManager} and {@code EntityTransaction} for
     * local junit testing.
     */
    public void setup() {

        Properties props = new Properties();
        props.put("hibernate.hbm2ddl.auto", "create-drop");
        props.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        props.put("hibernate.connection.url", "jdbc:mysql://localhost/db_name");
        props.put("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
        props.put("hibernate.connection.username", "user");
        props.put("hibernate.connection.password", "****");

        Ejb3Configuration cfg = new Ejb3Configuration();
        em = cfg.addProperties(props)
            .addAnnotatedClass(Class1.class)
            .addAnnotatedClass(Class2.class)
            ...
                    .addAnnotatedClass(Classn.class)
            .buildEntityManagerFactory()
            .createEntityManager();

        et = em.getTransaction();
    }
}

My test classes just extend PersistenceTestSupport and call the setup() in TestCase.setup().

我的测试类只是扩展 PersistenceTestSupport 并在 TestCase.setup() 中调用 setup()。

The only drawback is to keep the persistent classes up todate, but for JUnit testing this is acceptable for me.

唯一的缺点是保持持久类是最新的,但是对于 JUnit 测试,这对我来说是可以接受的。

回答by Steve Higham

Another approach is to use a separate persistence.xml for testing (test/../META-INF/persistence.xml but override the Scanner as follows: -

另一种方法是使用单独的persistence.xml 进行测试(test/../META-INF/persistence.xml 但覆盖扫描器如下:-

testing persistence.xml needs to contain

测试persistence.xml需要包含

<property name="hibernate.ejb.resource_scanner" value = "...TestScanner" />

<property name="hibernate.ejb.resource_scanner" value = "...TestScanner" />

Code for new class TestScanner is as follows.

新类 TestScanner 的代码如下。

import java.lang.annotation.Annotation;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Set;
import org.hibernate.ejb.packaging.NamedInputStream;
import org.hibernate.ejb.packaging.NativeScanner;


public class TestScanner extends NativeScanner
{

@Override
public Set <Class <?> > 
getClassesInJar (URL jar, Set <Class <? extends Annotation> > annotations)
{  return super.getClassesInJar (getUpdatedURL (jar), annotations); }

@Override
public Set <NamedInputStream> 
getFilesInJar (URL jar, Set <String> patterns)
{  return super.getFilesInJar (getUpdatedURL (jar), patterns); }

@Override
public Set <Package> 
getPackagesInJar (URL jar, Set <Class <? extends Annotation> > annotations)
{  return super.getPackagesInJar (getUpdatedURL (jar), annotations); }

private URL getUpdatedURL (URL url)
{
  String oldURL = url.toExternalForm ();
  String newURL = oldURL.replaceAll ("test-classes", "classes");
  URL result;
  try {
    result = newURL.equals (oldURL) ? url : new URL (newURL);
  } catch (MalformedURLException e)
  {  // Whatever  }
  return result;
}

}