Page 1 of 2 12 LastLast
Results 1 to 10 of 13

Thread: PersistenceUnit and multiple root URLs

  1. #1
    Join Date
    Aug 2004
    Posts
    230

    Default PersistenceUnit and multiple root URLs

    I'm trying to createa a PersistenceUnitInfo with multiple URLs for class discovery. But I'm not seeing a clear mechanism.

    I might like to extend PersistenceUnitReader to accept classpath*:, and scan for classes based on persistence.xml files found (this is how I used to do it before switching to spring jpa and inited hibernate ejb3 myself) .But LocalContainerEntityManagerFactoryBean hard codes "new PersistenceUnitReader()", so I can't provide another impl.

    Is there some mechanism that I am not seeing for creating a single EntityManagerFactory for all classes in the classpath/eclipse projects?

    thanks!
    Barry Kaplan (memelet)

  2. #2
    Join Date
    Jan 2005
    Location
    Bucharest, Romania
    Posts
    5,403

    Default

    I will modify the LCEMFB so that reader creation is separated and can be extended/replaced. Note that you should have one LCEMFB per persistence-unit - multiple units can have multiple provides which is unsupported.
    As for classes - they are taken from the classpath (the underlying classloader should know how to read them).
    Costin Leau
    SpringSource - http://www.SpringSource.com- Spring Training, Consulting, and Support - "From the Source"
    http://twitter.com/costinl
    Please use [ c o d e ] [ / c o d e ] tags

  3. #3
    Join Date
    Aug 2004
    Posts
    230

    Default

    Thanks Costin

    Let me outline what I'm trying to do to make sure we are talking about the same thing (and that I'm not trying to work outside tthe JPA spec):

    - I have mutliple projects/components/jars
    - These components are really part of a single, extensible system, so all Entity's
    should really go in the same DB/schema
    - Each component will provide Entity's, each of which will contain
    a meta-inf/persistence.xml -- but each will set the same name for the PU
    - I will set LCEMFB.allowRedeploymentWithSameName=true

    In the end I would like to be able to for any component to get an EntityManager via injection without having to specify a name and have all Entity's from all components available.

    This is pretty much how our system worked pre-JPA. We had a classpath scanner which would find all Entity's (and other EJB3 annotated classes) and would PostProcess the hibernate EntityEntityManagertFactoryBean (one of my own making) to poke into the hibernate AnnotationConfiguration all the classes, packages, etc prior to the creating the EMF. We also had a home-grown aspect that would inject the EM's created from the above EMF into '@PersistenceUnit EntityManager em' properties in instances with @Transactional methods.

    Now, with spring 2.0, I am hoping to delete tons of code. But I need some way for jars, which are components, to add Entity's to the system and the systems PU.

    Sorry for the long long verbage, but better you see what I am trying to do before make changes for something should not be trying do.

    thanks!!!
    Barry Kaplan (memelet)

  4. #4
    Join Date
    Jan 2005
    Location
    Bucharest, Romania
    Posts
    5,403

    Default

    I see. I'll review the specs and speak with some people that have more insight so I can see what is legal or not. AFAIK, appending configuration to a persistenceUnit is not allowed but multiple persistence.xml files are. However, for your case you can define only a persistence.xml and then let the scanning discover the classes transparently.
    I'll let you know once I have more info what are the 'legal' possibilities in this scenario - we certainly want to support as many scenarios as possible with the JPA support.
    Costin Leau
    SpringSource - http://www.SpringSource.com- Spring Training, Consulting, and Support - "From the Source"
    http://twitter.com/costinl
    Please use [ c o d e ] [ / c o d e ] tags

  5. #5
    Join Date
    Aug 2004
    Posts
    230

    Default experiment

    I did a quick spike just to see if I will be able to achive a unified and extensible persistence unit. Here is what I did:

    1. I modified LocalContainerEntityManagerBeanFactory to provide a hook method for creating the PersistenceUnitReader

    Code:
        private SpringPersistenceUnitInfo parsePersistenceUnitInfo() {
            PersistenceUnitReader reader = createPersistenceUnitReader(this.resourceLoader, this.dataSourceLookup);
            ...
        }
    
        protected PersistenceUnitReader createPersistenceUnitReader(ResourceLoader resourceLoader, DataSourceLookup dataSourceLookup) { 
            return new PersistenceUnitReader(resourceLoader, dataSourceLookup);
        }
    I had to change both classes PersistenceUnitReader and SpringPersistenceUnitInfo from package to public scope.

    2. Then I created MultiFilesystemClasspathLocalContainerEntityManage rFactoryBean

    Code:
    public class MultiFilesystemClasspathLocalContainerEntityManagerFactoryBean extends LocalContainerEntityManagerFactoryBean {
    
        private String classpathMarkerFile;
        
        public void setClasspathMarkerFile(String rootPersistenceXmlLocation) {
            this.classpathMarkerFile = rootPersistenceXmlLocation;
        }
        
        @Override
        protected PersistenceUnitReader createPersistenceUnitReader(ResourceLoader resourceLoader, DataSourceLookup dataSourceLookup) { 
            return new MultiFilesystemClasspathHibernatePersistenceUnitReader(classpathMarkerFile, resourceLoader, dataSourceLookup);
        }
    }
    3. MultiFilesystemClasspathHibernatePersistenceUnitRe ader does the actual work:

    Code:
    public class MultiFilesystemClasspathHibernatePersistenceUnitReader extends PersistenceUnitReader {
    
        private final String classpathMarkerFile;
        private final PathMatchingResourcePatternResolver resourcePatternResolver;
    
        public MultiFilesystemClasspathHibernatePersistenceUnitReader(String classpathMarkerFile, ResourceLoader resourceLoader, DataSourceLookup dataSourceLookup) {
            super(resourceLoader, dataSourceLookup);
            this.classpathMarkerFile = classpathMarkerFile;
            this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
        }
        
        @Override
        public SpringPersistenceUnitInfo[] readPersistenceUnitInfos(String persistenceXmlLocation) {
            List<URL> classpathRoots = new ArrayList<URL>();
            Resource[] resources = null;
            try {
                resources = resourcePatternResolver.getResources("classpath*:"+classpathMarkerFile);
                for (Resource resource : resources) {
                    if (resource.getURL().getProtocol().equals("file")) {
                        URL classpathRoot = new URL("file:"+determineClasspathRootDir(resource, classpathMarkerFile));
                        classpathRoots.add(classpathRoot);
                    }
                }
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Cannot resolve classpaths roots, marker=" + classpathMarkerFile, ex);
            }
            
            SpringPersistenceUnitInfo[] infos = super.readPersistenceUnitInfos(persistenceXmlLocation);
            if (infos.length > 1) {
                throw new IllegalArgumentException("Found more than one persistence unit: " +
                        "persistenceXmlLocation=" + persistenceXmlLocation +
                        "units=" + infos);
            } else if (infos.length == 0) {
                throw new IllegalArgumentException("Could find the persistence unit: " +
                        "persistenceXmlLocation=" + persistenceXmlLocation);
            }
            for (URL classpathRoot : classpathRoots) {
                infos[0].addJarFileUrl(classpathRoot);
            }
            return infos;
        }
    
        protected String determineClasspathRootDir(Resource resource, String classpathMarkerFile) throws IOException {
            String location = resource.getFile().getAbsolutePath();
            return location.substring(0, location.length()-classpathMarkerFile.length());
        }    
    }
    The result is a PersistenceUnit returned to LCEMFB which has all the classpath roots added to the PU as jars. This works with hibernate because they have generic class scanner. It may not work with other JPA providers.

    I made changes to the persistenceUnitRootUrl since there is no way to support multiple roots.

    4. Here is the "system/unified" EMFB bean definition:

    Code:
        <bean id="persistence.entityManagerFactory" class="org.opentrader.infra.spring.MultiFilesystemClasspathLocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="persistence.dataSource"/>
            <property name="classpathMarkerFile" value="META-INF/component.ctx.xml"/>
            <property name="persistenceXmlLocation" value="classpath:META-INF/persistence-unit.xml"/>
            <property name="jpaVendorAdapter">
                <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                ...
            </property>
            ...
        </bean>
    Any classpath that contains a the META-INF/component.ctx.xml will be added to the PU. But any file could have been used as the marker. I didn't use persistence.xml since I don't want more than one PU/EMF created. But clearly multiple LCEMFB instances could be defined with different marker files (or some other classpath selection strategy) and hence multiple EMF is possible.

    5. The unfied persistence-unit.xml is just to satisfy the spec, as I want to define everything possible in the spring context file:

    Code:
    <persistence ...>
    	<persistence-unit name="OpenTrader" transaction-type="JTA"/>
    </persistence>
    ----

    This seems to work pretty good. Is the change I made to LCEMFB what you had in mind Costin? Or were you going to provide an interface for a PersistenceUnitReaderCreator or some such? It would be nice not to have to subclass LCEMFB as well as the PUR.

    Do you see anything illegal about what I am doing? I could make the PersistenceUnitReader subclass more generic so it uses a strategy to determine the classpath roots to add as the jar URLs.

    In the case where where a provider does not accept file: URLs for the jar: URLs like hibernate does, the PersistenceUnitReader would probably need to scan the file system itself and set the classes using PU.addManagedClassName(...).

    Any feedback would be much appreciated. Oh, I'm really digging the spring JPA support. Its much cleaner than what I was doing to bootstrap hibernate and get it install all the EJB3 listeners. I also killed a whole test setup framework in place of AbstractJpaTests, which is working great.

    cheers
    Barry Kaplan (memelet)

  6. #6
    Join Date
    Jan 2005
    Location
    Bucharest, Romania
    Posts
    5,403

    Default

    This seems to work pretty good. Is the change I made to LCEMFB what you had in mind Costin? Or were you going to provide an interface for a PersistenceUnitReaderCreator or some such? It would be nice not to have to subclass LCEMFB as well as the PUR.

    Do you see anything illegal about what I am doing? I could make the PersistenceUnitReader subclass more generic so it uses a strategy to determine the classpath roots to add as the jar URLs.

    In the case where where a provider does not accept file: URLs for the jar: URLs like hibernate does, the PersistenceUnitReader would probably need to scan the file system itself and set the classes using PU.addManagedClassName(...).
    Making the methods protected for extension was one ideas as, as you pointed out, people might want to extend it to add custom support.

    The illegal thing would be that you are not defining everything inside your persistence unit - all the classes and configuration have to be defined only in one xml file. If the solution works fine for you it's okay, but one you move to a different provider w/o Spring you'll get errors.

    We're trying to be as transparent as possible and not do the parsing of the files ourselves as each provides has its own strategy. Note that class parsing is actually the provider job and not Spring's.

    I'm currently working on JPA and I'll try to see what solutions are there for supporting multiple persistence.xml. I have some ideas of my own but as you pointed out, I have to make sure I'm not doing anything 'illegal'. I'll let you know once we have something in place.

    Thanks a lot for the (positive) feedback!
    Costin Leau
    SpringSource - http://www.SpringSource.com- Spring Training, Consulting, and Support - "From the Source"
    http://twitter.com/costinl
    Please use [ c o d e ] [ / c o d e ] tags

  7. #7
    Join Date
    Aug 2004
    Posts
    230

    Default

    Colin, I see you have checked in changes to LCEMFB making parsePersistenceUnitInfo protected. But this still requires that I reimplement the entire method (ie, copy the contents of the base class impl), when all I really need is to provide my own PersistenceUnitReader. Providing a hook factory method like in my spike above would allow for reusing all of the other logic in parsePersistenceUnitInfo.
    Barry Kaplan (memelet)

  8. #8
    Join Date
    Jan 2005
    Location
    Bucharest, Romania
    Posts
    5,403

    Default

    I've committed some code on the CVS today which address reading of multiple persistence.xml but not yet the reader hook. I'll discuss this with Juergen and get back to you once we have decided on something.

    P.S. the name is Costin not Colin.
    Costin Leau
    SpringSource - http://www.SpringSource.com- Spring Training, Consulting, and Support - "From the Source"
    http://twitter.com/costinl
    Please use [ c o d e ] [ / c o d e ] tags

  9. #9
    Join Date
    Aug 2004
    Posts
    230

    Default

    Whoops, sorry Costin.

    One more thing I ran into: As I described, I am setting the jars to the classpath roots (which of course may only work for hibernate, but in production each of the eclipse projects will by then be a jar). However, LCEMFB.createNativeEntityManagerFactory is setting the PersistenceUnitRootUrl on the unit, with a default of "classpath:". This results, with my custom unit reader, in the same classpath root being included in the jars and set as the root url property of the unit. This causes hibernate to complain about duplicates of various things.

    I could excluded the PersistenceUnitRootUrl from the set of jars, but that property is private in LCEMFB. Would it be possible to add a public or protected getter for that property?

    tx!
    Barry Kaplan (memelet)

  10. #10
    Join Date
    Aug 2004
    Posts
    230

    Default

    Actually, it would be even better if LCEMFB.findPersistenceUnitRootUrl could be public/protected, otherwise I would have duplicate that code.
    Barry Kaplan (memelet)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •