11

I have a problem when an Entity is mapped to another Entity which has a direct implementation on its subclasses. See sample mapping below:

@Entity
class Location {
      @OneToOne
      @JoinColumn(...)
      private Person person;
}

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="person_type",discriminatorType=DiscriminatorType.STRING)
abstract class Person {
}

@Entity
@DiscriminatorValue("M")
class Man extends Person {
    ...
}

@Entity
@DiscriminatorValue("W")
class Woman extends Person {
    ...
}

Now, this is what I have on my database table:

location-table: id=1, person_id=1 person-table: id=1,person_type="M"

When I retrieve the Location using entity manager, hibernate throws an exception saying that i cannot instantiate an abstract class or an interface.

Location location = entityManager.find(Location.class, 1L);

Hibernate throws this error:

javax.persistence.PersistenceException: org.hibernate.InstantiationException: Cannot instantiate abstract class or interface: Person
at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:630)
at org.hibernate.ejb.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:195)
at ......
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:59)
at org.junit.internal.runners.MethodRoadie.runTestMethod(MethodRoadie.java:98)
at org.unitils.UnitilsJUnit4TestClassRunner$TestListenerInvokingMethodRoadie.runTestMethod(UnitilsJUnit4TestClassRunner.java:174)
at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:79)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:87)
at org.unitils.UnitilsJUnit4TestClassRunner$TestListenerInvokingMethodRoadie.runBeforesThenTestThenAfters(UnitilsJUnit4TestClassRunner.java:156)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:77)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:42)
at org.unitils.UnitilsJUnit4TestClassRunner.invokeTestMethod(UnitilsJUnit4TestClassRunner.java:95)
at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
at org.unitils.UnitilsJUnit4TestClassRunner.access$000(UnitilsJUnit4TestClassRunner.java:44)
at org.unitils.UnitilsJUnit4TestClassRunner$1.run(UnitilsJUnit4TestClassRunner.java:62)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
at org.unitils.UnitilsJUnit4TestClassRunner.run(UnitilsJUnit4TestClassRunner.java:68)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
sevenFLiP
  • 188
  • 2
  • 10
  • 1
    What is the datatype of your discriminator column in the db? – Tom Tresansky Mar 08 '11 at 19:15
  • 1
    Can you show the full stacktrace? – axtavt Mar 08 '11 at 19:16
  • 2
    Have you tried @MappedSuperclass instead of @Entity on Person? – Martin Klinke Mar 08 '11 at 19:18
  • [This forum post](https://forum.hibernate.org/viewtopic.php?f=1&t=974073) may help as well although it's from 2007. Essentially, it suggests to add a dummy discriminator value to the abstract base class. – Martin Klinke Mar 08 '11 at 19:38
  • @Martin - I want to store those entities on the same table and I guess @MappedSuperclass cannot be mapped on an entity relationship e.g. @OneToOne – sevenFLiP Mar 08 '11 at 19:50
  • I have tried your example with EclipseLink as is and I'm not getting any exception. Maybe it's a problem with how you have initialized the data? Is there any entry without a value in the discriminator column in the person table? – Martin Klinke Mar 08 '11 at 21:58

7 Answers7

4

This did the trick for me, using hibernate as persistence provider.

@OneToOne(cascade = {CascadeType.PERSIST,CascadeType.MERGE}) 
Jesper
  • 41
  • 2
2

From The Java EE 6 Tutorial - Entity Inheritence:

Any mapping or relationship annotations in non-entity superclasses are ignored.

So you seem to be correct that you have to annotate the Person class with @Entity to associate it with a Location via @OneToOne.

From the @MappedSuperclass javadoc

A class designated with the MappedSuperclass annotation can be mapped in the same way as an entity except that the mappings will apply only to its subclasses since no table exists for the mapped superclass itself.

So you couldn't use @MappedSuperclass on Person, then map it with the @OneToOne, since there would be no Person table.

Seems like the JPA annotations you are using are correct. Have you tried @Martin Klinke's suggestion of a dummy discriminator value for the Person class?

Tom Tresansky
  • 19,364
  • 17
  • 93
  • 129
  • This solved my problem, I forgot @Entity on the subclass and received the same error until I added it to the child class. – Ransom Briggs Jun 12 '12 at 22:22
1

I found this kind of problem solves itself if the Entity classes implement Serializable.

Michael Petrotta
  • 59,888
  • 27
  • 145
  • 179
Kai
  • 2,145
  • 1
  • 20
  • 35
1

I had a similar error message with the following structure:

@Entity
@Table(name = "PERSON")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.STRING)
public abstract class Person {

  @Id
  @GeneratedValue
  private Long     id;

}

with a concrete child instance

@Entity
@DiscriminatorValue("REALPERSON")
public class RealPerson extends Person{ etc... }

and a class that has a field referencing the abstract class:

public class SomeClass() {
  @OneToOne
  private Person person;
}

The following changes fixed the problem for me:

  1. Change the @OneToOne to @OneToOne(cascade = CascadeType.ALL)
  2. Change the @GeneratedValue to @GeneratedValue(strategy = GenerationType.TABLE)

UPDATE: I also found I was not saving the RealPerson object before linking it to SomeClass. So now that I first save the instance first, the cascade attribute is no longer required

dvtoever
  • 3,896
  • 1
  • 28
  • 29
0

As indicated in my comment above, I have tried the same with EclipseLink and it works.

After creating some test data, I've cleared the discriminator value of a person entry in the DB and now I get a similar exception when trying to load the associated location. EclipseLink's message is a little bit more descriptive:

Exception Description: Missing class for indicator field value [] of type [class java.lang.String].
Descriptor: RelationalDescriptor(com.mklinke.webtest.domain.Person --> [DatabaseTable(PERSON)])
at org.eclipse.persistence.exceptions.DescriptorException.missingClassForIndicatorFieldValue(DescriptorException.java:937)
at org.eclipse.persistence.descriptors.InheritancePolicy.classFromValue(InheritancePolicy.java:355)
at org.eclipse.persistence.descriptors.InheritancePolicy.classFromRow(InheritancePolicy.java:342)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:485)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:456)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.buildObject(ObjectLevelReadQuery.java:723)
at org.eclipse.persistence.queries.ReadObjectQuery.registerResultInUnitOfWork(ReadObjectQuery.java:766)
at org.eclipse.persistence.queries.ReadObjectQuery.executeObjectLevelReadQuery(ReadObjectQuery.java:451)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeDatabaseQuery(ObjectLevelReadQuery.java:1080)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:808)
...

The mapping seems to work, so unless the data is "corrupt" (which it isn't in your case as you said), it's probably a bug or at least different behavior in Hibernate.

Martin Klinke
  • 7,294
  • 5
  • 42
  • 64
0

I run similar code to this, with a couple of differences. Firstly, I place the Abstract class behind an interface. Secondly, I explicitly define the targetEntity for the @OnetoOne mapping. An example would be:

@Entity
class Location {
    @OneToOne(targetEntity = AbstractPerson.class)
    @JoinColumn(...)
    private Person person;
}

public interface Person {
}

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="person_type",discriminatorType=DiscriminatorType.STRING)
abstract class AbstractPerson implements Person {
}

Here I've renamed your Person abstract class to 'AbstractPerson' for clarity. Took quite a bit of playing around with to get it working, I hope it solves your issue.

Bryn Sadler
  • 43
  • 1
  • 7
0

Try adding @ForceDiscriminator to Person. Without this annotation Hibernate often tries to instantiate the parent class, ignoring the discriminator that should tell it which child class to instantiate.

rjsang
  • 1,757
  • 3
  • 16
  • 26