How to extend JPA entity to just add composition - java

I have a concrete class for Employee entity. Employee is persisted by other application, I'm just using the data. I want to extend Employee to add properties that are other Entities using composition. I don't need to persist a child entity per se, but only the entities I'm trying to extend to Employee with using composition. Here is some code to help clear things up.
#Entity
#Table(name = "Legacy_Table_Name", schema = "another_owner")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "emp_id")
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private String emp_id;
private String firstName;
etc...
-
public class EnhancedEmployee extends Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Transient
private SomeEntity someCompositionProperty;
#OneToMany(mappedBy = "employee", fetch = FetchType.EAGER, cascade=CascadeType.ALL)
private Collection<AnotherEntityWithItsOwnTable1> list1;
#OneToMany(mappedBy = "employee", fetch = FetchType.EAGER, cascade=CascadeType.ALL)
private Collection<AnotherEntityWithItsOwnTable2> list2;
If I make EnhancedEmployee an entity then JPA tries to create/use an EnhancedEmployee database table (right now in dev so I'm using create-drop persistence.xml property). If I remove the #Entity annotation from EnhancedEmployee, JPA complains that EnhancedEmployee is not an Entity in other classes where I want to utilize these extra properties
#ManyToMany(cascade = CascadeType.MERGE)
#LazyCollection(LazyCollectionOption.FALSE)
#JoinTable(name = "PARTICIPATING_EMPLOYEES", joinColumns = { #JoinColumn(name = "event_id") }, inverseJoinColumns = { #JoinColumn(name = "emp_id") })
private Collection<EnhancedEmployee > participants;
All I'm trying to do is reference an Employee setter getters for these extra properties whether or not they have data persisted for these extra properties.
I realize I could probably just modify my Employee class and add someCompositionProperty and list1/2 relations to that class but doesn't that then violate https://en.wikipedia.org/wiki/Open/closed_principle. While my Employee entity class is the "same" for all my projects the source code is really part of each project's package so perhaps the open/closed doesn't apply here and I should just modify the Employee entity class

If I make EnhancedEmployee an entity then JPA tries to create/use an
EnhancedEmployee database table (right now in dev so I'm using
create-drop persistence.xml property).
You could avoid this by changing the inheritance type to SINGLE_TABLE
Also your discriminator column emp_id seems to me like a bad choice. Better change it to something like #DiscriminatorColumn(name = "TYPE") because emp_id is the primary key and and cannot repeat itself in a table. Also your EnhancedEmployee needs #DiscriminatorValue(value = "ENHANCED")

Related

How to map 2 tables through non-PK columns with different names in Hibernate

I have 2 tables that may be related to each other through non-PK secondary columns. Moreover, the column names for this match are different in each table. That is,
#Entity
#Table(name = "PLANS_T")
public class Plans {
private Integer id; // PK
//...
private String secondaryIdentifier; // Should be matched with TRAINEES.auxiliaryIdentifier
//...
}
#Entity
#Table(name = "TRAINEES_T")
public class Trainee {
private Integer id; // PK
//...
private String auxiliaryIdentifier; // Should be matched with PLANS.secondaryIdentifier
}
The relationship between PLANS and TRAINEE is Many-to-One: You can have many Plans for a Trainee.
I need to annotate these properly to indicate that PLANS_T.secondaryIdentifier should be used with TRAINEES_T.auxiliaryIdentifier for JOINs (such as in the Criteria API, which needs a Join Path from one table to the other).
But I can't use the typical examples e.g.
#Entity
class Trainee {
#OneToMany(mappedBy = "plan")
private Collection<Plans> plans = new ArrayList<Plans>();
}
#Entity
class Plans {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="auxiliary_identifier") // Where do I specify "secondaryIdentifier", a non-PK column?
private Trainee trainee;
}
I need a way to specify both of the non-PK columns in the annotations. When using Criteria API, these annotations provide the path to create Join paths.
You should correct your mapping in the following way:
#Entity
class Trainee {
#OneToMany(mappedBy = "trainee")
private List<Plans> plans = new ArrayList<Plans>();
}
#Entity
class Plans {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="secondary_identifier", referencedColumnName = "auxiliary_identifier")
private Trainee trainee;
}
The mappedBy of the #OneToMany is the name of the field that owns the relationship. This is trainee field of the Plans entity.
The referencedColumnName of the #JoinColumn is the name of the column referenced by this foreign key column.

OneToOne relationship, keep only foreign key

I am trying to establish a OneToOne relationship between two entities (PartnerDetails and JWTData. How ever, I only want to store the primary key of PartnerDetails entity in JWTData, not the whole object, like this.
#Entity
#Data
#Table(name = "partner_details")
public class PartnerDetails {
#Id
#Column(name = "partner_id")
private String partnerId;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "partnerId")
private JWTData jwtData;
}
#Entity
#Data
#Table(name = "jwt_data")
#NoArgsConstructor
public class JWTData {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(targetEntity = PartnerDetails.class)
#JoinColumn(name = "partner_id", foreignKey = #ForeignKey(name = "fk_jwt_partnerdetails_partnerid"))
private String partnerId;
#NotBlank
private String secret;
}
But after fetching the JWTData using repository, Hibernate cannot convert the String to a PartnerDetails. Can this be done using any other way?
If you just add PartnerDetails to JWTData then JPA will know to use only the id. JPA is an Object Oriented framework so you should reference objects unless you specifically want a field. JPA handles the details for you. Note that in this configuration JWTData in the "owning" entity because of the mappedBy annotation, therefore only setting the partnerDetails field in a JWTData instance will persist the relationship to the database. The jwtData field in PartnerDetails is for query results only and makes for a Bidirectional instead of a Unidirectional mapping. Also, because of this, having a CascadeType setting generally only makes sense on the owning entity since it is the one handling the database updates and deletes.
When playing around with JPA be sure to turn on the SQL output so that you know what is actually happening.
#Entity
#Data
#Table(name = "partner_details")
public class PartnerDetails {
#Id
#Column(name = "partner_id")
private String partnerId;
#OneToOne(mappedBy = "partnerDetails")
private JWTData jwtData;
#Entity
#Data
#Table(name = "jwt_data")
#NoArgsConstructor
public class JWTData {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// even though it looks like the entire class it's only saving the id to the database.
#OneToOne
private PartnerDetails partnerDetails;

Spring Jpa Data Repository save (update) with LinkedEntity for ManyToMany relationship

There are 2 entities (lets say Rule and Label) with many-to-many relationship using linked entity
as per hibernate reference documentation
Rule enity:
#Entity
#Table(name = "rule")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "name")
public class Rule implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NaturalId
#NotBlank
#Column(unique = true)
private String name;
#Lob
#Column(columnDefinition = "TEXT")
private String content;
#OneToMany(mappedBy = "rule", cascade = {CascadeType.PERSIST,
CascadeType.MERGE})
private List<RuleLabel> labels = new ArrayList<>();
...
Label entity:
#Entity
#Table(name = "label")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Label implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
private String name;
#OneToMany(mappedBy = "label", cascade = {CascadeType.PERSIST,
CascadeType.MERGE})
private List<RuleLabel> rules = new ArrayList<>();
...
Link entity:
#Entity
public class RuleLabel implements Serializable {
#Id
#ManyToOne
private Rule rule;
#Id
#ManyToOne
private Label label;
...
Repositories:
#Repository
public interface LabelRepository extends JpaRepository<Label, Long>
...
#Repository
public interface RuleRepository extends JpaRepository<Rule, Long>
...
Creating new entity via RuleRepository.save(Rule) works fine, but when I'm trying to update existing entity (the same method RuleRepository.save(Rule), but entity to be saved contains id field) it leads to infinite loop of Hibernate: select... queries:
Hibernate: select rule0_.id as id1_7_1_, rule0_.is_active as is_activ2_7_1_, rule0_.content as content3_7_1_, rule0_.is_deleted as is_delet4_7_1_, rule0_.import_section as import_s5_7_1_, rule0_.name as name6_7_1_, rule0_.rule_configuration as rule_con7_7_1_, labels1_.rule_id as rule_id1_8_3_, labels1_.label_id as label_id2_8_3_, labels1_.rule_id as rule_id1_8_0_, labels1_.label_id as label_id2_8_0_ from rule rule0_ left outer join rule_label labels1_ on rule0_.id=labels1_.rule_id where rule0_.id=?
and StackOverflowError as a result
java.lang.StackOverflowError: null
at com.mysql.jdbc.ServerPreparedStatement.getInstance(ServerPreparedStatement.java:332)
...
(LabelRepository acts in the same manner)
How it can be fixed?
Update:
After changing fetch strategy to Lazy
#Id
#ManyToOne(fetch = FetchType.LAZY)
private Rule rule;
#Id
#ManyToOne(fetch = FetchType.LAZY)
private Label label;
infinite loop problem has gone, but new one has appeared - related entities are not being populated and when Hibernate is trying to insert values into link table
Hibernate: insert into rule_label (rule_id, label_id) values (?, ?)
we get
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'rule_id' cannot be null
Okay, well I've always used an EmbeddableId for link entities with JPA. I haven't tried the hibernate example you refer to in terms of using cascade to do the work for me. It could be interesting but there are some differences between pure JPA and Spring Data Repositories. By using an EmbeddableId you can create a separate spring repository for the link entity. Then you manage the relationships yourself. If you don't want to do that then you should use a ManyToMany annotation, but the link entity allows you to create link entity attributes, not shown here. This code will work for you and get you to point B and you can experiment from there:
#Entity
public class Label {
#Id #GeneratedValue private Long id;
#OneToMany(mappedBy = "ruleLabelId.labelId")
private List<RuleLabel> rules = new ArrayList<>();
#Entity
public class Rule {
#Id #GeneratedValue private Long id;
#OneToMany(mappedBy = "ruleLabelId.ruleId")
private List<RuleLabel> labels = new ArrayList<>();
#Entity
public class RuleLabel {
#EmbeddedId
private RuleLabelId ruleLabelId;
#SuppressWarnings("serial")
#Embeddable
public class RuleLabelId implements Serializable {
private Long ruleId;
private Long labelId;
public interface RuleRepository extends JpaRepository<Rule, Long> {
#Query("from Rule r left join fetch r.labels where r.id = :id")
public Rule getWithLabels(#Param("id") Long id);
}
public interface RuleLabelRepository extends JpaRepository<RuleLabel, RuleLabelId> {}
and to use it:
Rule rule = new Rule();
Label label = new Label();
ruleRepo.save(rule);
labelRepo.save(label);
RuleLabel ruleLabel = new RuleLabel();
RuleLabelId ruleLabelId = new RuleLabelId();
ruleLabelId.setRuleId(rule.getId());
ruleLabelId.setLabelId(label.getId());
ruleLabel.setRuleLabelId(ruleLabelId);
ruleLabelRepo.save(ruleLabel);
rule = ruleRepo.getWithLabels(1L);
System.out.println(rule + Arrays.toString(rule.getLabels().toArray()));
Yes because its what you are telling hibernate to do.
By default, all #ManyToOne and #OneToOne associations are EAGER loaded, so when it querying Rule then its also querying RuleLabel and then inside there is Rule again which is causing infinite select queries. It's better to have them LAZY loaded.
You can do field lazy load like this #ManyToOne(fetch=FetchType.LAZY)
This is what JPA 2.0 spec say about defaults:
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
A good read on Lazy and Eager loading

Annotation to join columns with OR condition instead of AND condition

I have 2 java classes, Relation and Person, which both are present in my database.
Person:
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column
private int id;
#Column
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "slave_id", referencedColumnName="id"),
#JoinColumn(name = "master_id", referencedColumnName="id")
})
private List<Relation> relations;
//Getters and setters
}
Relation:
#Entity
#Table(name = "relations")
public class Relation {
#Id
#Column
private int id;
#Column
private int child_id;
#Column
private int parent_id;
#Column
private String type;
//Getters and setters
}
Each Person has a list of relations (or not), the relation should be added to the list when the child_id or the parent_id of the relation is equal to the id of the person.
TL;DR:
When relation.child_id OR relation.parent_id = person.id => add relation to list of relations to the person
The issue I am facing is that this annotation:
#JoinColumns({
#JoinColumn(name = "child_id", referencedColumnName="id"),
#JoinColumn(name = "parent_id", referencedColumnName="id")
})
creates following SQL (just the necessary part):
relations relations6_
on this_.id=relations6_.slave_id
and this_.id=relations6_.master_id
What is the correct annotation in Java Hibernate to generate an SQL statement saying OR instead of AND
Some of the options that you could utilize:
Database views. Create the view that does custom join for you and map the entity to the view.
Join formula. I managed to make them work only on many-to-one associations. Nevertheless, you could make the association bidirectional and apply the formula in the Relation entity.
#Subselect. This is a kind of Hibernate view, suitable if you can't afford to create a real database view or change the db schema to better suit the entity model structure.
This and this answer could also be helpful.
Also, you can always use two separate associations for slaves and masters:
public class Person {
#OneToMany
#JoinColumn(name = "slave_id"),
private List<Relation> slaves;
#OneToMany
#JoinColumn(name = "master_id"),
private List<Relation> masters;
public List<Relation> getRelations() {
List<Relation> result = new ArrayList<>(slaves);
result.addAll(masters);
return result;
}
}
However, keep in mind that joining all of them in a single query requires full Cartesian product between masters and slaves.
You can use #FilterDef and #Filter annotations.

Hibernate. Several foreign keys in one table

Have next tables structure in SQL schema :Clients, Employees, Orders.
And 3 Entity classes in java code accordingly : Client, Employee, Order.
Both primary id fields from Clients and Employees are in Orders table as foreign keys.
Question is how it should be displayed in java code?
As I understand here it should be done smth like adding Set field to Clients and Employees annotated with #OneToMany.
But what should be done in Order Entity and maybe I have to add any additional annotations except #OneToMany?
I think you have some misconceptions about the relational mapping of Hibernate.
If in fact your Orders table have foreign keys of Clients and Employees, then the annotation you are looking for is #ManyToOne
#OneToMany annotation is used when your entity have multiple records referenced by the targeted entity, while #ManyToOne is used when your entity have only one record referencing the targeted entity.
For example:
Orders entity have one reference from Clients and one reference from Employees entities.
In this case, Orders entity could be mapped by the following way:
#Entity
#Table(name = "Orders")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Client client;
#ManyToOne
private Employee employee;
//getters and setters
}
#Entity
#Table(name = "Clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String attribute1;
//getters and setters
}
#Entity
#Table(name = "Employees")
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String attribute1;
//getters and setters
}
With the example given above you should be able to make your schema work fine with Hibernate, but for the sake of understanding, let's imagine a scenario where you would need to get all the Orders from a Client, of course you could do it with a query selecting only the Orders inside the Client table, however Hibernate offers the #OneToMany annotation which will give you the possibility to access all the Orders from a Client without the need of a separate query, only by mapping! Let's see an example:
#Entity
#Table(name = "Orders")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Client client;
//getters and setters
}
#Entity
#Table(name = "Clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String attribute1;
#OneToMany(mappedBy = "client")
private List<Order> orders;
//getters and setters
}
In this example you should be able to get all the Orders from a Client just by calling the get of the "orders" attribute. Please, note that on the #OneToMany mapping we have specified the "mappedBy" attribute as "client", it was needed because we have a bidirectional mapping between Client and Order, in a simple usage of #OneToMany you would not need this mapping.
Important: When working with #OneToMany mapping you would eventually face some lazy fetching problems, in this case I highly recommend you to take a look at this question:
Solve “failed to lazily initialize a collection of role” exception
Also, I think you should start reading more about Hibernate to understand about it's basic concepts, please, check this other question about #OneToMany and #ManyToOne annotations on Hibernate:
Hibernate/JPA ManyToOne vs OneToMany

Categories