如何在Maven中configurationJPA进行testing
有没有办法在Maven项目中设置第二个persistence.xml文件,以便用于testing,而不是用于部署的正常testing?
我试着把一个persistence.xml放到src / test / resources / META-INF中,它被复制到target / test-classes / META-INF,但似乎是target / classes / META-INF(从src / main / resources)得到首选,尽pipemvn -X test
按照正确的顺序列出了类path条目:
[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 ...
我希望能够运行简单的hsqldbconfiguration的testing,而不必更改JPAconfiguration的部署版本,理想情况下直接在项目签出后不需要本地调整。
以下将适用于Maven 2.1+(在此之前,testing和包之间没有可以绑定执行的阶段)。
您可以使用maven-antrun-plugin将persistence.xmlreplace为testing期间的testing版本,然后在打包项目之前恢复正确的版本。
这个例子假定生产版本是src / main / resources / META-INF / persistence.xml,testing版本是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>
在一个EE6 / CDI / JPA项目中,testingsrc/test/resources/META-INF/persistence.xml
可以在没有任何进一步configuration的情况下正常工作。
在Spring中使用JPA时,以下工作在用于testing的应用程序上下文中:
<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>
这里/src/test/resources/META-INF/persistence.xml
(复制到target/test-classes
)优于/src/main/resources/META-INF/persistence.xml
(复制到target/classes
) 。
不幸的是, persistence.xml
文件的位置也决定了所谓的“ 持久性单元的根 ”,然后确定哪些类被扫描了@Entity
注解。 因此,使用/src/test/resources/META-INF/persistence.xml
将扫描target/test-classes
,而不是target/classes
(需要testing的类将在其中生存)。
因此,为了testing,需要明确地向persistence.xml
添加<class>
条目,以避免java.lang.IllegalArgumentException: Not an entity: class ...
通过使用不同的文件名(如persistence-TEST.xml
,可以避免对<class>
条目persistence-TEST.xml
,并将该文件放在与常规persistence.xml
文件完全相同的文件夹中。 然后你的testing文件夹中的Spring上下文可以引用<property name="persistenceXmlLocation" value="META-INF/persistence-TEST.xml" />
,Spring会在src/main
为你find它。
作为一种替代scheme,可以使persistence.xml
保持与实际应用程序和testing相同,只在src/main
定义一个。 大多数configuration,如驱动程序,方言和可选凭证可以在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>
看来多个persistence.xml文件是JPA的一个普遍问题,只能通过类加载技巧来解决。
解决方法是在一个persistence.xml文件中定义多个持久性单元,然后确保您的部署和testing代码使用不同的绑定(在Spring中可以在实体pipe理器工厂中设置“persistenceUnitName”属性)。 它污染你的部署文件与testingconfiguration,但如果你不介意,它的工作正常。
我尝试了ClassLoaderProxy的方法,但有问题,JPA注释的类不被处理为持久化类hibernate。
所以决定在不使用persistence.xml的情况下尝试一下。 优点是maven构build和Eclipse JUnittesting将不加修改地工作。
我有一个JUnittesting的persitent支持类。
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(); } }
我的testing类只是扩展PersistenceTestSupport并在TestCase.setup()中调用setup()。
唯一的缺点是保持持久化类,但是对于JUnittesting来说,这是可以接受的。
为testing添加一个persistance.xml: /src/test/resources/META-INF/persistence.xml
正如@Arjan所说,这将改变持久性单元的根和实体类将在目标/testing类中被扫描。 要处理这个问题,请将jar-file元素添加到此persistence.xml中:
/src/test/resources/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>
然后,将testing资源的过滤添加到您的pom.xml中:
<project> ... <build> ... <testResources> <testResource> <directory>src/test/resources</directory> <filtering>true</filtering> </testResource> </testResources> ... </build> ... </project>
这将工作,因为jar文件可以定位到目录,而不仅仅是jar文件。
我更喜欢使用不同的persistence.xml进行testing和生产的解决scheme,作为丰富的卖方后 (谢谢!!)。
但是需要改变:
<copy file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml"/>
对于:
<move file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml" overwrite="true"/>
为了将persistence.xml.properembedded到.jar文件中
这个答案听起来可能很愚蠢,但是我正在寻找一种方法,让我通过Run As
– > JUnit Test
从eclipse运行这些JUnit Test
。 这是我做到的:
@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")); // ... }
我只是复制testing/ persistence.xml到classes / persistence.xml。 这工作。
保留persistence.xml文件的两个副本。 一个用于testing,另一个用于正常构build。
默认的生命周期将构buildpersistence.xml复制到src / test / resources / META-INF
创build一个单独的configuration文件,在运行时将testingpersistence.xml复制到src / test / resources / META-INF
除非您明确列出所有类并添加,否则将使用Persistence.xml作为search实体类的起点。 所以如果你想用另外一个覆盖这个文件,比如src / test / resources,你必须在第二个persistence.xml中指定每一个实体类,否则就不会find实体类。
另一种解决scheme是使用maven-resources-plugin('copy-resources'goal)覆盖文件。 但是你必须覆盖它两次,一次用于testing(例如阶段过程testing类)和一次真正的包装(阶段“准备包”)。
我正在尝试做同样的事情。 我有一个解决scheme适用于我 – 你可能会有所不同(你可能不喜欢解决scheme…这是一个低级别)。
我在网上看到一篇文章,他们使用一个自定义的类加载器来做类似的事情,作为灵感。 如果有人可以看到如何改善,那么build议将是顺便说一句。 对于部署,我依赖于EntityManager的容器注入,但是为了testing,我使用这个代码自己创build它:
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的请求redirect到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"); } } }
只是为了更多地解释这一点:
- 有两个persistence.xml文件(一个名为persistence.xml,在testing之外使用,一个名为test-persist.xml,用于testing)。
- 自定义类加载器只对unit testing有效(对于部署,一切都是正常的)
- 自定义类加载器将“META-INF / persistence.xml”的请求redirect到testing版本(“META-INF / test-persist.xml”)。
我最初是碰到一些问题,因为Hibernate会恢复(用某种方法)到用来加载Hibernate的类加载器(至less我认为是这样)。 我发现把ClassLoader的切换代码(第一个块)作为一个静态块放在你的testing用例中,它将在Hibernate之前被加载,但是,根据你的unit testing结构,你可能还需要把相同的代码放在其他地方(呸)。
另一种方法是使用一个单独的persistence.xml进行testing(testing/../ META-INF / persistence.xml,但重写Scanner如下: –
testingpersistence.xml需要包含
<property name="hibernate.ejb.resource_scanner" value = "...TestScanner" />
新类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; } }
把它的persistence.xml放在自己的Maven项目中
当使用OpenEJB时,persistence.xml可以被替代描述符覆盖: http : //tomee.apache.org/alternate-descriptors.html
我build议使用不同的mavenconfiguration文件,你可以过滤你的database.proprerties文件,并有一个database.properties每个configuration文件。
这样,您就不必保留除.properties之外的任何其他configuration文件的重复项。
<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>
在unit testing的帮助下,集成testing失败,你就完成了。
<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>
现在你需要mvn test
你的unit testing和mvn verify -Pintegration
你的集成testing。 很明显,你应该在指定的(在configuration文件中)path(或其他地方,并改变path)创builddatabase.properties文件,
基于参考: http : //www.petrikainulainen.net/programming/tips-and-tricks/creating-profile-specific-configuration-files-with-maven/
这是Rich Seller的答案的延伸,正确处理了Hibernate在类path中查找多个persistence.xml文件和预testing状态恢复。
build立:
创build一个用于部署/打包的持久性文件,另一个用于testing:
-
SRC /主/资源/ persistence.xml中
-
src / test / resources / persistence- testing .xml
在你的pom.xml中添加到插件部分:
<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>
优于其他解决scheme
- 不需要额外的Java代码
- classpath上只有一个persistence.xml
- build设和testing都按预期进行
- 在控制台上描述输出(echo)
- 对于包装状态是100%恢复。 没有剩余的文件
这个用例的另一个select是添加多个持久化单元,一个用于生产,另一个用于testing,并相应地注入EntityManagerFactory。
将两个持久化单元放入实际项目的persistence.xml中,并让testing用例注入正确的EntityManager。 下面的例子说明了如何用guice做到这一点。 请注意,我已经留下了一些mockito嘲笑完整性,mockito特定的代码已被相应标记,不需要注入。
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; } }