I have somewhat big object model in my app, and want to decouple model definition from persistence layer. This is an example of class that I want to generate entity class from:
#Setter
#Getter
public abstract class AbstractAccount {
protected Long id;
protected AbstractProfile profile;
protected AbstractAuthorization<AbstractRole> authorization;
protected AbstractWallet wallet;
protected AbstractActivityHistory<AbstractGameSessionRecord, AbstractOperationRecord> history;
// Constructors and stuff...
}
Simply marking every class with #Entity, manipulating field annotations as well would be enough, but I really need to decouple them. I want to get entities like this:
#Data
#Entity
#SuperBuilder
#Inheritance(strategy = InheritanceType.JOINED)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public abstract class AccountEntity {
public AccountEntity(AccountType accountType) {
this.accountType = accountType;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
#Embedded
public AbstractWalletEntity walletEntity;
#Embedded
public AbstractProfileEntity profileEntity;
#Embedded
AbstractAuthorizationEntity authorizationEntity;
#Column(nullable = false)
#Enumerated(EnumType.STRING)
public AccountType accountType;
}
Related
In my project, we are moving from SQL to NoSQL to a certain extent.
I wanted to know, how can we inherit BaseClass properties into child classes in spring data mongo.
I know how to do it in Spring JPA for SQL.
Example,
Below is BaseEntity parent class which is annotated with #MappedSuperClass
It has id and version as its fields.
#MappedSuperclass
public class BaseEntity {
#Id
#GeneratedValue
private Long id;
#Version
private Integer version;
//Getters and setters omitted for brevity
}
entities can extend the BaseEntity class and skip declaring the #Id or #Version properties since they are inherited from the base class.
#Entity(name = "Post")
#Table(name = "post")
public class Post extends BaseEntity {
private String title;
#OneToMany
private List comments = new ArrayList();
#OneToOne
private PostDetails details;
#ManyToMany
#JoinTable(//Some join table)
private Set tags = new HashSet();
//Getters and setters omitted for brevity
public void addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void addDetails(PostDetails details) {
this.details = details;
details.setPost(this);
}
public void removeDetails() {
this.details.setPost(null);
this.details = null;
}
}
#Entity(name = "PostComment")
#Table(name = "post_comment")
public class PostComment extends BaseEntity {
#ManyToOne(fetch = FetchType.LAZY)
private Post post;
private String review;
//Getters and setters omitted for brevity
}
How can I implement same thing in Mongo? For example
#MappedSuperclass
public class BaseEntity {
#Id
#GeneratedValue
private Long id;
#Version
private Integer version;
//Getters and setters omitted for brevity
}
#Document(collection = "Post")
public class Post extends BaseEntity {
private String title;
//Rest of the code
}
#Document(collection = "PostComment")
public class PostComment extends BaseEntity {
#ManyToOne(fetch = FetchType.LAZY)
private Post post;
private String review;
//Getters and setters omitted for brevity
}
You do not need any annotation to do that in Mongo. Mongo itself will take care of superclass for you.
Just extend BaseEntity class in all your entities, all entities will have fields from BaseEntity class when you read and write entities to database. This also works at multilevel hierarchy. i.e. Post extends BaseEntity, BaseEntity extends Entity, in this case Post will have fields from both BaseEntity and Entity class.
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.
I have tree Entities: Client, ClientConfigurationA and ClientConfigurationB.
I try to make something like:
#Entity
#Table(name = "CLIENT")
public class Client {
#Id
private int id;
#OneToOne(mappedBy = "client")
private ClientConfiguration configuration
// some getter and setter
}
#MappedSuperClass
public class ClientConfiguration {
#Id
private int id;
#OneToOne
#MapsId
protected Client client;
// getters and setters
}
#Entity(name = "CLIENT_CONF_A")
public class ClientConfigurationA extends ClientConfiguration { ... }
#Entity(name = "CLIENT_CONF_B")
public class ClientConfigurationB extends ClientConfiguration { ... }
But actually I can't define a OneToOne related to MappedSuperClass and not an Entity.
So what's the best way to implement this case ?
I already tried this solution without succes.
Thanks for reading.
It's impossible and here's why:
When hibernate processes entities it needs to know what table to join with when resolving one to one relations.
The problem arise with #MappedSuperClass is that it doesn't define table, so multiple entities could be inherited from this class, each one having different table. Hibernate just couldn't know what table to join then.
If you want to have common super class, I suggest using an abstract #Entity class.
After many attempts i finally succeeded.
#Entity
#Table(name = "CLIENT")
public class Client {
#Id
private int id;
#OneToOne(mappedBy = "client")
private ClientConfiguration configuration
// some getter and setter
}
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#DiscriminatorColumn(name = "TYPE")
public abstract class ClientConfiguration {
#Id
private int id;
#Column(name = "TYPE")
private String type;
#OneToOne
#MapsId
protected Client client;
// getters and setters
}
#Entity(name = "CLIENT_CONF_A")
public class ClientConfigurationA extends ClientConfiguration { ... }
#Entity(name = "CLIENT_CONF_B")
public class ClientConfigurationB extends ClientConfiguration { ... }
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.
I want to use mixed #Inheritance strategy, but Hibernate doesn't support it.
Is there any way to implement JOINED inheritance without actual class inheritance.
For example:
#Entity
#Table(name="A")
#Inheritance(strategy=InheritanceType.JOINED)
public class A {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID_SEQ")
private Long id;
//getters
//setters
}
#Entity
#Table(name="B")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class B {
#Id
private Long id;
//getters
//setters
}
So, basically in B I just want to refer to #Id generated in A without extending from A.
I found the solution. JPA doesn't allow you to combine #Id and #OneToOne. However, #MapsId annotation does the trick:
#Entity
public class A {
#Id
private Long id;
//getters
//setters
}
#Entity
public class B {
#Id
private Long id;
#MapsId
#OneToOne(optional=false, fetch=FetchType.EAGER)
#JoinColumn(nullable=false, name="id")
private A a;
//getters
//setters
}
I think you can accomplish this by making a #OneToOne relationship or a #OneToMany and point the table name like this
#Id #OneToOne
#JoinColumn(name = "id")
private A a;