Attempting achieve low coupling between JPA entities - oop

One issue I'm running into is a good way to map One-To-Many relationships with JPA / Hibernate, without sacrificing SOLID principles along the way. Here's an example of this problem from a current project I'm working on:
Given the following code which defines a unidirectional One-To-Many relationship:
#Entity
#Table(name = "users")
public class User extends AggregateEntity<User> implements HasRoles, HasContactInfo, HasContacts, HasDeals {
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinColumn(name = "fk_hasContacts")
private Set<Contact> contacts;}
My understanding is that this creates an "fk_hasContacts" column in the "contacts" table, without the "Contact" class knowing or caring which object it is being referenced from. Also, notice how "User" implements the "hasContacts" interface, which creates a decoupling effect between the two entities. If tomorrow I want to add another class; say "BusinessEntity" which also has contacts, there's nothing that needs to be changed within the current code. However, after reading up on the topic in seems that this approach is inefficient from a database performance perspective.
The best way to map a #OneToMany association is to rely on the #ManyToOne side to propagate all entity state changes - As expounded upon here.
Seems to be the prevailing wisdom. Were I to engineer it that way, the "User" class would now look like this:
#Entity
#Table(name = "users")
public class User extends AggregateEntity<User> implements HasRoles, HasContactInfo, HasContacts, HasDeals {
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private Set<Contact> contacts;
And the (previously decoupled) "Contact" class would now have to look like this:
#Entity
#Table(name = "contacts")
public class Contact extends StandardEntity<Contact> {
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "fk_user")
private User user;
The key problem is that it seems from here that JPA doesn't support defining an interface as an entity attribute . So this code:
#Entity
#Table(name = "contacts")
public class Contact extends StandardEntity<Contact> {
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "fk_user")
private HasContacts hasContacts;//some class which implements the "HasUser" interface
Isn't an option. Seems one would have to chose between SOLID OOP, and valid ORM entities. After a lot of reading I still don't know whether the hit to database performance is bad enough to justify writing what amounts to tightly coupled, rigid and ultimately bad code.
Are there any workarounds / solutions to this seeming contradiction in design principles?

Related

Optaplanner: problems with InverseRelationShadowVariable

have a many-1 relationship pupil-formGroup: pupils are assigned to a formGroup and a formGroup can contain many pupils. I have attempted to implement an InverseRelationShadowVariable having watched your video/tutorial https://www.youtube.com/watch?v=ENKHGBMDaCM (which does not quite correspond with the latest optaplanner documentation I realise)
FormGroup extracts
#Entity
#PlanningEntity
public class FormGroup {
#InverseRelationShadowVariable(sourceVariableName = "formGroup")
#OneToMany(mappedBy = "formGroup", fetch = FetchType.EAGER)
private List<Pupil> pupilList = new ArrayList<Pupil>();
public List<Pupil> getPupilList() {
return pupilList;
}
public Integer getPupilCount() {
return pupilList.size();
}
Pupil extracts
#Entity
#PlanningEntity
public class Pupil
#PlanningVariable(valueRangeProviderRefs = "formGroupRange")
#ManyToOne
private FormGroup formGroup;
Config extracts
<solutionClass>org.acme.optaplanner.domain.Plan</solutionClass>
<entityClass>org.acme.optaplanner.domain.Pupil</entityClass>
<entityClass>org.acme.optaplanner.domain.FormGroup</entityClass>
I believe I've followed the steps in the videoexactly (don't we all say that) but at solve time I get hundreds of errors... Repetitions of the following
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
Any hint gratefully received...
The InverseRelationShadowVariable creates a bi-directional relationship between the genuine planning entity (Pupil) and the planning value (FormGroup). This may become problematic if you re-use your planning domain classes for other purposes, such as ORM persistence or serialization.
In this case, Jackson is unable to serialize Pupil, because it references a FormGroup, which has a List containing a reference back to that Pupil. See? An endless loop.
Solve this issue by adding the #JsonIgnore annotation on top of your inverse relation property and breaking that loop for Jackson:
#Entity
#PlanningEntity
public class FormGroup {
#JsonIgnore // THIS IS THE FIX
#InverseRelationShadowVariable(sourceVariableName = "formGroup")
#OneToMany(mappedBy = "formGroup", fetch = FetchType.EAGER)
private List<Pupil> pupilList = new ArrayList<Pupil>();
public List<Pupil> getPupilList() {
return pupilList;
}
public Integer getPupilCount() {
return pupilList.size();
}
...

JPA: single table inheritance possible performance tradeoff

I have following MS SQL DB tables, where I am storing school related data:
HighSchools<---GradeLevels<---Courses<---CourseGrades
The relations between the tables looks like
#Entity
#Table(name = "GradeLevels", schema = "dbo")
public class HighSchoolGradeLevel {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "AttendedId", nullable = false)
private HighSchoolAttended highSchoolAttended;
#OneToMany(mappedBy = "highSchoolGradeLevel")
#OrderBy("createdDate ASC")
private List<HighSchoolCourse> highSchoolCourses = new ArrayList<>(0);
// other fields and getter/setter methods....
}
#Entity
#Table(name = "Courses", schema = "dbo")
public class HighSchoolCourse {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "GradeLevelId", nullable = false)
private HighSchoolGradeLevel highSchoolGradeLevel;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "highSchoolCourse", cascade={CascadeType.REMOVE, CascadeType.PERSIST})
#OrderBy("createdDate ASC")
private List<HighSchoolCourseGrade> highSchoolCourseGrades = new ArrayList<>(0);
// other fields and getter/setter methods....
}
#Entity
#Table(name = "CourseGrades", schema = "dbo")
public class HighSchoolCourseGrade {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CourseId", nullable = false)
private HighSchoolCourse highSchoolCourse;
// other fields and getter/setter methods....
}
The java web-app users can do CRUD operations with the tables data.
The new requirement is to consume the data with the same structure from a third party. The consumed data SHOULD NOT be displayed for the web-app users.
Therefore, I am thinking to use JPA single table inheritance which allows me to:
avoid adding clones of the existing tables
avoid cloning existing business logic which is tight to existing
entity types
avoid code refactoring of existing CRUD ops in case implementing own discriminator column.
The Single table inheritance looks reasonable to use in my, but there is one thing I am not sure about "What is the performance trade off of using Single Table Inheritance, especially while operating on the Parent and its Childs entities?". The question may sound dummy, knowing the fact that Hibernate will update SQL queries with corresponding discriminator statements. But still it would be nice to improve the current knowledge and be sure that the existing users of wep-app will not get bad usage experience.
The existing web-app DAO layer uses with Spring Data JPA. Currently, each of existing tables contains more than ~500k records.

Best way to set transient attribute on entity instance after Metadata.create

I'm currently setting the result of a jpql query on a transient attribute of several instances of entities attached with composition using BeforeDetachEntityListener.
Since I'm also using Metadata.create to create them, I would like to be able to do the same operation after creating them. What's the best way to handle the situation?
You can set values at object creation time with #PostConstruct
public class MyEntity extends StandardEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
protected User creator;
#PostConstruct
protected void init() {
setCreator(AppBeans.get(UserSessionSource.class).getUserSession().getUser());
}
}
More information about how to initialize data in entities can be found in the docs at 5.8.3.1 Entity Fields Initialization and 5.8.3 Assigning Initial Values

Hibernate inheritance without table for basic entity

I've never used inheritance in hibernate and I don't know which strategy should I use (or even do I really need to use strategy). I have three tables with the same interface (the same columns) and I want to create three entities with basic interface for them so it will look like this:
#Entity
+ Basic
+ #Entity
#Table(name="TABLE_1")
Table1
+ #Entity
#Table(name="TABLE_2")
Table2
+ #Entity
#Table(name="TABLE_3")
Table3
As you see I don't want to use table for basic entity. If it is possible to do this kind of inheritance, how to do it? Maybe I don't need 'hibernate' inheritance and I should use normal inheritance?
In application it is used like this:
Somewhere in configuration we store information which entity to use (Table1, Table12 or Table3)
Choosen entity is used in our queries (some writen in HQL, some in Criteria) so each query should know which entity to use.
EDIT
What's more each entity can be used as attribute of some entities and we wan't to know which table should be used. For example:
#Entity
#Table(name="USER")
class User {
#Id
private Integer id;
#ManyToOne
#JoinColumn(name = "SOME_ID", referencedColumnName = "ID", nullable = false)
private Basic basicEntity; // how to use proper strategy using some configuration value (eg. class static attribute or configuration value stored in db?)
}
I think this is recommended way of achieving your goal:
#MappedSuperclass
public abstract class BaseEntity {
public static final int SHARED_PAREMETER = 2;
#Column(name = "modified", columnDefinition = "TIMESTAMP DEFAULT NOW()")
protected Date modified;
//... other fields, getters and setters
}
#Entity
#Table(name = "TABLE_1")
public class Table1 {
#Id
private Integer id;
#ManyToOne
#JoinColumn(name = "SOME_ID", referencedColumnName = "ID", nullable = false)
private Table2 table2;
}
#Entity
#Table(name = "TABLE_2")
public class Table2 {
#Id
private Integer id;
}
In this case, we will have only two tables but both would have fields from BaseEntity. You can't, though, make a relation in Entity to an abstract class but in processing you're fully entitled to do something like this:
public void process(BaseEntity entity){
// processing..
}

Synchronize two lucene documents mapped for the same table (entity)

Consider two identical Java entities (PersonM1, PersonM2) mapped for the same table (PERSON) with the same attributes defined as:
#Entity
#Table(name = "PERSON")
#Indexed
public class PersonM1 {
#Id
#DocumentId
private long id;
#Field
#Column
private String name;
//setters, gettes, ...
}
#Entity
#Table(name = "PERSON")
#Indexed
public class PersonM2 {
#Id
#DocumentId
private long id;
#Field
#Column
private String name;
//setters, gettes, ...
}
Is there a way to update PersonM2 indexes when we update a PersonM1 object?
If The object PersonM1 is updated, changes are persisted on the database, but not in PersonM2 index directory, so PersonM2 indexes won't be correct in this case.
Shall I do it manually (update PersonM1 when PersonM2 is updated)?
Note: Java inheritance trick is not relevant!
There is no way currently, as the identity of the indexed type is represented directly the the class instance of the model. This will change in Hibernate Search 5, so in that version you might have a "clean" solution for such a scenario but I don't know yet if we will expose an API for this, and how this would look like. You'll probably have to provide your custom implementation of "entity identity".