Multiple persistence.xml files and JPA

When using JPA as the standard for doing object persistence, an important part of its configuration is utilizing a persistence.xml. The persistence.xml file is for configuring a Data Source to the Entities that you want to interact with. The persistence.xml usually will list classes that contain JPA annotations, mapping files that contain binding configuration, or archive files like jars that contain annotated classes or mapping files. One of the downsides of the development approach of trying to compartmentalize service functionality is that there might be multiple persistence.xml files spread through the jar files that are pulled together in a single service war. Without some kind of special merging capabilities, only one of those persistence.xml files will be used and the last one found on the classpath will win.  Therefore, if I have a persistence.xml file that lists all the orm.xml files I have for models and then produce a different persistence.xml file for a service with an its own orm.xml (named query), if the service orm.xml is the last one in the classpath, Hibernate will then complain that it can’t find the orm.xml files for the models that are used in the query.  There are three articles I want to look at in order to resolve this issue:

http://ancientprogramming.blogspot.com/2007/05/multiple-persistencexml-files-and.html

http://labs.bsb.com/2010/10/flexible-jpa-configuration-with-spring/

http://labs.bsb.com/2010/11/configuring-modular-jpa-applications-with-spring/

Based on these articles (specifically the second two), I am going to create a class called MultiConfigAwarePersistenceUnitManager.  One thing to understand is what was mentioned before, I get access to the meta-data for persisting objects via JPA annotations, mapping files, or jar files that contain one or both.  The article deals with merging multiple files from jars and this is not necessarily how the persistence is being done, so the class from the article will be modified to merge multiple the orm.xml files from multiple persistence files.

MultipleConfigAwarePersistenceUnitManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
import java.net.URL;

import javax.persistence.spi.PersistenceUnitInfo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;

/**
 * An extension to the {@link DefaultPersistenceUnitManager} that is able to merge multiple <tt>persistence.xml</tt>
 * associated to the same persistence unit name.
 * <p />
 * If a module persistence unit defines managed classes explicitly, only adds the specified classes. If the module
 * persistence unit does not define any managed classes, module scanning is assumed: any entity classes defined in the
 * module holding the persistence unit will be associated to the main one.
 */

public class MultiConfigAwarePersistenceUnitManager extends DefaultPersistenceUnitManager {

    /** The Constant LOGGER. */
    private static final Logger LOGGER = LoggerFactory.getLogger(MultiConfigAwarePersistenceUnitManager.class);

    /** The strict. */
    private transient boolean strict = false;

    /** The persistence unit name. */
    private transient String persistenceUnitName = "default";

    /**
     * {@inheritDoc}.
     */

    @Override
    protected void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
        super.postProcessPersistenceUnitInfo(pui);

        // This our template persistence unit that I am post-processing
        // so let's just skip.
        if (isApplicationPersistenceUnit(pui)) {
            return;
        }

        final MutablePersistenceUnitInfo mainPui = getMainPersistenceUnitInfo(pui);

        if (strict) {
            pui.addJarFileUrl(pui.getPersistenceUnitRootUrl());
        }

        if (mainPui != null) {
            if (strict) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Merging existing jar file urls " + mainPui.getJarFileUrls()
                            + " to persistence unit [" + pui.getPersistenceUnitName() + "]");
                }
                copyPersistenceUnit(mainPui, pui);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Persistence unit[" + pui.getPersistenceUnitName() + "] has now "
                            + "the following jar file urls " + pui.getJarFileUrls() + "");
                }
            } else {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Merging information from [" + pui.getPersistenceUnitName() + "] " + "to ["
                            + mainPui.getPersistenceUnitName() + "]");
                }
                mergePersistenceUnit(pui, mainPui);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Persistence unit[" + mainPui.getPersistenceUnitName() + "] has now "
                            + "the following jar file urls " + mainPui.getJarFileUrls());
                }
            }
        } else if (!pui.getPersistenceUnitName().equals(persistenceUnitName)) {
            LOGGER.debug("Adding persistence unit [" + pui.getPersistenceUnitName() + "] as is (no merging)");
        }
    }

    /**
     * Specifies if the manager should process the persistence units in strict mode. When enabled, only the persistence
     * unit that have the exact same names as an existing one are merged. When disabled (the default), if the name of
     * the persistence unit matches the prefix, it is merged with the persistence unit defined by the prefix.
     *
     * @param strict
     *                        if merging occurs on an exact match or on the prefix only
     */

    public void setStrict(boolean strict) {
        this.strict = strict;
    }

    /**
     * Sets the name of the persistence unit that should be used. If no such persistence unit exists, an exception will
     * be thrown, preventing the factory to be created.
     * <p />
     * When the <tt>strict</tt> mode is disabled, this name is used to find all matching persistence units based on a
     * prefix. Say for instance that the <tt>persistenceUnitName</tt> to use is <tt>pu</tt>, the following applies:
     *
     * pu-base will be merged pufoo will be merged base-pu will <b>not</b> be merged
     *
     * Make sure to configure your entity manager factory to use this name as the persistence unit
     *
     * @param persistenceUnitName
     *                        the name of the persistence unit to use
     */

    public void setPersistenceUnitName(String persistenceUnitName) {
        this.persistenceUnitName = persistenceUnitName;
    }

    /**
     * Merges a persistence unit to another one. Takes care of handling both managed classes and urls. If the
     * persistence unit has managed classes, only merge these and prevents scanning. If no managed classes are
     * available, add the url of the module for entity scanning.
     *
     * @param from
     *                        the persistence unit to handle
     * @param to
     *                        the target (merged) persistence unit
     */

    protected void mergePersistenceUnit(MutablePersistenceUnitInfo from, MutablePersistenceUnitInfo to) {
        if (from.getMappingFileNames().size() != 0) {
            for (String s : from.getMappingFileNames()) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Adding entity [" + s + "]");
                }
                to.addMappingFileName(s);
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Added [" + from.getMappingFileNames().size() + "] mapping file to " + "persistence unit["
                        + to.getPersistenceUnitName() + "]");
            }
        } else if (from.getManagedClassNames().size() != 0) {
            for (String s : from.getManagedClassNames()) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Adding entity [" + s + "]");
                }
                to.addManagedClassName(s);
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Added [" + from.getManagedClassNames().size() + "] managed classes to "
                        + "persistence unit[" + to.getPersistenceUnitName() + "]");
            }
        } else {
            to.addJarFileUrl(from.getPersistenceUnitRootUrl());
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Added [" + from.getPersistenceUnitRootUrl() + "] for entity scanning "
                        + "to persistence unit[" + to.getPersistenceUnitName() + "]");
            }
        }
    }

    /**
     * Copies a persistence unit to another one. Takes care of copying both managed classes and urls.
     *
     * @param from
     *                        the persistence unit to copy
     * @param to
     *                        the target (copied) persistence unit
     */

    protected void copyPersistenceUnit(MutablePersistenceUnitInfo from, MutablePersistenceUnitInfo to) {
        for (String s : from.getMappingFileNames()) {
            to.addMappingFileName(s);
        }
        for (String s : from.getManagedClassNames()) {
            to.addManagedClassName(s);
        }
        // Copy jar file urls
        for (URL url : from.getJarFileUrls()) {
            to.addJarFileUrl(url);
        }
    }

    /**
     * Specifies whether the specified persistence unit is the template one we use to merge.
     *
     * @param pui
     *                        the persistence unit to test
     * @return <tt>true</tt> if the persistence unit is the target, template persistence unit
     */

    private boolean isApplicationPersistenceUnit(MutablePersistenceUnitInfo pui) {
        return (!strict && persistenceUnitName.equals(pui.getPersistenceUnitName()));
    }

    /**
     * Returns the main {@link MutablePersistenceUnitInfo} to use, based on the given {@link MutablePersistenceUnitInfo}
     * and the settings of the manager.
     * <p />
     * In strict mode, returns the declared persistence unit with the specified name. In non strict mode and if the
     * specified <tt>pui</tt> starts with the configured <tt>persistenceUnitName</tt>, returns the template persistence
     * unit info used for the merging.
     *
     * @param pui
     *                        the persistence unit info to handle
     * @return the persistence unit info to use for the merging
     */

    private MutablePersistenceUnitInfo getMainPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
        MutablePersistenceUnitInfo result = null;
        if (strict) {
            return getPersistenceUnitInfo(pui.getPersistenceUnitName());
        }
        if (pui.getPersistenceUnitName().startsWith(persistenceUnitName)) {
            // We have a match, retrieve our persistence unit name then
            result = getPersistenceUnitInfo(persistenceUnitName);
            // Sanity check
            if (result == null) {
                throw new IllegalStateException(
                        "No persistence unit found with name ["
                                + persistenceUnitName
                                + "] "
                                + "so no merging is possible. It usually means that the bootstrap-persistence.xml has not been "
                                + "included in the list of persistence.xml location(s). Check your configuration as it "
                                + "should be the first in the list!");
            }
        }
        // Nothing has been found
        return result;
    }

    /**
     * {@inheritDoc}.
     */

    @Override
    public PersistenceUnitInfo obtainPersistenceUnitInfo(String puName) {
        PersistenceUnitInfo persistenceUnitInfo = super.obtainPersistenceUnitInfo(puName);
        persistenceUnitInfo.getJarFileUrls().remove(persistenceUnitInfo.getPersistenceUnitRootUrl());
        return persistenceUnitInfo;
    }

}

This is done by creating a bootstrap persistence.xml file and providing a key name for a data sources persistence unit (for this example I will call it common):

common-bootstrap-persistence.xml or common-bootstrap-persistence-test.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<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="commonDataServiceTestPu">
    </persistence-unit>

</persistence>

commonDataServiceTestPu is the key name for this data source and for doing the merge.  In the Spring Context file, I will set this value to be injected into our persistence merging class:

Common Jdbc Spring Context

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="commonPersistenceUnitManager"
      class="com.foo.MultiConfigAwarePersistenceUnitManager">
    <property name="persistenceXmlLocations">
        <list>
        classpath*:META-INF/jpa/common-bootstrap-persistence.xml
        classpath*:META-INF/jpa/common-persistence.xml
        </list>
    </property>
    <property name="defaultDataSource" ref="commonDataSource"/>
    <property name="persistenceUnitPostProcessors" ref="commonPersistenceUnitPostProcessor"/>
    <property name="persistenceUnitName" value="commonDataServiceTestPu"/>
</bean>

Here I am configuring our multi persistence manager, and I tell it where to find persistence files (the first one always being the bootstrapped file).  persistenceUnitName is where I set that bootstrap key.

So the MultiConfigAwarePersistenceUnitManager loads the common-bootstrap-persistence.xml file into memory and verifies that the persistenceUnitName matches.  Then as it finds other persistence.xml files, if the name of those files starts with this persistenceUnitName, then the orm files from that newly opened persistence.xml file are merged into the bootstrap persistence context.  Once this is complete, all common-persistence.xml will be merged into the context for the common-bootstrap-persistence.xml.

You can see this by running a JUnit where the Spring Context files are loaded:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.foo.AddressSearchServiceTest
2011-03-18 16:18:42,917 [main] DEBUG com.foo.JtaPersistenceUnitPostProcessor - Enriching the persistence unit info with the non-jta aware data source.
2011-03-18 16:18:42,933 [main] DEBUG com.foo.JtaPersistenceUnitPostProcessor - Enriching the persistence unit info with the non-jta aware data source.
2011-03-18 16:18:42,933 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Merging information from [commonDataServiceTestPuModule] to [commonDataServiceTestPu]
2011-03-18 16:18:42,949 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Adding entity [com/foo/AddressSearchService-orm.xml]
2011-03-18 16:18:42,949 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Added [1] mapping file to persistence unit[commonDataServiceTestPu]
2011-03-18 16:18:42,949 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Persistence unit[commonDataServiceTestPu] has now the following jar file urls []
2011-03-18 16:18:42,949 [main] DEBUG com.foo.JtaPersistenceUnitPostProcessor - Enriching the persistence unit info with the non-jta aware data source.
2011-03-18 16:18:42,949 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Merging information from [commonDataServiceTestPuModule] to [commonDataServiceTestPu]
2011-03-18 16:18:42,949 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Adding entity [com/foo/Address-orm.xml]
2011-03-18 16:18:42,949 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Adding entity [com/foo/Phone-orm.xml]
2011-03-18 16:18:42,949 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Adding entity [com/foo/Person-orm.xml]
2011-03-18 16:18:42,949 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Adding entity [com/foo/Email-orm.xml]
2011-03-18 16:18:42,964 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Adding entity [com/food/Codes-orm.xml]
2011-03-18 16:18:42,964 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Added [5] mapping file to persistence unit[commonDataServiceTestPu]
2011-03-18 16:18:42,964 [main] DEBUG com.foo.MultiConfigAwarePersistenceUnitManager - Persistence unit[commonDataServiceTestPu] has now the following jar file urls []

One benefit of utilizing this concept, is that I can rename the persistence.xml files to something unique and change their location in the classpath.  Why is this important?  As one of the articles states above, WebLogic Server uses Kodo as it’s default JPA provider which does not support schemas of version=”2.0″, and will cause errors if it finds orm.xml files or persistence.xml files listed with the version=”2.0″.  Kodo/WebLogic Server look for files called persistence.xml that are in the root of the META-INF directory.  So I have renamed the files and put them into a different directory.  This alleviates those issues and helps in our usage of Hibernate with more advanced JPA 2.0 features than WebLogic Server supports.

http://download.oracle.com/docs/cd/E12840_01/wls/docs103/ejb30/using_kodo.html

http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/weblogic

http://forums.oracle.com/forums/thread.jspa?threadID=2150287&amp;tstart=45

If you are interested in learning more about JPA, wikibooks has a really good online book for this information.

RESOURCES

http://en.wikibooks.org/wiki/Java_Persistence/Runtime

buy modafinil

Share and Enjoy