WrongClassException: Object [id=null] was not of the specified subclass - java

I have a model like this one below:
#Entity(name = "request")
public class VisitRequest {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "visitRequest", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
private List<Visitor> visitors;
//default constructor, getters and setters
}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "class")
public class Visitor {
#Id
#GeneratedValue
private Long id;
#ManyToOne
#JsonBackReference
private VisitRequest visitRequest;
//default constructor, getters and setters
}
#Entity
public class ContactPerson extends Visitor {
private PhoneNumber phoneNumber;
//default constructor, getters and setters
}
But when I try to update a visitRequest by exchanging one of the visitors with a contact person, and try to execute the method on a CRUD repository visitRequestRepository.save(visitRequest); I'm getting this exception:
Servlet.service() for servlet [dispatcherServlet] in context with path
[] threw exception [Request processing failed; nested exception is
org.springframework.orm.ObjectRetrievalFailureException: Object
[id=null] was not of the specified subclass
[cern.ais.visits.core.domain.visitor.Visitor] : class of the given
object did not match class of persistent copy; nested exception is
org.hibernate.WrongClassException: Object [id=null] was not of the
specified subclass [cern.ais.visits.core.domain.visitor.Visitor] :
class of the given object did not match class of persistent copy] with
root cause
Maybe the problem is that in the database there is the same id used in the contact_person and visitor tables?
How can I solve the problem? I've searched for the solutions but none worked for me.

You're probably not initializing visitRequest reference in ContactPerson. Please take a look at the following configuration, it works with Spring Boot JPA. I have used lombok to generate Getter and Setters.
Here's a working example implemented in Spring Boot https://github.com/ConsciousObserver/SpringBootJpaInheritance
#Data
#Entity(name = "request")
class VisitRequest {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "visitRequest", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
private List<Visitor> visitors = new ArrayList<>();
}
#NoArgsConstructor
#Data
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "class")
class Visitor {
#Id
#GeneratedValue
private Long id;
#ManyToOne
#JsonBackReference
private VisitRequest visitRequest;
public Visitor(VisitRequest visitRequest) {
this.visitRequest = visitRequest;
}
}
#NoArgsConstructor
#Data
#ToString(callSuper = true)
#EqualsAndHashCode(callSuper = true)
#Entity
class ContactPerson extends Visitor {
private String phoneNumber;
public ContactPerson(VisitRequest visitRequest, String phoneNumber) {
super(visitRequest);
this.phoneNumber = phoneNumber;
}
}

I suspect the reason you are having this issue is your annotations #Id and #GeneratedValue are not inherited. If you define property id in ContactPerson you will have a generated Id and will not be a problem anymore.
Try changing ContactPerson class to:
#Entity
public class ContactPerson extends Visitor {
#Id
#GeneratedValue
private Long id;
private PhoneNumber phoneNumber;
//default constructor, getters and setters
}

#Entity(name = "request")
public class VisitRequest {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "visitRequest", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
private List<Visitor> visitors;
//default constructor, getters and setters
}
#Entity(name = "visitor")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "visitor_type", discriminatorType = DiscriminatorType.STRING, length = 16)
#DiscriminatorValue(value = "visitor")
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "class")
public class Visitor {
#Id
#GeneratedValue
private Long id;
#ManyToOne
#JsonBackReference
private VisitRequest visitRequest;
//default constructor, getters and setters
}
#Entity(name = "contact_person")
#Table(name = "contact_person")
#DiscriminatorValue(value = "contact_person")
public class ContactPerson extends Visitor {
private PhoneNumber phoneNumber;
//default constructor, getters and setters
}
And you able to persist ContactPerson like contactPersonDao.save(contactPerson),
you can`t persist field with visitor link.
I am not expert but it work for me.
And you can persist visitor as visitor if he not contactPerson

It seems the id's between different tables has same value, so when hibernate is trying to load an entity with a specific id and if another entity with same id is already present in memory then hibernate is complaining about this issue.

Related

Field 'id' doesn't have a default value because of nested object creation

I am getting field 'id' doesn't have a default value error in my Spring application.
I am trying to create an Applicant with #Post method but as I am creating the Applicant, new creditRating object needs to be created.
Here is the method
public Applicant create(ApplicantDTO applicantDTO) {
Applicant applicant = ApplicantMapper.toEntity(applicantDTO);
applicant.setCreditRating(creditRatingService.create());
return applicantRepository.save(applicant);
}
Here is my Applicant class
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "applicant")
public class Applicant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long identificationNumber;
private String firstName;
private String lastName;
private double monthlyIncome;
private String phoneNumber;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "credit_rating_id", referencedColumnName = "id")
private CreditRating creditRating;
#OneToOne(cascade = CascadeType.ALL)
#JoinTable(name = "applicant_credit",
joinColumns = {#JoinColumn(name = "applicant_id")},
inverseJoinColumns = {#JoinColumn(name = "credit_id")}
)
private Credit credit;
}
And this is the create method for CreditRating object.
public CreditRating create() {
CreditRating creditRating = new CreditRating();
creditRating.setCreditRating(getRandomCreditRating());
return creditRatingRepository.save(creditRating);
}
I want this object to be created while creating an Applicant but somehow I think JPA can't generate the id for it as I am doing the creation like this.
As requested here is CreditRating Entity
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "credit_rating")
public class CreditRating {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int creditRating;
}
I figured out the problem. Problem was in database creditRating and credit somehow didn't have Auto Increment ticked. I dropped the schema then let JPA create the tables again. Then Auto Increment was both ticked on credit and creditRating tables.

Deleting an entity with one to one relation

My two entities have one to one relation
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(uniqueConstraints = #UniqueConstraint(columnNames = "email"), name = "library_user")
public class AppUser {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#EqualsAndHashCode.Exclude
private Long id;
// other fields
#OneToOne(mappedBy="user", cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
private PasswordResetToken token;
// getters/setters and equals/hashcode
}
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(name = "password_reset_token")
public class PasswordResetToken {
private static final int EXPIRATION = 60 * 24;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// other fields
#OneToOne(targetEntity = AppUser.class, fetch = FetchType.EAGER, cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
#JoinColumn(nullable = false, name = "user_id")
private AppUser user;
// getters/setters and equals/hashcode
I tried to delete my user entity by this method
public void deleteUser(Long id) {
resetTokenRepository.deleteAllByUserId(id);
userRepository.deleteById(id);
}
PasswordResetTokenRepository class which method I called in my service method, for deleting user I used regular hibernate method deleteById(Long id)
#Repository
public interface PasswordResetTokenRepository extends JpaRepository<PasswordResetToken, Long> {
void deleteAllByUserId(Long id);
}
But when I try to delete by this method I got this error:
not-null property references a null or transient value : kpi.diploma.ovcharenko.entity.user.PasswordResetToken.user
I read several websites how to delete one to one relation, but their advices didn't help me. For example, I tried a lot of variants of annotation cascade={CascadeType.ALL}, tried all the variants(CascadeType.REMOVE,CascadeType.PERSIST and so on), all time I got the same error. Help me pls, to understand what I do wrong.
try this:
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
Here is complete explication .

JPA one to one mapping creates multiple query when child entity is not found

I have a parent entity 'contracts' that has a one-to-one relation with another entity 'child-contract'. the interesting thing is that the mapping field ('contract_number')id not a primary key-foreign key but is rather a unique field in both the tables. Also it is possible for a contracts to not have any child contract altogether. With this configuration I have observed hibernate to generate 1 additional query every time a contracts does not have a child-contract. I filed this behavior very strange. Is there a way to stop these unnecessary query generation or have I got something wrong.
below is a piece of my code configuration.
#Data
#Entity
#Table(name = "contracts")
public class Contracts implements Serializable {
#Id
#JsonIgnore
#Column(name = "id")
private String id;
#JsonProperty("contract_number")
#Column(name = "contract_number")
private String contractNumber;
#OneToOne(fetch=FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JsonProperty("crm_contracts")
#JoinColumn(name = "contract_number", referencedColumnName = "contract_number")
private ChildContract childContract ;
}
#Data
#NoArgsConstructor
#Entity
#Table(name = "child_contract")
#BatchSize(size=1000)
public class ChildContract implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#JsonProperty("id")
#Column(name = "id")
private String id;
#JsonProperty("contract_number")
#Column(name = "contract_number")
private String contractNumber;
}
Please help.
Thank-you
You can use NamedEntityGraph to solve multiple query problem.
#NamedEntityGraph(name = "graph.Contracts.CRMContracts", attributeNodes = {
#NamedAttributeNode(value = "crmContract") })
Use this on your repository method as
#EntityGraph(value = "graph.Contracts.CRMContracts", type = EntityGraphType.FETCH)
// Your repo method in repository

Hibernate inheritance - OneToMany with 2 class that extend parent not work

I have 3 Entities:
#Data
#AllArgsConstructor
#Entity
#Table(name = "beneficiary")
#Inheritance
#DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING, name = "type")
public abstract class Beneficiary {
public Beneficiary() {}
#Id private String id;
private String description;
}
#Data
#Entity
#DiscriminatorValue("company")
#EqualsAndHashCode(callSuper = true)
public class BeneficiaryCompany extends Beneficiary {
public BeneficiaryCompany() {
super();
}
public BeneficiaryCompany(String id, String description) {
super(id, description);
}
}
#Data
#Entity
#DiscriminatorValue("person")
#EqualsAndHashCode(callSuper = true)
public class BeneficiaryPerson extends Beneficiary {
public BeneficiaryPerson() {}
public BeneficiaryPerson(String id, String description) {
super(id, description);
}
}
An in the other class I want to have 2 separate collections:
#Data
#AllArgsConstructor
#Entity
#Table(name = "transaction")
public class Transaction {
public Transaction() {}
#Id private String id;
private String description;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, targetEntity = BeneficiaryCompany.class)
#JoinColumn(name = "transaction_id", nullable = false)
private Set<BeneficiaryCompany> beneficiaryCompanies;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true,targetEntity = BeneficiaryPerson.class)
#JoinColumn(name = "transaction_id", nullable = false)
private Set<BeneficiaryPerson> beneficiaryPeople;
}
The problem is that every Beneficiary was fetched into beneficiaryCompanies, and beneficiaryPeople in the debugger tells me that:
Unable to evaluate the expression Method threw
'org.hibernate.WrongClassException' exception.
The database records looks fine (DiscriminatorColumn was created). What could be the problem? Why beneficiaryCompanies contains BeneficiaryPerson objects?
#EDIT:
To fetch the records I am using SpringData JPA repositories.
Use #MappedSuperclass on your base class Beneficiary
Alexandar Petrov is absolutely correct. You have to remove #Entity because superclass is not an entity. When dealing with inheritance extending a class, you can use #MappedSuperclass annotation on the base class, in your case, it is Beneficiary.
Edit:
This is a very good article you can refer to.

#JsonIdentityInfo Stop serialization after first occurrence

I have two entities as such:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "numeroOfferta")
#Entity
#Table(name=DatabaseConstants.TABELLA_OFFERTE)
public class Offerta {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private BigInteger numeroOfferta;
#ManyToOne(fetch=FetchType.EAGER)
private Cliente cliente;
private Double importoOfferta;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "offerta")
private Set<Ordine> ordini;
And:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#Entity
#Table(name=DatabaseConstants.TABELLA_ORDINI)
public class Ordine {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private BigInteger id;
private String numeroOrdine;
#ManyToOne(fetch=FetchType.EAGER)
private Offerta offerta;
private String stato;
#OneToOne(fetch= FetchType.EAGER)
private Binder binder;
Where exist a many-to-one relationship between the ORDINE and OFFERTA objects (more ORDINE to one OFFERTA).
When the entities are serialized in JSON it appears that if there is more than one ORDINE, only the first one is retreived completely, while the others appear only as their ID.
What I need is to serialize the ORDINE and its OFFERTA, without going back to ORDINE.
Is the use of #JsonIdentityInfo correct?
Can anyone provide an explanation of the behavior described?

Categories