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