When Spring Test + JUnit 4 + JPA 2.0 is used for unit testing, the following requirements are met:
Many test cases are created under different packages. To test the persistence feature of Jap, you need to create some object classes that can be persisted, that is, because package1.entity and package2.entity are lazy, the object class names under each package are the same, but the access level is the package level, so there will be no import confusion, however, it is expected that only package1.entity will be loaded in JPA when test1 is executed, and only package1.entity will be loaded when Test2 is executed.
Package2.entity, such an easy way is by modifying META-INF/persistence. <class> XXX. xxx. the value of the package1.entity </class> label. However, if you modify the value, the test cases in other packages cannot be passed. Of course, you can also create many persistence. XML file, and then specify the persistence. XML
(You can load beans in Different Spring configuration files. but even if you do this, you will find your persistence. most of the content in the XML file is the same, but the values in the <class/> label are different, therefore, we began to consider whether we could configure the object class to be loaded when running the test case in the annotation at the test case level, so that the effect of this annotation is equivalent to persistence. the configuration of the <class/> label in the XML file XXX. xxx. package1.entityxxx. xxx. package1.test1 ------------------------- XXX. xxx. package2.entityxxx. xxx. package2.test1 specifies the object class to be loaded by JPA in the test case, as follows:
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:beans.xml")@Transactional@TransactionConfiguration(defaultRollback = false)@LoadEntities({ "com.jqd.examples.jpa2.idmapping.IdentityEntity" })public class IdMappingTests {
The loadentities annotation is defined as follows (custom)
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic @interface LoadEntities {String[] value();}
This does not need to be performed in persistence. the <class/> label is configured in XML, because the values in the label are declared along with the test case. you only need to configure public information in XML.Question: How can I notify the container of the loadentities annotation configured in the test case before creating the entitymanagerfactory instance?Analysis: instances of the entitymanagerfactory class are created and managed by spring, according to the spring configuration file beans. XML <bean id = "entitymanagerfactory" class = "org. springframework. orm. JPA. localentitymanagerfactorybean "> <property name =" persistenceunitname "value =" MySQL. persistence "/> </bean> we use the factory bean localentitymanagerfactorybean to create the entitymanagerfactory. Then, by looking at the annotations in the factory bean source code, we can find the following descriptions in the annotations: some comments in the localentitymanagerfactorybean class are as follows: Annotations tell us that only limited configuration is provided in localentitymanagerfactorybean. If you need more flexible configuration, you can use localcontainerentitymanagerfactorybean instead, so in the same package, find this class and trace the source code of the class localcontainerentitymanagerfactorybean. Some code and comments are as follows: this class has two dependencies: persistenceunitmanager and persistenceunitinfo. A final defaultpersistenceunit, it can be estimated that internalpersistenceunitmanager is an internal implementation of persistenceunitmanager, or it is a default implementation. The persistenceunitmanager attribute provides the corresponding
Set method, but the persistenceunitinfo attribute does not provide the set method, through the tracking code, we can find that the value of the persistenceunitinfo attribute is obtained using the persistenceunitmanager # obtaindefaperpersistenceunitinfo method to trace an implementation class mutablepersistenceunitinfo interface. Part of the Code is as follows: with this add method, we can add the class name specified by @ loadentities annotation in the test case. This is our ultimate solution.Question: What if I can get a reference to the persistenceunitinfo implementation class in localcontainerentitymanagerfactorybean?Continue to observe the source code of localcontainerentitymanagerfactorybean. We find that although there is no private persistenceunitpostprocessors [] postprocessors attribute declaration in the next method, however, we know that spring injection uses the set method, even if this class does not declare this attribute, however, if the set method is declared, we can also inject references to the instance of this class. observe that the persistenceunitpostprocessors interface is declared as follows: The persistenceunitpostprocessors interface code is as follows: This is a callback interface, callback after JPA processes the persistenceunitinfo object, and then the mutablepersistence Reference of unitinfo object (Note: The mutablepersistenceunitinfo class implements the persistenceunitinfo Interface) Is this what we need ?!, Create a new class to implement this interface. The code for customizing the loadentitypersistenceunitpostprocessor class is as follows: Modify the spring beans. xml configuration file as follows:Question: How does JUnit set the loadentities configured in the test case to the loadentityclassnames attribute in the loadentitypersistenceunitpostprocessor class?This problem can be implemented through a callback interface testexecutionlistener provided by spring test. You can customize an implementation class for this interface, or rewrite the required method loadannotationentitytestexecutionlistener by inheriting abstracttestexecutionlistener. The class code is as follows: the code in the corresponding test case is as follows! Appendix I: the entire implementation code idmappingtests. Java
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:beans.xml")@Transactional@TransactionConfiguration(defaultRollback = false)@TestExecutionListeners(listeners = { LoadAnnotationEntityTestExecutionListener.class,DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class })@LoadEntities({ "com.jqd.examples.jpa2.idmapping.IdentityEntity" })public class IdMappingTests {@PersistenceContext(name = "mysql.persistence")private EntityManager em;@Testpublic void saveIdentityEntity() {Assert.assertNotNull(em);}}
Loadannotationentitytestexecutionlistener. Java
public class LoadAnnotationEntityTestExecutionListener extends AbstractTestExecutionListener {@Overridepublic void beforeTestClass(TestContext testContext) throws Exception {Class<?> testClass = testContext.getTestClass();LoadEntities loadEntitiesAnnotation = testClass.getAnnotation(LoadEntities.class);if (loadEntitiesAnnotation != null) {String[] loadEntityClassNames = loadEntitiesAnnotation.value();LoadEntityPersistenceUnitPostProcessor.registerLoadEntityClassNames(Arrays.asList(loadEntityClassNames));}}}
Loadentitypersistenceunitpostprocessor. Java
public class LoadEntityPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor {private static List<String> loadEntityClassNames = new ArrayList<String>();public static void registerLoadEntityClassNames(List<String> loadEntities) {loadEntityClassNames.clear();loadEntityClassNames.addAll(loadEntities);}public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {for (String loadEntityClassName : loadEntityClassNames) {pui.addManagedClassName(loadEntityClassName);}}}
Beans. xml
<!-- JPA EntityManager --><bean id="entityManagerFactory"class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"><property name="persistenceUnitName" value="mysql.persistence" /><property name="persistenceUnitPostProcessors"><bean class="com.jqd.examples.jpa2.LoadEntityPersistenceUnitPostProcessor" /></property></bean>
Persistence. xml
<persistence-unit name="mysql.persistence"transaction-type="RESOURCE_LOCAL"><provider>org.hibernate.ejb.HibernatePersistence</provider><properties><property name="hibernate.show_sql" value="true" /><property name="hibernate.format_sql" value="true" /><property name="hibernate.hbm2ddl.auto" value="create" />
...... Appendix II: localcontainerentitymanagerfactorybean class partial dependency UML diagram