How to use Optimistic Locking between 3 entities in JAVA - java

I have the following entities:
#Entity
public class TableA {
#Id
#GeneratedValue(...)
private Long id;
private Timestamp updatedDateTime;
#Version
private int version;
}
#Entity
public class TableB{
#Id
#GeneratedValue(...)
private Long id;
#ManyToOne
#JoinColumn(name="fieldTableC")
private TableC paymentsData;
}
#Entity
public class TableC{
#Id
#Column(name = "fieldTableC")
private String fieldTableC;
private String someOtherField;
}
The problem I am facing, is that I cannot find any information on how to do the following scenario:
If either TableB or TableC gets updated, then I need hibernate to automatically increment the version in TableA. Should I add a FK of TableA to TableB & TableC ?
As you can see, I already have a #ManyToOne from TableB to TableC, if that helps in any way.
PS: I am using JpaRepository

I ended up by adding the child reference in parent, and parent reference into child. Whenever I want to save a child, I also update the parent updatedDateTime value. My method is #Transient, so everything will be saved in 1 transaction.
Also added CascadeType.SAVE_UPDATE, so that when saving parent, the child will also be saved
Eg:
Added to TableA:
#OneToMany(mappedBy = "tableA")
#Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
private List<TableB> list= new ArrayList<>();
Added to TableB:
#ManyToOne
#JoinColumn(name = "tableA_root_id")
private TableA tableA;

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.

Nested entities contains null after save

I have an entity with some nested entities like this
public class MyEntity {
#ManyToOne
#JoinColumn(name="FK_ENTITY2")
private Entity2 fkEntity2;
#ManyToOne
#JoinColumn(name="FK_ENTITY3")
private Entity3 fkEntity3;
}
with entity2 and entity3 like this:
public class Entity2/3{
#Id
private Long id;
#Column(name = "code")
private String code;
#Column(name = "desc")
private String desc;
//getter and setter etc
}
Both Entity2 and Entity3 have values stored in the database so when I'm doing an insert on MyEntity, I'm doing this:
#Transactional
#Service
public MyService {
//idFromController and otherIdFromController refers to existent records in the database
public MyDto save(Long idFromController, Long otherIdFromController){
Entity2 entity2 = new Entity2();
entity2.setId(idFromController);
Entity3 entity3 = new Entity3();
entity3.setId(otherIdFromController);
MyEntity newMyEntity = new MyEntity();
newMyEntity.setEntity2(entity2);
newMyEntity.setEntity3(entity3);
MyEntity result = myEntityRepository.saveAndFlush(newMyEntity);
return getDto(result);
}
}
it works fine, the data are stored correctly in the DB with the correct foreign keys BUT...
After insert I want to build a DTO which contains id, code and desc from the nested entities so something like this:
private MyDto getDto(MyEntity result){
MyDto dto = new MyDto();
dto.setId2(result.getEntity2().getId());
dto.setCode2(result.getEntity2().getCode());
dto.setDesc2(result.getEntity2().getDesc());
dto.setId3(result.getEntity3().getId());
dto.setCode3(result.getEntity3().getCode());
dto.setDesc3(result.getEntity3().getDesc());
}
Here is the problem, I only got the id fields and null on code and description.
When I call getDto() in a search it works and every field has the correct values, so it is something related to the insert transaction? how could I solve this?
When you create the DTO, the (existing) entities are not attached to the persistence context, so the corresponding data has not been loaded from the DB and cannot be copied to the DTO.
One option would be to load the entities e.g. via 'myRepository.findById...' and associate the returned (managed) entities.
you missed some part of the many-to-one relation. first in MyEntity class it was better to define a fetch type. in Entity2 and Entity3 you need to define #OneToMany as the other side of the relation with declaration of fetch type and cascade = CascadeType.ALL which simply tells to hibernate what it can do with relative entity when you are doing save, delete ,update. I reformat your code as following
public class Entity2/3{
#Id
private Long id;
#Column(name = "code")
private String code;
#Column(name = "desc")
private String desc;
#OneToMany(cascade = CascadeType.ALL,mappedBy ="fkEntity2",fetch=FetchType.LAZY)
private List<Entity2> entityTwoList;
// for Entity3
#OneToMany(cascade = CascadeType.ALL,mappedBy ="fkEntity3",fetch=FetchType.LAZY)
private List<Entity3> entityThreeList;
public class MyEntity {
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="FK_ENTITY2")
private Entity2 fkEntity2;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="FK_ENTITY3")
private Entity3 fkEntity3;
}
}

JPA: Mapping crossed OneToOne and ManyToOne relations

I have two entities, which we'll call A and B. B always has A as a parent with a ManyToOne relation.
However, I need A to have a OneToOne relation with the latest record inserted in table B.
This is because I need to save multiple versions of B but 99% of the time will only need to use the most recent one.
This looks something like this:
#Data
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Setter(AccessLevel.NONE)
private Long id;
/* Properties
...
*/
#OneToOne(optional = false)
private B latest;
}
#Data
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Setter(AccessLevel.NONE)
private Long id;
/* Properties
...
*/
#Column(nullable = false)
private Date lastModified;
#ManyToOne(optional = false)
private A parent;
}
Now, the issue at hand is that I cannot seem to persist these entities as one always appears to be transient:
A cannot be persisted because latest references B, yet B is not persisted.
B cannot be persisted because parent references A, yet A is not persisted.
Attempting to do so results in:
java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : B.parent -> A
I tried wrapping the code responsible for persisiting them in a #Transactional method but the same happens:
#Transactional
public void saveAB(A parent, B child) {
parent.setLatest(child);
child.setParent(parent);
Arepository.save(parent);
Brepository.save(child);
}
I also thought of disregarding the OneToOne relation from A to B, instead having latest as a transient #Formula field which would query B to take the most recent record. However, #Formula seems to be limited to primitives, not full entities.
What would be the proper way to do this with JPA? Am I approaching this the wrong way?
Since A and B depend on each other they should probably be considered a single aggregate with A being the aggregate root.
This means you'd have only an ARepository and also CascadeType.ALL on the relationships.
The solution was to apply #JoinFormula as explained here.
#Data
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Setter(AccessLevel.NONE)
private Long id;
/* Properties
...
*/
#ManyToOne
#JoinFormula(value = "(SELECT b.id FROM b " +
"WHERE b.id = id ORDER BY b.lastModified DESC LIMIT 1)")
private B latest;
}
Then on B:
#ManyToOne(optional = false)
private A parent;

Why in this Hibernate mapping it is used #ManyToOne instead #OneToOne?

I am absolutly new in Hibernate development and I have the following problem.
I have 2 entity classes that maps 2 DB tables:
1) The first entity class (the main one) is named KM_ProjectInfo and map a DB table named KM_PROJECT.
2) The second entity class is named KM_ProjectInfoStatus and map a DB table named KM_PROJECT_INFO_STATUS.
So the second one represent a specific field of the first one (a status of the row representd by an instance of the KM_ProjectInfo class). Infact I have something like this:
1) KM_ProjectInfo class:
#Entity
#Table(name = "KM_PROJECT")
public class KM_ProjectInfo implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfo;
#Column(name = "name")
private String name;
#Column(name = "technology")
private String technology;
#ManyToOne
#JoinColumn(name = "idCountry")
private KMCountry country;
#Column(name = "power")
private long power;
#Column(name = "cod")
private String cod;
#ManyToOne
#JoinColumn(name = "idProjectInfoStatus")
private KM_ProjectInfoStatus status;
// GETTERS & SETTERS
}
2) KM_ProjectInfoStatus:
#Entity
#Table(name = "KM_PROJECT_INFO_STATUS")
public class KM_ProjectInfoStatus implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfoStatus;
#Column(name = "foldertech")
private Long foldertech;
#Column(name = "folderproject")
private Long folderproject;
// GETTERS & SETTERS
}
So, as you can see in the previous snippet, the KM_ProjectInfoStatuss is a field of the KM_ProjectInfo because I want that it contains the primary key of this table as foreign key.
In the logic of my application I want that at one row of the KM_PROJECT table (so at one instance of the KM_ProjectInfo entity class) is associated a single row of the KM_PROJECT_INFO_STATUS (one instance of the KM_ProjectInfoStatus entity class) because it represent a specific status for the KM_PROJECT row.
In my code I have:
#ManyToOne
#JoinColumn(name = "idProjectInfoStatus")
private KM_ProjectInfoStatus status;
but I think that is wrong because at one row of my first table it is associated a specific single row of the second table. But maybe I am missing something about how Hibernate work.
Can you help me to understand what I am missing? What it work? Why I have #ManyToOne instead #OneToOne?
Tnx
It all depends on how you want to model things. In terms of Database structure, OneToOne and ManyToOne are implemented in the same way:
One or more JoinColumns which makes a foreign key pointing to the primary key of the other table.
So both solutions correctly map to your database, but it depends if you want to allow several KM_ProjectInfo to point to the same KM_ProjectInfoStatus, or only allow a single one.
Note that, even though you would declare a OneToOne, you could still end up with multiple KM_ProjectInfo pointing to the same KM_ProjectInfoStatus if you don't manipulate Hibernate properly.
Here you did not declare the reverse relationship, but if you did, the declaration would have to be different:
In case of a OneToOne, you would have a KM_ProjectInfo member
In case of a OneToMany (reverse of ManyToOne), you would have a Collection<KM_ProjectInfo> member
From the description it seems you want to have one-to-one relationship. That is the project entity should have its very own status not shared by any other project. You could achieve this by using #OneToOne as below.
#Entity
#Table(name = "KM_PROJECT")
public class KM_ProjectInfo implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfo;
#OneToOne
#JoinColumn(name = "idProjectInfoStatus")
private KM_ProjectInfoStatus status;
}
#Entity
#Table(name = "KM_PROJECT_INFO_STATUS")
public class KM_ProjectInfoStatus implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfoStatus;
#OneToOne(mappedBy="idProjectInfoStatus")
private KM_ProjectInfo project;
}
This way you can have specific status for the KM_PROJECT.
Coming back to #ManyToOne, you will want to have this if you want to share the same status with multiple projects, but that's not what you want in your case. I have tried to explain mappings in simple way here One-to-One mapping.

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)

Categories

Resources