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.