2011-03-08 9 views
10

Tengo un problema cuando una entidad se asigna a otra entidad que tiene una implementación directa en sus subclases. Ver mapeo muestra a continuación:JPA @OneToOne throws Error cuando se asigna a un @Entity abstracto con subclases

@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 { 
    ... 
} 

Ahora bien, esto es lo que tengo en mi mesa de la base de datos:

lugar de la tabla: id = 1, person_id = 1 persona tabla: id = 1, person_type = "M"

Cuando recupero la ubicación usando el administrador de entidades, hibernate arroja una excepción diciendo que no puedo instanciar una clase abstracta o una interfaz.

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

Hibernate tiros este 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) 
+1

¿Cuál es el tipo de datos de su columna de discriminador en la base de datos? –

+1

¿Puedes mostrar la stacktrace completa? – axtavt

+2

¿Has probado @MappedSuperclass en lugar de @Entity on Person? –

Respuesta

0

Como se indica en mi comentario anterior, he intentado lo mismo con EclipseLink y funciona.

Después de crear algunos datos de prueba, borré el valor del discriminador de una entrada de persona en el DB y ahora recibo una excepción similar cuando intento cargar la ubicación asociada. El mensaje de EclipseLink es un poco más descriptivo:

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) 
... 

El mapeo parece que funciona, así que a menos que los datos son "corruptos" (que no lo es en su caso, como usted ha dicho), es probable que sea un error o por lo comportamiento menos diferente en Hibernate.

2

De The Java EE 6 Tutorial - Entity Inheritence:

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

Por lo que parece ser cierto que hay que anotar la clase Person con @Entity a asociarlo con un Location través @OneToOne.

Desde el @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.

lo que no podía usar @MappedSuperclass en persona, a continuación, asignar con el @OneToOne, ya que no habría ninguna tabla Persona.

Parece que las anotaciones JPA que está utilizando son correctas. ¿Has probado la sugerencia de @Martin Klinke de un valor de discriminador falso para la clase Person?

+0

Esto resolvió mi problema, olvidé @Entity en la subclase y recibí el mismo error hasta que lo agregué a la clase secundaria. –

1

Encontré que este tipo de problema se resuelve solo si las clases Entity implementan Serializable.

0

Ejecuto un código similar a esto, con un par de diferencias. En primer lugar, coloco la clase Abstract detrás de una interfaz. En segundo lugar, defino explícitamente targetEntity para el mapeo @OnetoOne. Un ejemplo sería:

@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 { 
} 

Aquí he cambiado el nombre de su clase abstracta persona a 'AbstractPerson' para mayor claridad. Jugué bastante jugando para que funcione, espero que resuelva tu problema.

0

Intente agregar @ForceDiscriminator a Person.Sin esta anotación, Hibernate a menudo intenta crear una instancia de la clase padre, haciendo caso omiso del discriminador que debería decirle qué clase secundaria crear.

1

que tenía un mensaje de error similar con la siguiente estructura:

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

    @Id 
    @GeneratedValue 
    private Long  id; 

} 

con una instancia secundaria concreta

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

y una clase que tiene un campo de referencia de la clase abstracta:

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

Los siguientes cambios me solucionaron el problema:

  1. Cambiar el @OneToOne a @OneToOne(cascade = CascadeType.ALL)
  2. Cambiar el @GeneratedValue a @GeneratedValue(strategy = GenerationType.TABLE)

ACTUALIZACIÓN: También me di cuenta de que no estaba guardando el objeto RealPerson antes de su vinculación con SomeClass. Así que ahora que primero guardo la instancia primero, el atributo cascade ya no es necesario

4

Esto me sirvió de algo, usando hibernate como proveedor de persistencia.

@OneToOne(cascade = {CascadeType.PERSIST,CascadeType.MERGE}) 
Cuestiones relacionadas