I have a one to many relationship between "code" and "code system" (a code system has many codes.) I implemented my service with the following to save codes for code systems:
service
code = new Code();
code.setCode(valueConcept.getCode());
code.setName(valueConcept.getDisplay_name());
code.setValueSet(valueSet);
code.setCodeSystem(codeSystem);
code.setDateCreated(new Date());
code.setDateUpdated(new Date());
codeRepository.save(code);
The last line of the code is throwing the exception. "Code System" is an object which is created above using a similar JPA repository.
To implement this one to many relationship I have the following two entity classes:
Code.java
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "codeCode_idGenerator")
#Basic(optional = false)
#Column(name = "\"CODE_ID\"", nullable = false, insertable = false, updatable = false)
#SequenceGenerator(allocationSize = 1, name = "codeCode_idGenerator", sequenceName = "crisp_pophealth.application.code_id_seq", schema = "application", catalog = "crisp_pophealth")
public Integer getId() {
return this.id;
}
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY)
#org.hibernate.annotations.Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE })
#Basic(optional = true)
#JoinColumn(name = "\"CODE_SYSTEM_ID\"", nullable = true)
public CodeSystem getCodeSystem() {
return this.codeSystem;
}
CodeSystem.java
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "codeSystemRecord_idGenerator")
#Basic(optional = false)
#Column(name = "\"RECORD_ID\"", nullable = false)
#SequenceGenerator(allocationSize = 1, name = "codeSystemRecord_idGenerator", sequenceName = "crisp_pophealth.application.code_system_id_seq", schema = "application", catalog = "crisp_pophealth")
public Integer getId() {
return this.id;
}
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST,
CascadeType.MERGE }, mappedBy = "codeSystem")
#org.hibernate.annotations.Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE })
#Basic(optional = false)
#Column(name = "\"RECORD_ID\"", nullable = false)
public Set<Code> getCodes() {
return this.codes;
}
The reason the sequences are set the way they are is because the primary key is defined as follows on each postgres table (as an example):
CREATE TABLE application.code ("CODE_ID" integer NOT NULL DEFAULT nextval('application.code_id_seq'::regclass),
What's the best way annotate the classes for this relation to exist? I'm using the sequence strategy AUTO since the database is managing sequence values on creation. I've tried saving unsuccessfully using an entity manager, but I would prefer to have all persistence managed by these JPA repository classes.
Related
I want to use check constraint to verify if there are more students in the subject more than vacancies. These are the entities:
SubjectOffer
#Entity
#SequenceGenerator(name = "SUBJECT_OFFER_SEQ", sequenceName = "SUBJECT_OFFER_SEQ")
#Table(name = "SUBJECT_OFFER", uniqueConstraints = {
#UniqueConstraint(name = "UQ_SUBJECT_OFFER_COURSE_SUBJECT_SEMESTER_CLASS", columnNames = {"COURSE_ID", "SUBJECT_ID", "SEMESTER", "CLASS_NUMBER"})})
#Check(constraints = "COUNT(STUDENT_SUBJECT_ID) <= VACANCIES")
public class SubjectOffer {
#Id
#GeneratedValue(generator = "SUBJECT_OFFER_SEQ")
#Column(name = "SUBJECT_OFFER_ID", nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = CascadeType.ALL)
#JoinColumn(name = "STUDENT_SUBJECT_ID")
private Set<StudentSubject> studentSubjects = new HashSet<>();
//other attributes
#Column(name = "VACANCIES", nullable = false)
private int vacancies;
}
StudentSubject
#Entity
#Table(name = "STUDENT_SUBJECT")
public class StudentSubject {
#EmbeddedId
private StudentSubjectId id = new StudentSubjectId();
#MapsId("studentId")
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "STUDENT_ID", nullable = false)
private Student student;
#MapsId("subjectOfferId")
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "SUBJECT_OFFER_ID", nullable = false)
private SubjectOffer subjectOffer;
#Column(name = "SEMESTER", nullable = false)
private int semester;
#Column(name = "GRADE")
private BigDecimal grade;
}
I also tried column definition in Set #JoinColumn but it didn't work
SQL check constraints only work on a single table. What you want is a so called SQL assertion constraint, but no database implements that. The best you can do is to pre-create rows for the amount of vacancies and just assign students to these rows without ever creating more rows. This way, you can make sure that you only assign as many students as there are vacancies, given that you use optimistic/pessimistic locking when assigning a student.
let's consider two JPA entities A and B :
#Entity
#Table(name = "A")
#SequenceGenerator(name = "seq_a", allocationSize = 50, initialValue = 1)
public class A {
#Id
#GeneratedValue(generator = "seq_a", strategy = GenerationType.SEQUENCE)
#Column(name = "ID", insertable = false, updatable = false, unique = true, nullable = false)
private Long id;
#Column(name = "CODE")
private String code;
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<B> bSet = new HashSet<>();
#Column(name = "CREATED_TIME")
private LocalDateTime createdTime;
//getters + setters
}
#Entity
#Table(name = "B")
#SequenceGenerator(name = "seq_b", allocationSize = 50, initialValue = 1)
public class B {
#Id
#GeneratedValue(generator = "seq_b", strategy = GenerationType.SEQUENCE)
#Column(name = "ID", insertable = false, updatable = false, unique = true, nullable = false)
private Long id;
#Column(name = "SOMETHING")
private String something;
#ManyToOne(optional = false)
#JoinColumn(name = "A_ID", nullable = false, updatable = false)
private A a;
#Column(name = "CREATED_TIME")
private LocalDateTime createdTime;
//getters + setters
}
then consider RestController (springboot context) that have one GET method used for retrieving detail of entity A :
#GetMapping("/{id}")
public ResponseEntity<ADTO> getA(#PathVariable(name = "id", required = true) Long id) {
return aRepository.findById(id)
.map(a -> new ResponseEntity<>(mapper.mapToDomain(a), HttpStatus.OK))
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
method POST used for creating records of A:
#PostMapping
public ResponseEntity<ADTO> addA(#RequestBody #Valid ADTO aDTO) {
return new ResponseEntity<>(mapper.mapToDomain(a.save(mapper.mapToEntity(ADTO))), HttpStatus.CREATED);
}
and PUT method for updating :
#PutMapping
public ResponseEntity<ADTO> updateA(#RequestBody #Valid ADTO aDTO) {
A a = aRepository.findById(aDTO.getId()).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
ADTO aDTOfound = mapper.mapToDomain(a);
BeanUtils.copyProperties(aDTO, aDTOfound);
return new ResponseEntity<>(mapper.mapToDomain(aRepository.save(mapper.mapToEntity(aDTOfound), HttpStatus.OK)));
}
then let's say that, createdTime attribute is updated everytime the entity is persisted (including created - updating createdTime attribute is done under the hood ...).
Then let's consider scenario, where two users at the same time are retrieving detail of the same entity A (id 1). If user X update the detail from the retrieved content via PUT method, is there any way how to avoid user Y to update the same entity with old content (notice that the createdTime attribute is updated on record with id 1) ? I know that one possible solution, is to make sure that the retrieved createdTime and one from aDTO in update method is the same, but is there any "more" standard solution for this problem ? For example, how to avoid updating entity A (if it was updated previously with USER 1) but let update the childs in Bset which ones for example were not updated by user 1 ...
This is typical problem statement of Optimistic Locking
Optimistic locking is a mechanism that prevents an application from
being affected by the "lost update" phenomenon in a concurrent
environment while allowing some high degree of concurrency at the same
time.
I will solve this problem using #Version, add #Version field in your entity like below
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(name = "student_name")
private String studentName;
#Column(name = "roll_number")
private String rollNumber;
#Column(name = "version")
#Version
private Long version;
}
In above case When we create an entity for the first time default version value will be zero
On update, the field annotated with #Version will be incremented and subsequent update will same version will fail with OptimisticLockException
The #Version annotation in hibernate is used for Optimistic locking while performing update operation. For more details you can visit https://vladmihalcea.com/jpa-entity-version-property-hibernate/
When saving my entities, child entities that work through the #OneToMany relationship are not saved to their tables. I can’t understand what’s the matter.
Employee:
#Entity
#Table(name = "EMPLOYEE", schema = PUBLIC)
public class Employee {
private String name;
private String lastname;
#OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, orphanRemoval = true)
List<EmployeePhoneNumber> employeePhoneNumbers = new ArrayList<>();
}
EmployeePhoneNumber:
#Entity
#Table(name = "EMPLOYEE_PHONES", schema = PUBLIC)
public class EmployeePhoneNumber {
#Id
#SequenceGenerator(allocationSize = 1, name = "SEQ_EMPLOYEE_PHONES", schema = PUBLIC,
sequenceName = "EMPLOYEE_PHONES_ID_SEQ")
#GeneratedValue(generator = "SEQ_EMPLOYEE_PHONES", strategy = GenerationType.SEQUENCE)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#ManyToOne
#JoinColumn(name = "employee_id", referencedColumnName = "id",
nullable = false, insertable = false, updatable = false)
private Employee employee;
#Column(name = "PHONE_NUMBER", unique = true, nullable = false)
private String phoneNumber;
#Enumerated(EnumType.STRING)
#Column(name = "NUMBER_TYPE", nullable = false)
private PhoneNumberType phoneNumberType;
}
How I set those fields and then save the entity:
EmployeePhoneNumber workPhone = new EmployeePhoneNumber();
workPhone.setPhoneNumber(workPhone);
workPhone.setPhoneNumberType(PhoneNumberType.WORK_PHONE);
EmployeePhoneNumber mobilePhone = new EmployeePhoneNumber();
mobilePhone.setPhoneNumber(mobilePhone);
mobilePhone.setPhoneNumberType(PhoneNumberType.MOBILE_PHONE);
EmployeePhoneNumber corporatePhone = new EmployeePhoneNumber();
corporatePhone.setPhoneNumber(corporatePhoneNumber);
corporatePhone.setPhoneNumberType(PhoneNumberType.CORPORATE_PHONE);
List<EmployeePhoneNumber> employeePhoneNumbers = employee.getEmployeePhoneNumbers();
employeePhoneNumbers.add(workPhone);
employeePhoneNumbers.add(mobilePhone);
employeePhoneNumbers.add(corporatePhone);
employee.setEmployeePhoneNumbers(employeePhoneNumbers);
employeeRepository.save(employee);
Upon completion of the method, I do not have a single error, everything works out correctly, only the tables are not filled - why?
You must also set the Employee reference in EmployeePhoneNumber because Hibernate will use this to save it.
workPhone.setEmployee(employee);
mobilePhone.setEmployee(employee);
corporatePhone.setEmployee(employee);
The best solution would be to create an addEmployeePhoneNumber method on the Employee like this:
public void addEmployeePhoneNumber(EmployeePhoneNumber phoneNumber) {
phoneNumber.setEmployee(this);
employeePhoneNumbers.add(phoneNumber);
}
That way you will not forget to set both sides of the relationship.
I have read many topics about mapping with JPA + Hibernate, but after trying several things I can not get the expected result.
I have declared all my unidirectional relationships since I do not see the need to bidirect them
My objects to map are:
Client has a Country and a list of addresses.
#Table(name = "Client")
#Entity
public class Client
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
#GenericGenerator(name = "native", strategy = "native")
private Long id;
#OneToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST })
#JoinColumn(name="country_id", unique = false, /*nullable = false,*/ insertable = true, updatable = false, foreignKey = #ForeignKey(name = "country_fk0"))
private Country country;
#OneToMany(cascade = { CascadeType.PERSIST }, orphanRemoval = true)
#JoinColumn(name = "address_id",/* nullable = false,*/ foreignKey = #ForeignKey(name = "address_fk0"))
private List<Address> address;
//GETTERS / SETTERS
}
then I will evaluate the client and save it in a new ClientProcessed table, which will reference the Addresses and Country objects stored in my Client Object.
#Table(name = "ProcessedClient")
#Entity
public class ProcessedClient
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
#GenericGenerator(name = "native", strategy = "native")
private Long id;
#OneToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST })
#JoinColumn(name="client_evaluation_id", unique = false, /*nullable = false,*/ insertable = true, updatable = false, foreignKey = #ForeignKey(name = "evaluation_fk0"))
private ClientEvaluation evaluation;
#OneToOne(cascade = { CascadeType.MERGE })
#JoinColumn(name="country_id", unique = false, /*nullable = false,*/, foreignKey = #ForeignKey(name = "country_fk1"))
private Country country;
#OneToMany(cascade = { CascadeType.MERGE}, orphanRemoval = true)
#JoinColumn(name = "address_id",/* nullable = false,*/ foreignKey = #ForeignKey(name = "address_fk2"))
private List<Address> addresses;
//GETTERS / SETTERS
}
So then when i do that:
Country country = new Country();
country.setId(1l); // (DB ID)
// I do the same with addresses
ProcessedClient processedClient = new ProcessedClient();
processedClient.setAddresses(addresses);
processedClient.setCountry(country);
this.getDao().save(processedClient);
Result:
org.hibernate.TransientPropertyValueException: object references an
unsaved transient instance - save the transient instance before
flushing
Thx
From documentation:
Hibernate defines and supports the following object states:
Transient - an object is transient if it has just been instantiated using the new operator, and it is not associated with a Hibernate Session . ...
Persistent - a persistent instance has a representation in the database and an identifier value.
You should load country from database by id and then set it to ProcessedClient.
I am trying to create a ManyToMany relation between DocumentModels, with an additionnal information in the relation (dosIndex)
#Entity
#Table(name = "T_DOCUMENT_MODELS_DMO")
public class TDocumentModelsDmo extends fr.axigate.nx.frontend.server.common.entity.ValidityPeriodEntity implements Serializable
{
#Id
#SequenceGenerator(name = "T_DOCUMENT_MODELS_DMO_DMOID_GENERATOR", sequenceName = "T_DMO_ID_SEQ")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "T_DOCUMENT_MODELS_DMO_DMOID_GENERATOR")
#Column(name = "DMO_ID", precision = 22)
private Long dmoId;
//Other unrelated members, no reference to TjDocumentSourcesDos
//constructors, getters and setters without annotations
}
#Entity
#Table(name = "TJ_DOCUMENT_SOURCES_DOS")
public class TjDocumentSourcesDos implements Serializable
{
#Column(name = "DOS_INDEX", nullable = false, precision = 22)
private long dosIndex; //the additionnal info on the relation
#EmbeddedId
private TjDocumentSourcesDosPK id = new TjDocumentSourcesDosPK();
#ManyToOne
#MapsId("dosParentId")
#JoinColumn(name = "DOS_PARENT_ID", nullable = false, insertable = false, updatable = false)
private TDocumentModelsDmo TDocumentModelsDmoParent;
#ManyToOne
#MapsId("dosSourceId")
#JoinColumn(name = "DOS_SOURCE_ID", nullable = false, insertable = false, updatable = false)
private TDocumentModelsDmo TDocumentModelsDmoSource;
//constructors, getters and setters without annotations
}
#Embeddable
public class TjDocumentSourcesDosPK implements Serializable
{
#Column(name = "DOS_PARENT_ID", nullable = false, precision = 22)
private Long dosParentId;
#Column(name = "DOS_SOURCE_ID", nullable = false, precision = 22)
private Long dosSourceId;
//constructors, getters and setters without annotations
//hashCode and equals implemented
}
I can insert datas in both tables, but when I try to request it using an entityManager, i get something strange :
Query query = entityManager.createQuery("SELECT dos.TDocumentModelsDmoSource FROM TDocumentModelsDmo AS dmo, TjDocumentSourcesDos as dos WHERE dmo.dmoId = :modelId AND dos.TDocumentModelsDmoParent = dmo");
query.setParameter("modelId", someData);
ArrayList<TjDocumentSourcesDos> dosList = (ArrayList<TjDocumentSourcesDos>) query.getResultList();
will work, while the following will throw an exception : QuerySyntaxException: dos.TDocumentModelsDmoSource is not mapped
Query query = entityManager.createQuery("SELECT sources FROM TDocumentModelsDmo AS dmo, TjDocumentSourcesDos as dos, dos.TDocumentModelsDmoSource AS sources WHERE dmo.dmoId = :modelId AND dos.TDocumentModelsDmoParent = dmo");
query.setParameter("modelId", someData);
ArrayList<TjDocumentSourcesDos> dosList = (ArrayList<TjDocumentSourcesDos>) query.getResultList();
This prevents me from doing more complicated requests where I would use my sources models in the WHERE condition.
I tried adding a referencedColumnName = "DMO_ID" in both my JoinColumn annotations, but I still get the same error