Java 如何配置JPA以在Maven中进行测试
有没有办法在Maven项目中设置第二个persistence.xml文件,以便将其用于测试,而不是用于部署的普通文件 我尝试将persistence.xml放入src/test/resources/META-INF,它会被复制到target/test classes/META-INF中,但似乎target/classes/META-INF(src/main/resources的副本)更受欢迎,尽管Java 如何配置JPA以在Maven中进行测试,java,testing,maven-2,jpa,integration-testing,Java,Testing,Maven 2,Jpa,Integration Testing,有没有办法在Maven项目中设置第二个persistence.xml文件,以便将其用于测试,而不是用于部署的普通文件 我尝试将persistence.xml放入src/test/resources/META-INF,它会被复制到target/test classes/META-INF中,但似乎target/classes/META-INF(src/main/resources的副本)更受欢迎,尽管mvn-X test以正确的顺序列出了类路径条目: [DEBUG] Test Classpath :
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
...
我希望能够针对简单的hsqldb配置运行测试,而无需更改JPA配置的部署版本,理想情况下在项目签出后立即运行,而无需任何本地调整。保留persistence.xml文件的两个副本。一个用于测试,另一个用于正常构建 默认生命周期将build persistence.xml复制到src/test/resources/META-INF
创建一个单独的配置文件,当运行该配置文件时,它将把testing persistence.xml复制到src/test/resources/META-INF看来多个persistence.xml文件是JPA的一个普遍问题,只有通过类加载技巧才能解决
对我来说,一种解决方法是在一个persistence.xml文件中定义多个持久性单元,然后确保部署和测试代码使用不同的绑定(在Spring中,您可以在entity manager工厂上设置“persistenceUnitName”属性)。它会用测试配置污染您的部署文件,但如果您不介意它正常工作的话。Persistence.xml用作搜索实体类的起点,除非您明确列出所有类并添加。 因此,如果您想用另一个文件(比如src/test/resources)覆盖此文件,那么必须在第二个persistence.xml中指定每个实体类,否则将找不到实体类
另一个解决方案是使用maven资源插件覆盖文件(“copy-resources”目标)。但是,您必须覆盖它两次,一次用于测试(例如,阶段过程测试类),一次用于真正的打包(阶段“准备打包”)。我正在尝试做同样的事情。我有一个适合我的解决方案-你的可能会有所不同(而且你可能不喜欢这个解决方案…它有点低级) 我在网上看到一篇文章,他们使用一个定制的类加载器来做一些类似的事情,这给了我灵感。如果有人能看到如何改进,那么建议将是受欢迎的。对于部署,我依赖EntityManager的容器注入,但对于测试,我使用以下代码自己创建它:
final Thread currentThread = Thread.currentThread();
final ClassLoader saveClassLoader = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(new ClassLoaderProxy(saveClassLoader));
EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("test");
em = emFactory.createEntityManager();
然后,ClassLoaderProxy将尽可能小,只是将META-INF/persistence.xml请求重定向到META-INF/test-persistence.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");
}
}
}
public类ClassLoaderProxy扩展类加载器{
公共类装入器代理(最终类装入器父级){
超级();
}
@凌驾
公共枚举getResources(最终字符串名称)引发IOException{
if(!“META-INF/persistence.xml”.equals(name)){
返回super.getResources(name);
}否则{
println(“重定向persistence.xml以测试persistent.xml”);
返回super.getResources(“META-INF/test persist.xml”);
}
}
}
再解释一下:
我最初遇到了一些问题,因为Hibernate将(以某种方式)恢复到用于加载Hibernate的类加载器(至少我认为是这样)。我发现将类加载器切换代码(第一个块)作为静态块放在测试用例中,它将在Hibernate之前加载,但是,根据您的单元测试结构,您可能还需要将相同的代码放在其他地方(糟糕)。以下内容适用于Maven 2.1+(在此之前,在测试和包之间没有可以绑定执行的阶段) 您可以使用maven antrun插件在测试期间用测试版本替换persistence.xml,然后在打包项目之前恢复正确的版本 本例假设生产版本为src/main/resources/META-INF/persistence.xml,测试版本为src/test/resources/META-INF/persistence.xml,因此它们将分别复制到target/classes/META-INF和target/test classes/META-INF 将其封装到一个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>
maven antrun插件
1.3
复制测试持久性
过程测试资源
跑
恢复持久性
准备包装
跑
使用persistence.xml将测试放在自己的maven项目中作为富卖家,我更喜欢使用不同persistence.xml进行测试和生产的解决方案(谢谢!!)
但需要改变:
<copy file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml"/>
用于:
为了使persistence.xml.property不嵌入EE6/CDI/JPA项目中的.jar文件中,测试
src/test/resources/META-INF/persistence.xml
在没有任何进一步配置的情况下被很好地拾取
在Spring中使用JPA时,以下内容在用于测试的应用程序上下文中起作用:
这里,/src/test/resources/META-INF/persistence.xml
(复制到目标/测试类中
<move file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml" overwrite="true"/>
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();
}
}
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;
}
}
<properties>
<!-- Used to locate the profile specific configuration file. -->
<build.profile.id>default</build.profile.id>
<!-- Only unit tests are run by default. -->
<skip.integration.tests>true</skip.integration.tests>
<skip.unit.tests>false</skip.unit.tests>
<integration.test.files>**/*IT.java</integration.test.files>
</properties>
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<!--
Specifies the build profile id, which is used to find out the correct properties file.
This is not actually necessary for this example, but it can be used for other purposes.
-->
<build.profile.id>default</build.profile.id>
<skip.integration.tests>true</skip.integration.tests>
<skip.unit.tests>false</skip.unit.tests>
</properties>
<build>
<filters>
<!--
Specifies path to the properties file, which contains profile specific
configuration. In this case, the configuration file should be the default spring/database.properties file
-->
<filter>src/main/resources/META-INF/spring/database.properties</filter>
</filters>
<resources>
<!--
Placeholders found from files located in the configured resource directories are replaced
with values found from the profile specific configuration files.
-->
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<!--
You can also include only specific files found from the configured directory or
exclude files. This can be done by uncommenting following sections and adding
the configuration under includes and excludes tags.
-->
<!--
<includes>
<include></include>
</includes>
<excludes>
<exclude></exclude>
</excludes>
-->
</resource>
</resources>
</build>
</profile>
<profile>
<id>integration</id>
<properties>
<!--
Specifies the build profile id, which is used to find out the correct properties file.
This is not actually necessary for this example, but it can be used for other purposes.
-->
<build.profile.id>integration</build.profile.id>
<skip.integration.tests>false</skip.integration.tests>
<skip.unit.tests>true</skip.unit.tests>
</properties>
<build>
<filters>
<!--
Specifies path to the properties file, which contains profile specific
configuration. In this case, the configuration file is searched
from spring/profiles/it/ directory.
-->
<filter>src/main/resources/META-INF/spring/profiles/${build.profile.id}/database.properties</filter>
</filters>
<resources>
<!--
Placeholders found from files located in the configured resource directories are replaced
with values found from the profile specific configuration files.
-->
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<!--
You can also include only specific files found from the configured directory or
exclude files. This can be done by uncommenting following sections and adding
the configuration under includes and excludes tags.
-->
<!--
<includes>
<include></include>
</includes>
<excludes>
<exclude></exclude>
</excludes>
-->
</resource>
</resources>
</build>
</profile>
</profiles>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<junitArtifactName>org.junit:com.springsource.org.junit</junitArtifactName>
<!--see: https://issuetracker.springsource.com/browse/EBR-220-->
<printSummary>false</printSummary>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
<!-- Skips unit tests if the value of skip.unit.tests property is true -->
<skipTests>${skip.unit.tests}</skipTests>
<!-- Excludes integration tests when unit tests are run. -->
<excludes>
<exclude>${integration.test.files}</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.12</version>
<configuration>
<!-- Skips integration tests if the value of skip.integration.tests property is true -->
<skipTests>${skip.integration.tests}</skipTests>
<includes>
<include>${integration.test.files}</include>
</includes>
<forkMode>once</forkMode>
<!--
<reuseForks>false</reuseForks>
<forkCount>1</forkCount>
-->
</configuration>
<executions>
<execution>
<id>integration-test</id>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
<execution>
<id>verify</id>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
@BeforeClass
public static void setUp() throws IOException {
Files.copy(new File("target/test-classes/META-INF/persistence.xml"), new File("target/classes/META-INF/persistence.xml"));
// ...
}
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="com.some.project">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jar-file>${project.basedir}/target/classes</jar-file>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/test_database" />
<property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />
<property name="javax.persistence.jdbc.user" value="user" />
<property name="javax.persistence.jdbc.password" value="..." />
</properties>
</persistence-unit>
</persistence>
<project>
...
<build>
...
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
...
</build>
...
</project>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
<executions>
<execution>
<id>copy-test-persistence</id>
<phase>process-test-resources</phase>
<configuration>
<tasks>
<echo>renaming deployment persistence.xml</echo>
<move file="${project.build.outputDirectory}/META-INF/persistence.xml" tofile="${project.build.outputDirectory}/META-INF/persistence.xml.proper"/>
<echo>replacing deployment persistence.xml with test version</echo>
<copy file="${project.build.testOutputDirectory}/META-INF/persistence-testing.xml" tofile="${project.build.outputDirectory}/META-INF/persistence.xml" overwrite="true"/>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
<execution>
<id>restore-persistence</id>
<phase>prepare-package</phase>
<configuration>
<tasks>
<echo>restoring the deployment persistence.xml</echo>
<move file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml" overwrite="true"/>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
public class HibernateTestDatabaseProvider extends AbstractModule {
private static final ThreadLocal<EntityManager> ENTITYMANAGER_CACHE = new ThreadLocal<>();
@Override
public void configure() {
}
@Provides
@Singleton
public EntityManagerFactory provideEntityManagerFactory() {
return Persistence.createEntityManagerFactory("my.test.persistence.unit");
}
@Provides
public CriteriaBuilder provideCriteriaBuilder(EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.getCriteriaBuilder();
}
@Provides
public EntityManager provideEntityManager(EntityManagerFactory entityManagerFactory) {
EntityManager entityManager = ENTITYMANAGER_CACHE.get();
if (entityManager == null) {
// prevent commits on the database, requires mockito. Not relevant for this answer
entityManager = spy(entityManagerFactory.createEntityManager());
EntityTransaction et = spy(entityManager.getTransaction());
when(entityManager.getTransaction()).thenReturn(et);
doNothing().when(et).commit();
ENTITYMANAGER_CACHE.set(entityManager);
}
return entityManager;
}
}