org.hibernate.WrongClassException on saving an entity via Hibernate

拟墨画扇 提交于 2019-12-31 03:33:06

问题


In this question I am working with Hibernate 4.3.4.Final and Spring ORM 4.1.2.RELEASE.

I have an User class, that holds a Set of CardInstances like this:

@Entity
@Table
public class User implements UserDetails {

    protected List<CardInstance> cards;

    @ManyToMany
    public List<CardInstance> getCards() {
        return cards;
    }

    // setter and other members/methods omitted 
}

@Table
@Entity
@Inheritance
@DiscriminatorColumn(name = "card_type", discriminatorType = DiscriminatorType.STRING)
public abstract class CardInstance<T extends Card> {

    private T card;

    @ManyToOne
    public T getCard() {
        return card;
    }
}

@Table
@Entity
@Inheritance
@DiscriminatorOptions(force = true)
@DiscriminatorColumn(name = "card_type", discriminatorType = DiscriminatorType.STRING)
public abstract class Card {
    // nothing interesting here
}

I have several types of cards, each extending the Card base class and the CardInstance base class respectivly like this:

@Entity
@DiscriminatorValue("unit")
public class UnitCardInstance extends CardInstance<UnitCard> {
    // all types of CardInstances extend only the CardInstance<T> class
}

@Entity
@DiscriminatorValue("leader")
public class LeaderCardInstance extends CardInstance<LeaderCard> {

}

@Entity
@DiscriminatorValue("unit")
public class UnitCard extends Card {
}

@Entity
@DiscriminatorValue("leader")
public class LeaderCard extends AbilityCard {
}

@Entity
@DiscriminatorValue("hero")
public class HeroCard extends UnitCard {
    // card classes (you could call them the definitions of cards) can
    // extend other types of cards, not only the base class
}

@Entity
@DiscriminatorValue("ability")
public class AbilityCard extends Card {
}

If I add a UnitCardInstance or a HeroCardInstance to the cards collection and save the entity everything works fine. But if I add a AbilityCardInstance to the collection and save the entity it fails with a org.hibernate.WrongClassException. I added the exact exception + message at the bottom of the post.

I read through some questions, and lazy loading seems to be a problem while working with collections of a base class, so here is how I load the User entity before adding the card and saving it:

User user = this.entityManager.createQuery("FROM User u " +
                "WHERE u.id = ?1", User.class)
                .setParameter(1, id)
                .getSingleResult();

        Hibernate.initialize(user.getCards());

        return user;

The database entries for "cards"


The database entries for "cardinstances"


org.hibernate.WrongClassException: Object [id=1] was not of the specified subclass [org.gwentonline.model.cards.UnitCard] : Discriminator: leader

Thanks in advance for any clues how to fix this problem. If you need additional information I will gladly update my question!


回答1:


According to the first paragraph of the JavaDocs for @ManyToOne:

It is not normally necessary to specify the target entity explicitly since it can usually be inferred from the type of the object being referenced.

However, in this case, @ManyToOne is on a field whose type is generic and generic type information gets erased at the type of compilation. Therefore, when deserializing, Hibernate does not know the exact type of the field.

The fix is to add targetEntity=Card.class to @ManyToOne. Since Card is abstract and has @Inheritance and @DiscriminatorColumn annotations, this forces Hibernate to resolve the actual field type by all possible means. It uses the discriminator value of the Card table to do this and generates the correct class instance. Plus, type safety is retained in the Java code.


So, in general, whenever there is the chance of a field's type not being known fully at runtime, use targetEntity with @ManyToOne and @OneToMany.




回答2:


I solved the problem.

The root cause lies in this design:

@Table
@Entity
@Inheritance
@DiscriminatorColumn(name = "card_type", discriminatorType = DiscriminatorType.STRING)
public class CardInstance<T extends Card> {  
    protected T card;
}

@Entity
@DiscriminatorValue("leader")
public class LeaderCardInstance extends CardInstance<LeaderCard> {
}

At runtime information about generic types of an class are not present in java. Refer to this question for further information: Java generics - type erasure - when and what happens

This means hibernate has no way of determining the actual type of the CardInstance class.


The solution to this is simply getting rid of the generic type and all extending (implementing) classes and just use one class like this:

@Table
@Entity
@Inheritance
@DiscriminatorColumn(name = "card_type", discriminatorType = DiscriminatorType.STRING)
public class CardInstance {
    Card card;
}

This is possible (and by the way the better design) because the member card carries all the information about the card type.


I hope this helps folk if they run into the same problem.



来源:https://stackoverflow.com/questions/31257027/org-hibernate-wrongclassexception-on-saving-an-entity-via-hibernate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!