Reputation: 333
I'm trying to implement inheritance with Kotlin and JPA. My abstract base class (annotated with @Entity
) holds the ID (annotated with @Id
and @GeneratedValue
) and other metadata, like createDate, etc. I'm getting several errors from Hibernate, one for each field except the ID:
org.hibernate.tuple.entity.PojoEntityTuplizer - HHH000112: Getters of lazy classes cannot be final: com.example.BaseEntity.createDate
As I've read I need to include the open keyword for each property.
I have 3 questions regarding this:
Edit:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
abstract class BaseEntity(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0,
val createdAt: Instant = Instant.now()
)
@Entity
class SubClass(
val someProperty: String = ""
) : BaseEntity()
I'm using the JPA plugin for Gradle, which I believe creates the noarg constructor, that's why I don't have to specify everything nullable.
Thank you!
Upvotes: 2
Views: 3834
Reputation: 8297
The logged error has to do with lazy loading.
Hibernate extends
entities at runtime to enable it. It is done by intercepting an access to properties when an entity is loaded lazily.
Kotlin has flipped the rules and all classes are final
by default there. It is the reason why we're advised to add an open
keyword.
If a property is not open
hibernate cannot intercept access to it because final
methods cannot be overridden. Hence the error.
Why isn't it complaining about the ID?
Because @Id
is always loaded. There is no need to intercept access to it.
It seems to work without the
open
keyword, then why is the error logged?
The key word here is seems. It may introduce subtle bugs.
Consider the following @Entity
:
@Entity
public class Book {
@Id
private Long id;
private String title;
public final Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public final String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
And the @Test
:
@Test
public void test() {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
// signal here
Book book = new Book();
book.setId(1L);
book.setTitle("myTitle");
entityManager.persist(book);
// noise
entityManager.getTransaction().commit();
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
// signal
Book reference = entityManager.getReference(Book.class, 1L);
String title = reference.getTitle();
assertNull(title); // passes
entityManager.getTransaction().commit();
entityManager.close();
}
This test passes but it should not (and fails if getTitle
is not final
).
This would be hard to notice
Why do I have to do that in the superclass, and don't need in subclass? I'm not overriding those properties.
Looks like Hibernate gives up when it sees final
@Entity
.
Add open
to SubClass
and you will the precious:
2019-05-02 23:27:27.500 ERROR 5609 --- [ main] o.h.tuple.entity.PojoEntityTuplizer : HHH000112: Getters of lazy classes cannot be final: com.caco3.hibernateanswer.SubClass.someProperty
See also:
PS
Did you forget to include @MappedSuperclass
on BaseEntity
?
Without the annotation it should fail with something like:
org.hibernate.AnnotationException: No identifier specified for entity: com.caco3.hibernateanswer.SubClass
Upvotes: 3