JPA/Hibernate: Multiple unnecessary entires in a table - java

I'm new here and I'm also new in JPA! I developed a little JPA Application with the following entities:
A Customer(Name, Prename, ID) has got an Address(ZIP_Code, city, ID). The relation between these entities is ManyToOne (Customer's perpective) and OneToMany (Address' perspective).
The code (parts) are the following: (without getters/setters)
#Entity
public class AddressEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String city;
private int zipCode;
#Column
#ElementCollection(targetClass=CustomerEntity.class)
private List<CustomerEntity> customers;
public AddressEntity() {}
#OneToMany(cascade=CascadeType.ALL,
fetch=FetchType.EAGER,
mappedBy="addressentity")
public List<CustomerEntity> getCustomers() {
return customers;
}
#Entity
public class CustomerEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int primaryKey;
private String preName, surName;
#ManyToOne(targetEntity=AddressEntity.class, cascade=CascadeType.ALL)
#JoinColumn(name="Address_ID")
private AddressEntity address;
public CustomerEntity() {}
}
Now i want to add two persons:
1) Bart Simpson 1234 Springfield
2) Homer Simpson 1234 Springfield
The problem is that in the address table Springfield appears twice. But that's not the sense of normalized database! How can I realize that an AddressEntity is only added when it does not exists before!
Thanks for answering,
eniac

Persisting new addresses
It is possible to have many AddressEntities with the same City and Zipcode. If you require that a given CustomerEntity is related to the same address, then you must use that specific entity. So use the same AddressEntity that you used for Bart for Homer also
CustomerEntity bart = new CustomerEntity();
//Set Barts’s fields.
CustomerEntity homer = new CustomerEntity();
//Set Homers’s fields.
AddressEntity simpsonsPlace = new AddressEntity();
//set 1234 and Springfield.
bart.setAdress(simpsonsPlace);
homer.setAdress(simpsonsPlace);
List<CustomerEntity> simpsons = new ArrayList<CustomerEntity>();
simpsons.add(bart);
simpsons.add(homer);
simpsonsPlace.setCustomers(simpsons);
yourEntityManager.persist(bart);
yourEntityManager.persist(homer);
Associating with existing addresses.
If you are creating homer sometime after bart then you will want to associate bart to an existing address. It maybe that you will pass in the address from the client. This will have the id field populated and will be a detached entity. You can use this entity with the em.merge() operation;
CustomerEntity homer = new CustomerEntity();
homer.setAdress(detachedAddress);
yourEntityManager.merge(homer);
A better way might be to form a compound PK of Zip/HouseNumber for example. This ensures that any AddressEntity with Zip/HouseNumber combination already in the DB will be treated as a detached object, and will of course only appear once in the DB.
Embeddables
You have both #ElementCollection and #OneToMany targeted to your CustomerEntity Entity.
As per JPA Spec 11.1.14;
The ElementCollection annotation defines a collection of instances of
a basic type or embeddable class.
It is therefore incorrect to target an entity with #ElementCollection.
You require a relation between your two entities, therefore, in you your AddressEntity you just need to use the #OneToMany and drop the #ElementCollection. You also need to map (with mappedBy) to the field of the target class and not the target class itself;
#Entity
public class AddressEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String city;
private int zipCode;
#OneToMany(cascade=CascadeType.ALL,
fetch=FetchType.EAGER,
mappedBy="address") //Not addressentity
private List<CustomerEntity> customers;
public AddressEntity() {}
public List<CustomerEntity> getCustomers() {
return customers;
}
Alternatively you can define your CustomerEntity as an #Embeddable and not an #entity. In this way you can use #ElementCollection to target CustomerEntity from AddressEntity, but CustomerEntity would not be an entity in its own right and cannot be processed independently as it is dependent on AddressEntity

Related

Java entity modeling with table that relates to several others

I have a doubt about how the modeling of my entity would be. Come on, I have a table in the database that serves to save documents from my system, this table has the columns id, fk_id (element foreign key), fk_table (entity name) and file_name (stores the name of my file) .
I did a lot of research before posting my question here, but I didn't find anything related to it, what would my entities, user, patient and doctor?
DB:
id
fk_id
fk_table
file_name
1
21
user
test1.jpg
2
32
doctor
test2.pdf
3
61
user
test10.pdf
4
100
patient
test5.jpg
Class:
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String LastName;
// What would a one-to-many relationship look like?
}
public class patient{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// What would a one-to-many relationship look like?
}
You can use #Where. But be aware that #Where is a Hibernate annotation. It's not in the JPA standard.
For example in the User entity: (I assume that your table is mapped to an entity called Document)
#Where( clause = "fk_table = 'user'")
#JoinColumn(name = "fk_id")
#OneToMany
private List<Document> documents = new ArrayList<>( );
The following is based only on standard JPA annotations. The idea is to create an inheritance hierarchy for the documents table. The base is:
#Entity
#Table(name = "XX_DOCUMENT")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "fk_table")
public abstract class BaseDocument {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#Column(name = "file_name")
private String fileName;
}
Here we define that all entities extending this will go to the same table, with the fk_table column to discriminate. The entities extending it are defined as follows:
#Entity
#DiscriminatorValue("doctor")
public class DoctorDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Doctor doctor;
}
#Entity
#DiscriminatorValue("patient")
public class PatientDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Patient patient;
}
// and so on
The interesting thing is that we are reusing the column fk_id to point to the right table. From a small experiment, Hibernate seems to not have problems with it. I would suggest that you manage the DB creation another way just to be safe.
The Doctor, Patient etc need not have a common base class, e.g.:
#Entity
#Table(name = "XX_DOCTOR")
public class Doctor {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "doctor")
private Collection<DoctorDocument> documents = new ArrayList<>();
// any doctor-specific fields
}
#Entity
#Table(name = "XX_PATIENT")
public class Patient {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "patient")
private Collection<PatientDocument> documents = new ArrayList<>();
// any patient-specific fields
}
// and so on
You can read a (doctor, patient, ...)'s documents from the relevant collection. You can even query BaseDocument instances based on any criteria.
You can even go ahead and do more fabcy stuff with the Java code. E.g. define an interface HasDocuments:
public interface HasDocuments<D extends BaseDocument> {
Collection<D> getDocuments();
}
Doctor, Patient, ..., implements this, so they can all be treated the same way.

Different bidirectional #ManyToOne parent for 2 Entities extending some base Entity, but keeping base entity data in one table

This title is closest I could get to what I am trying to do. :)
Let's start with how it should look in database (tables with columns):
Email
- id : PK
- email : String
CompanyEmail
- email_id : FK
- company_id : FK
PersonEmail
- email_id : FK
- person_id : FK
Company
- id : PK
Person
- id : PK
Now let's look at model:
#Entity
public class Company
{
#Id
#GeneratedValue
private Long id;
#OneToMany
private List<CompanyEmail> emails = new ArrayList<>();
}
#Entity
public class Person
{
#Id
#GeneratedValue
private Long id;
#OneToMany
private List<PersonEmail> emails = new ArrayList<>();
}
// Should this be #Entity? Maybe #MappedSuperclass? What strategy to use to be able to extend it?
public class Email // This is base email class/entity
{
#Id
#GeneratedValue
private Long id;
#javax.validation.constraints.Email
private String email;
}
#Entity
public class PersonEmail // This needs to somehow extend Email, so all Email data is also accessible here.
{
#ManyToOne
private Person person;
}
#Entity
public class ComanyEmail // This needs to somehow extend Email, so all Email data is also accessible here.
{
#ManyToOne
private Company company;
}
Now my question is - is it possible in Hibernate (latest) to achieve such structure?
Key points when designing above (what drove me):
Keep ALL emails in one table, for sanity checks (uniqueness).
Have smallest database footprint - above gives just one table for Email and then 2 join tables with 2 FK.
Still be able to keep the model design Hibernate-friendly (basically don't use any special queries, just JPA annotations). This means that both Company and Person can easily LAZY load their specific emails (subclasses), and also those subclassed emails can correspond to them (PersonEmail to Person, and CompanyEmail to Company) - making model bidirectional.
Note: I've also considered creating something like Contactable base class for Comapny and Person which would have Email list, but that doesn't suit my needs (neither class per table, nor same table designs).
My question is - is it possible? Even if I don't get answer with example based on classes I gave, just the fact will give me hope and I will find it.
Edit 1
Should I maybe use discriminator in Email table and then keep FK there?
Email
- id : PK
- email : String
- companyOrPerson : FK
- discriminator : Decides what FK points at
Company
- id : PK
Person
- id : PK
Here I am grabbing straws - I have no idea if such thing is possible, can discriminator decide about parent table (companyOrPerson)? Does bidirectional work here (like I mentioned, maybe I should make some base class Contactable)?
I am open to suggestions on how to do it well.
Favour composition over inheritence. It tends to simplify things.
If you had main entities of
Person
Company
Email
Then a couple of composites
PersonEmail (contains a Person and an Email)
CompanyEmail (contains a Company and an Email)
Would that not fit what you need?
e.g.
#Entity
public class Person {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#OneToMany
#JoinColumn(name="person_id")
private List<PersonEmail> emails = new ArrayList<>();
}
#Entity
public class Company {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#OneToMany()
#JoinColumn(name="company_id")
private List<CompanyEmail> emails = new ArrayList<>();
}
#Entity
public class Email {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#javax.validation.constraints.Email
private String email;
}
#Entity
public class PersonEmail {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="person_id", referencedColumnName="id")
private Person person;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="email_id", referencedColumnName="id")
private Email email;
}
#Entity
public class CompanyEmail
{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="company_id", referencedColumnName="id")
private Company company;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="email_id", referencedColumnName="id")
private Email email;
}

Guidance on mapping the following relationship with ebean using jpa annotations

I have the following two entities in the DB ( structure is fixed ) which I am trying to map using JPA Annotations and EBEAN is ORM.
I have the following beans:
class Item {
public Long id;
public String name;
public Consignee intermediate;
public Consignee ultimate;
}
class Consignee {
public Long id;
public String name;
public String address;
public Item item;
}
And their corresponding tables:
Item
----
id
name
Consignee
---------
id:
name
address
item_id
type: [1,2] / 1: intermediate, 2:ultimate
the main entity is ITEM although the relationship is mapped from the consignee side.
How can I mapped this using the Annotations so that the consignees ( ultimate, intermediate ) are loaded when I fetch the Item object from DB ?
Could you please point me to the right direction
The two tables your are trying to map to Ebean are called Entity Models and the relation between Entity Consignee to Item is a One to Many Relationship.
Such relation can be mapped with a #OneToMany annotation on the Consignee side, and with an #ManyToOne on the Item side.
Also the field type of Consignee can be mapped with an Enumeration persisted as integer, and the remaining fields can be mapped via #Column annotation.
A possible implementation of your requirements could be something like:
public enum CONSIGNEE_TYPE {
INTERMEDIATE,
ULTIMATE
}
#Entity
#Table(name = "Consignee")
public class Consignee extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public String name;
public String address;
#Enumerated(EnumType.ORDINAL)
public CONSIGNEE_TYPE type;
#Column(name="item_id")
#OneToMany(cascade = CascadeType.ALL, mappedBy = "consignee", fetch = FetchType.EAGER)
public List<Item> item = new ArrayList<Item>();
//TODO: Generate Constructors/Getters/Setters
}
#Entity
#Table(name = "Item")
public class Item extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public String name;
#ManyToOne(optional = false, fetch = FetchType.EAGER)
public Consignee consignee = new Consignee();
//TODO: Generate Constructors/Getters/Setters
}
I removed the ambiguation with the ultimate and intermediate consignee type from the class Item since you can store the type of the consignee on the consignee itself.
Hope this helps.
As a very nice resource for the future, I recommend you to read the Unit Tests available on the source code of the Ebean itself. It ain't pretty but it helped me a lot!

Persist an entity that have associated a list of entities that use #idclass

I have an Evaluation entity that has an associated list of EvaluationEvaluator. I need to explicitly create that entity because it required an extra column "STATUS". Before I continue evaluation. I do: evaluation.setEvaluationEvaluator(listEvaluator) where listEvaluator is a list of EvaluationEvaluator type. Then persist(evaluation). When I run this, it does not throw any kind of exception. But in the database, it inserts in the Evaluation table, and not inserted into the EvaluationEvaluator table.
Below my Evaluation entity.
#Entity
public class Evaluation implements Serializable{
#Id
#GeneratedValue
private Long id;
//MORE FIELDS
#OneToMany(mappedBy="evaluation")
private List<EvaluationEvaluator> evaluators;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvalutionEvaluator Entity:
#Entity
#Table(name= "EVALUATION_EVALUATOR")
#IdClass(EvaluationEvaluatorId.class)
public class EvaluationEvaluator implements Serializable{
#Id
#Column(name="EMPLOYEE_ID", insertable=false , updatable=false)
private Long EmployeeID;
#Id
#Column(name="EVALUATION_ID", insertable=false, updatable=false)
private Long EvaluationID;
#ManyToOne
#JoinColumn(name"EMPLOYEE_ID")
private Employee employee;
#ManyToOne
#JoinColumn(name"EVALUATION_ID")
private Evaluation evaluation;
#NotNull
private String status;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvaluationEvaluatorId class
public class EvaluationEvaluatorId implements Serializable{
private Long employeeID;
private Long evaluationID;
//CONSTRUCTOR
//GETTER AND SETTERS
}
And finally, this is my EvaluationBean class
#Stateful
#Named
#LocalBean
#ConversationScoped
public class EvaluationBean {
#PersistentContext(type= PersistenceContextType.EXTENDED)
private EntityManager em;
#Inject
Conversation conversation;
private Evaluation evaluation;
//IN MY WEBPAGE I IMPLEMENT PRIMEFACES PICKLIST AND IT REQUIRE DUALIST TO HANDLE
private DualListModel<Employe> evaluators;
private EvaluationEvaluator evaluationEvaluator;
private List<EvaluationEvaluator> listEvaluators;
#Inject
private EmployeeList employeeList;
//GETTER AND SETTERS
public String begin(){
if (conversation.isTransient()){
converstaion.begin();
}
evaluationEvaluator = new EvaluationEvaluator();
listEvaluators = new ArrayList<EvaluationEvaluator>();
evaluation = new Evaluation();
List<Employee> source = employeeList.findAll();
target = new ArrayList<Employee>();
evaluators = new DualListModel<Employee>(source, target);
return "/evalution/evaluationAsig.xhtml"
}
public String save(){
Iterator<Employee> iterator = evaluators.getTarget().iterator();
while (iterator.hasNext()){
EvaluationEvaluator ev = new EvaluationEvaluator();
ev.setEmployee(iterator.next());
listEvaluators.add(ev);
}
evalution.setEvaluationEvaluators(listEvaluators);
if(evaluation.getId()==null){
em.persist(evalution);
} else{
em.merge(evalution);
}
if(!conversation.isTransient()){
convesation.end();
}
return "/evalution/evaluationsAsig.xhtml"
}
}
When I debug my application,apparently everything is correct, but I mentioned above, doesn't persist in EvaluationEvaluator table.
Your #OneToMany association is missing cascading configuration.
Add cascade = CascadeType.ALL or cascade = {CascadeType.PERSIST, CascadeType.MERGE} to the #OneToMany annotation. JPA assumes no cascading by default so you would need to persist each EvaluationEvaluator by yourself explicitely otherwise.
UPDATE
There is another thing wrong with the code - the Ids of EvaluationEvaluators are never assigned. You have a complex key made of two Long columns. Both are marked not insertable nor updatable which tells to JPA that the id is going to be somehow generated on database level and it should not care about it. There is however no sequence configured explicitely in your entity (although it is not necessarily required) and also from your comment:
I did what you recommended but it throws the following exception. "A different object with same identifier was already associated with the session"
I assume that this is not the case and both id column values default to null or zero and are same for all EvaluationEvaluators you are trying to persist. If you'd like the database to generate the id for you automatically use #GeneratedValue - Configure JPA to let PostgreSQL generate the primary key value - here you can find explanation how to do this (the database part is database dependent, this is for PostgreSQL). The most common use case however, is to configure the sequence but let hibernate pick the next value, instructions here - Hibernate sequence on oracle, #GeneratedValue(strategy = GenerationType.AUTO)

JPA #MapsId vs #JoinColumn(updatable=false, insertable=false)

It seems to me that there is virtually no difference between the below two ways of mapping. Here is an example base on #MapsId javadoc:
// parent entity has simple primary key
#Entity
public class Employee {
#Id long empId;
...
}
// dependent entity uses EmbeddedId for composite key
#Embeddable
public class DependentId {
String name;
long empid; // corresponds to primary key type of Employee
}
#Entity
public class Dependent {
#EmbeddedId DependentId id;
...
#MapsId("empid") // maps the empid attribute of embedded id
#ManyToOne Employee emp;
}
What if I change Dependent's mapping to:
#Entity
public class Dependent {
#EmbeddedId DependentId id;
#ManyToOne
#JoinColumn("empid", insertable=false, updatable=false)
Employee emp;
}
What is the difference of the above two approach?
So, I tested #MapsId for my usage when in the table I have only one foregin key it was no different. But for tables where I have two foregin keys to one table like ...
UserTable, and EmailTable-> #MapsId(owner)UserTable owner, #MapsId(receiver) UserTable receiver i have problems with that. Hibernate throws exceptions. So i have to back to old #JoinColumn way of doing that. That was a one differemce that I met with that adnotations.
I am using combination of both #MapsId and #JoinColumn together to avoid getting extra field getting created in DB for associating the entities. IF I ignore #JoinColumn, an extra field is getting created in DB.
#Entity
public class BookingsModel implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private SlotDateModel slotDateModelObj;
#JsonProperty
String slotnumber;
#MapsId("memberid")
#JsonBackReference
#ManyToOne
#JoinColumn(name="memberid",referencedColumnName = "memberid")
#NotNull
MemberModel memberModel;
.
.
.
}
#Entity
public class MemberModel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#JsonProperty
#Id
String memberid;
#JsonProperty
String name;
#JsonIgnore
String phoneno;
#JsonManagedReference
#OneToMany
Set<BookingsModel> bookings;
.
.
.
}
#Embeddable
public class SlotDateModel implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
String memberid;
String slotdate;
.
.
.
}
Tables generated with #JoinColumn
Table generated when #JoinColumn is commented Can notice that the extra field "member_model_memberid" is getting added.

Categories

Resources