Mohamad ABBAS opened SPR-16233 and commented

Hello,

I've created a sample project with two modules:

  • The first module example-project is a spring project that contains a JPA entity + a service class and a DAO class for this entity. (I configure this entity inside an orm.xml file + the persistence unit inside a persist.xml file).
  • The second module example-project-boot is a simple spring-boot project that calls the service class of the first module.

Please find below the codebase of this project (you can also find it here).

When I run this project with IntelliJ or Eclipse, everything works as expected.

When I package this same project as a fat jar, then I try to run it, I am getting an

IllegalArgumentException: Unable to locate persister.

I noticed that, on startup, a call is made to method

org.springframework.orm.jpa.persistenceunit.PersistenceUnitReader#determinePersistenceUnitRootUrl

with /Users/mabbas/example-project-parent/example-project-boot/target/example-project-boot-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/example-project.jar!/META-INF/persist.xml

and this method is returning /Users/mabbas/example-project-parent/example-project-boot/target/example-project-boot-0.0.1-SNAPSHOT.jar

instead of

/Users/mabbas/example-project-parent/example-project-boot/target/example-project-boot-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/example-project.jar (the root of persist.xml is example-project and not example-project-boot).

Because of this result, I think that the entity defined inside example-project.jar is never loaded and then the exception is thrown.

I have first created a spring-boot issue here, but it seems that this problem is more about spring-orm than spring-boot.

Thanks in advance,


Affects: 4.3.12

Reference URL: https://github.com/spring-projects/spring-boot/issues/11144

Attachments: - example-project-parent.zip (350.82 kB)

1 votes, 4 watchers

Comment From: spring-projects-issues

Stéphane Nicoll commented

Thanks for the sample.

That setup is super unusual, have you considered migrating it (i.e. let Spring Boot creates the datasource rather than doing everything yourself? or configure things at least in one place?). Looking at the code you are creating the entity manager in one module and the persistence unit in another. Yet the persistence unit is linked so you have an inverse dependency from a configuration standpoint.

The URL that is computed is actually correct (remember, it is a default URL that we compute as a fallback because JPA requires one). I've tried to force the use of orm.xml by adding the following to your java configuration but didn't manage to start your application.

persistenceUnitManager.setMappingResources("classpath:/META-INF/orm.xml");

Comment From: spring-projects-issues

Mohamad ABBAS commented

Thank you, Stéphane for your comment.

I completely agree with you; it is super unusual. But unfortunately, in my real project, I have a dependency on three legacy JARs, each of them declares a persistent unit, and those JARs cannot be modified.

I made a test by intercepting the call, in my IDE, to method

org.springframework.orm.jpa.persistenceunit.PersistenceUnitReader#determinePersistenceUnitRootUrl

and when i replace the result of this method with

/Users/mabbas/m2/repository/com/example/example-project/0.0.1-SNAPSHOT/example-project-0.0.1-SNAPSHOT.jar

instead of

/Users/mabbas/example-project-parent/example-project-boot/target/example-project-boot-0.0.1-SNAPSHOT.jar

it resolves the problem. This is why I was thinking about a bug in determinePersistenceUnitRootUrl.

As a workaround to this problem, and instead of building a fat jar, i can extract all the dependencies of my project to a 'lib' directory and point the classpath to this directory when running the application. It resolves my problem but of course, it is not the perfect solution.

Comment From: spring-projects-issues

Stéphane Nicoll commented

Understood. Just to confirm, the behaviour of determinePersistenceUnitRootUrl is correct but won't work in a Spring Boot app. I am actually surprised that setting the path to mapping resources doesn't work either. Perhaps Juergen Hoeller can chime in here?

Comment From: spring-projects-issues

Thomas Heigl commented

I ran into this issue with my app as well. I solved it by deleting all my persistence.xml files and using Spring support for entity scanning like this:

final LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource());
emf.setPackagesToScan("com.my.module1", "com.my.module2"); 

My orm.xml files are still picked up and everything works as expected.

Comment From: ajacob

I have a very similar setup as Mohamad, in my case using EclipseLink.

The persistence.xml is in an inner jar of the spring-boot fat jar, configured with exclude-unlisted-classes to false.

When spring configures the PersistenceUnitInfo it uses PersistenceUnitReader.determinePersistenceUnitRootUrl which gives as a root URL the fat jar (and not the inner jar containing the persistence.xm).

When EclipseLink tries to load it's entities through classpath scanning, it's not going to get down to inner jars but will only consider classes from the fat jar.

In turn, entities present in the inner jar that holds the persistence.xml file are not seen.

I tested to change the root url returned by PersistenceUnitReader.determinePersistenceUnitRootUrl to point to the inner jar and it fixes the issue.

@snicoll if you confirm that it is the correct behavior, may be I should open an issue in EclipseLink project ?

It's quite misleading because it works well inside IDE's but fails when running as a spring boot fat jar.

Comment From: snicoll

Would someone please able to provide a sample with a supported version of the framework? We've been improving quite a lot in this area (both here and in spring boot) so I am wondering if that is still relevant.

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: spring-projects-issues

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.

Comment From: s-b-u

I would like to ask to re-open the issue since I still see it (spring-boot-3.2). To give at bit more context I would like to ask you to have a look at following scenario. One or more persistence-units (pu.jar) are deployed alongside a main-application (app) leading to boot jar (app.jar) with the layout

jar:file:/app.jar!/BOOT-INF/lib/pu.jar!/META-INF/persistence.xml

. The persistence-unit is picked up by DefaultPersistenceUnitManager#DEFAULT_PERSISTENCE_XML_LOCATION as expected during DefaultPersistenceUnitManager#readPersistenceUnitInfos, but the PersistenceUnitReader#determinePersistenceUnitRootUrl resolves the root-url for the persistence-unit to jar:file:/app.jar instead of jar:file:/app.jar!/BOOT-INF/lib/pu.jar. This leads hibernate to scan the app.jar instead of the pu.jar.

Applying a custom PersistenceUnitPostProcessor: pui -> pui.setPersistenceUnitRootUrl(new URL("jar:file:/app.jar!/BOOT-INF/lib/pu.jar")) works around the issue and behaves the same way as the application is started from an unpacked location. Therefore I would assume that this issue is the real root-cause and the scenario is well supported.

Thanks in advance for giving some clarification on that.