Corda's JPA classes should not be final or have final methods

Description

The JEE7 specification stipulates the following for @Entity and @Embeddable objects (and presumably @MappedSuperclass ones too):

  1. The class must be annotated with the javax.persistence.Entity annotation.

  2. The class must have a public or protected, no-argument constructor. The class may have other constructors.

  3. The class must not be declared final. No methods or persistent instance variables must be declared final.

  4. If an entity instance is passed by value as a detached object, such as through a session bean's remote business interface, the class must implement the Serializable interface.

However, the net.corda.core.schemas.PersistentStateRef class and both entities in net.corda.finance.schemas are declared as final, as are almost all of their methods. Similarly, while net.corda.core.schemas.PersistentState, net.corda.core.schemas.CommonSchemaV1$LinearState and net.corda.core.schemas.CommonSchemaV1$FungibleState are not final, most of their methods are. This means that JPA cannot create proxies for any of these classes.

The solution is to apply the kotlin-allopen plugin to the core and finance modules.

To be fair, we should probably also ask whether these classes should implement java.io.Serializable as well.

Activity

Show:
Chris Rankin
March 27, 2018, 2:00 PM

JPA cannot create proxies for any of our entities because they are either final classes or have final methods. This means Hibernate must always be EAGER when fetching entities from the database, even when the user explicitly asks for a LAZY proxy. For example, consider the following code using EntityManager.getReference():

At the moment, this results in the following SQL being generated:

However, if we fix the entities to allow Hibernate to create proxies, we get this instead:

You will notice that in this case, the SELECT has been deferred until we try to read the owner, currency and pennies properties. And this is how we asked Hibernate to work, because this is the point of getReference() vs find().

Chris Rankin
March 27, 2018, 2:03 PM

Yes, I also saw the kotlin-jpa plugin. But that plugin is just a wrapper around the kotlin-noarg plugin with configuration for JPA entities. It does not perform every JPA-related tweak (unfortunately).

Jose Coll
March 27, 2018, 2:33 PM

Thanks for the clear explanation .
This is a good performance optimisation which we should ask team to attempt to benchmark (Eg. to quantify the before / after effect of the optimisation).
Are there any specific paths (calling sequences) within our code that led you to report this issue?

Chris Rankin
March 27, 2018, 3:28 PM

No, my "performance cluster" at home didn't spot any issues. I simply observed that proxy-generation could not possibly work with the current code, proved it to myself and then raised an issue.

However, you will notice that all occurrences of FetchType.LAZY are meaningless without working proxies. And also that FetchType.LAZY is the default mode for OneToMany and ManyToMany relationships, c.f. NodeInfoSchemaV1.PersistentNodeInfo.

Katelyn Baker
March 29, 2018, 11:44 AM

Merged onto the V3 branch so setting fixed version to reflect

Assignee

Michele Sollecito

Reporter

Chris Rankin

Labels

None

Sprint

None

Epic Link

None

Priority

Medium

Severity

None

CVSS Score

None

CVSS Vector

None

Due Date

None

Engineering Teams

None

Fix versions

Affects versions

None

Ported to...

None

Story Points / Dev Days

None
Configure