Home > Software design >  org.hibernate.WrongClassException when loading entity via hibernate with abstract class inheritance
org.hibernate.WrongClassException when loading entity via hibernate with abstract class inheritance

Time:03-16

Question :

Do I need to remove generic on my interface IAbstractUserService ?

Is it related to this answer with type erasure ? https://stackoverflow.com/a/31266152/6698175

Problem :

I'm getting this exception :

org.hibernate.WrongClassException: Object [id=1] was not of the specified subclass [com.faz.idb.models.Adviser] : loaded object was of wrong class class com.faz.idb.models.Customer

when trying to load entity with :

T getUserByEmail(String email);

like so :

AbstractUserServiceImpl<? extends AbstractUser> userService;
AbstractUser user = userService.getUserByEmail(email);

Currently I have :

hibernate v7.0.3.Final

An abstract parent class : AbtrsactUser

Child classes: Customer and Adviser ;

@Getter
@Setter
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "user_type", discriminatorType = DiscriminatorType.STRING)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(name = "customer", value = Customer.class),
        @JsonSubTypes.Type(name = "adviser", value = Adviser.class)
})
@DiscriminatorOptions(force = true)
public abstract class AbstractUser { ........ }


@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@DiscriminatorValue("adviser")
@DiscriminatorOptions(force = true)
public class Adviser extends AbstractUser { ........ }


@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@DiscriminatorValue("customer")
@DiscriminatorOptions(force = true)
public class Customer extends AbstractUser { ........ }

Service :

public interface IAbstractUserService<T extends AbstractUser> {
        T getUserByEmail(String email);

Implementation :

@Service public class AbstractUserServiceImpl<T extends AbstractUser> implements IAbstractUserService<T> {

    @Autowired
    private AbstractUserRepository<T> userRepository;

    @Override
    public T getUserByEmail(String email) {
        return userRepository.findByEmail(email).orElse(null);
    }

Repository :

@Repository public interface AbstractUserRepository extends JpaRepository<T, Long> { Optional findByEmail(String email); }

Generated SQL :

Hibernate: select abstractus0_.id as id2_0_, abstractus0_.email as email3_0_, abstractus0_.password as password4_0_, abstractus0_.user_type as user_typ1_0_ from abstract_user abstractus0_ where abstractus0_.user_type in ('adviser', 'customer') and abstractus0_.email=?

Hibernate: select person0_.customer_id as customer1_5_0_, person0_.first_name as first_na2_5_0_, person0_.gender as gender3_5_0_, person0_.last_name as last_nam4_5_0_, adviser1_.id as id2_0_1_, adviser1_.email as email3_0_1_, adviser1_.password as password4_0_1_ from person person0_ left outer join abstract_user adviser1_ on person0_.customer_id=adviser1_.id where person0_.customer_id=? 2022-03-14 06:14:34.029

INFO 79095 --- [nio-8080-exec-2] o.h.e.internal.DefaultLoadEventListener : HHH000327: Error performing load command

In my database I have only one user :

user_type | id | email | password

customer | 1 | [email protected] | ....

CodePudding user response:

Problem solved.

I was using @MapsId without @JoinColumn(name="id") in the child entity of @OneToOne relation.

Explanation:

"AbstracUser" is extended by a "Customer" entity and he has @OneToOne relation with "PersonDetails".

So in Customer I had :

@OneToOne(mappedBy = "Customer", fetch = FetchType.LAZY, 
cascade {CascadeType.ALL}, optional = false)  
@PrimaryKeyJoinColumn  private PersonDetails person;

And PersonDetails :

@MapsId()
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name="id")
private Customer customer;

The solution was to add the referenced column in

@MapsId("id")

or

@MapsId
@JoinColumn(name="id")

*N.B

  • Also make sure you syncronize both sides of a relation by "setting" their corresponding object. (ressource I used)
  • For a better architecture, I Finally ended up putting the @OneToOne relation inside the "AbstractUser" entity (not in Customer)".*
  • Related