Hibernate/JPA Using parents's identity id on cascade son's id - java

Anyone has been through this using Hibernate / JPA:
In a mapping that does the "many-to-many" with extra fields I do the following assembly:
Father1 -> SonDocument - Since the son has a composite key which is the father's ID + the ID of a Document table.
The problem: The father's id is Identity and I persist the father with a cascade of the children, so at the time of the child's persist it would have to have a "refresh" to get the ID that was just generated in the father. It turns out that this ID is never loaded.
Mapping:
public class Pai{
#OneToMany(cascade={CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH}, fetch = FetchType.LAZY, mappedBy = "pai", orphanRemoval = true)
private Set<FilhoDocumento> documentos;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "nome", length = 30, unique = false, nullable = false)
private String nome;
public void addDocumento(final Documento) {
getDocumentos().add(new FilhoDocumento(this, d));
}
}
Son's Mapping
#Entity
#AttributeOverrides({
#AttributeOverride(name = "paiId", column=#Column(name="...")),
#AttributeOverride(name = "documentoId", column=#Column(name="..."))
})
#IdClass(FilhoDocumentoPK.class)
public class FilhoDocumento implements Serializable {
#Column(name = "impeditivo", nullable = false)
private boolean impeditivo;
#Column(name = "obrigatorioMatriculaPeloAluno", nullable = false)
private boolean obrigatorioMatriculaPeloAluno;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "idPai", referencedColumnName = "id", insertable = false, updatable = false)
private Pai pai;
#Id
private Integer paiId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "IDDocumento", referencedColumnName = "ID", insertable = false, updatable = false)
private Documento documento;
#Id
private Integer documentoId;
....
}
And the PK's mapping class
public class PaiDocumentoMatriculaPK implements Serializable {
#Column(name = "IDPai")
private Integer paiId;
#Column(name = "IDDocumento")
private Integer documentoId;
...
}
Running the following test code or Father's id in the SonDocument table is null at the time of Persist execution on the Parent entity. For this idea, a cascade should take the Id that was just included and use it on the child. It is possible?
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
try {
Documento rg = new Documento("RG");
entityManager.persist(rg);
entityManager.flush();
Session session = entityManager.unwrap(Session.class);
rg = (Documento) session.bySimpleNaturalId(Documento.class)
.load("RG");
Pai hpjp1 = new Pai();
hpjp1.setNome("teste");
hpjp1.addDocumento(rg);
entityManager.persist(hpjp1);
...
shows the error:
Caused by: java.sql.SQLIntegrityConstraintViolationException: Column 'IDPai' cannot be null
Changing the way the parent entity was persisting, first recording and getting the ID and then passing the ID and persisting in the child entities, it worked.
Example:
Documento rg = new Documento("RG");
entityManager.persist(rg);
entityManager.flush();
Session session = entityManager.unwrap(Session.class);
rg = (Documento) session.bySimpleNaturalId(Documento.class)
.load("RG");
final Pai hpjp1 = new Pai();
// hpjp1.setId(1L);
hpjp1.setNome("teste");
hpjp1.addDocumento(rg);
HashSet<FilhoDocumento > docs = new HashSet<FilhoDocumento >(hpjp1.getDocumentos());
entityManager.persist(hpjp1);
entityManager.clear();
entityManager.flush();
for(FilhoDocumento r : docs) {
r.setPai(hpjp1);
r.setPaiId(hpjp1.getId());
r.setDocumentoId(rg.getId());
}
hpjp1.setDocumentos(docs);
entityManager.merge(hpjp1);
thank you all for the tips

Related

How to use Hibernate Check constraint in this case?

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.

OneToMany relationship does not store entity field

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.

How to solve (Java + Spring + JPA + Hibernate Mappings) relationships

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.

hibernate: mappedBy reference an unknown target entity property in oneToMany relationship

I have the following structure in my code:
A user has a favourites list of TvShows, a TvShow has a list of seasons, a season has a list of episodes.
User
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", updatable = false, nullable = false)
private int id;
#OneToMany(mappedBy = "tvshows", fetch = FetchType.EAGER)
private List<TvShow> favourites;
/// everything else removed for clarity
}
TvShow
#Entity
#Table(name = "tvshows")
public class TvShow {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "tvShow_id", updatable = false, nullable = false)
private int id;
#OneToMany(mappedBy = "tvshows", fetch = FetchType.EAGER)
private List<Season> seasons;
/// everything else removed for clarity
}
Season
#Entity
#Table(name = "seasons")
public class Season{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "season_id", updatable = false, nullable = false)
private int id;
#OneToMany(mappedBy = "seasons", fetch = FetchType.EAGER)
private List<Episode> episodes;
/// everything else removed for clarity
}
The app won't start with the following error message:
mappedBy reference an unknown target entity property: com.gcimpoies.project.model.Season.seasons in com.gcimpoies.project.model.TvShow.seasons
Also,
Invocation of init method failed; nested exception is org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: com.gcimpoies.project.model.Season.seasons in com.gcimpoies.project.model.TvShow.seasons
I'm pretty sure I'm just missing the correct attribute names(arguments of mappedBy) but I have no idea what I'm doing wrong.
Thanks in advance!
The name that you have to put inside mappedBy is the id of the class that you are referencing.
Instead of:
#OneToMany(mappedBy = "tvshows", fetch = FetchType.EAGER)
Try:
#OneToMany(mappedBy = "id", fetch = FetchType.EAGER)
You will have to do this for the rest of the mappings as well.

Unable to insert data using hibernate one to one mapping

I have two tables:
ptUSER(userID, name)
ptProteinData(userID, total, goal)
I tried to implement this using Hibernate one to one relationship.
The implementation of those 2 tables are the following:
Implementation of ptUser table (User.java):
#Entity
#Table(name = "ptuser")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userID", unique = true, nullable = false)
int userID;
#Column(name = "name", unique = true, nullable = false, length = 45)
String name;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "ptuser", cascade = CascadeType.ALL)
ProteinData proteinData;
// getters and setters
}
Implementation of ptProteinData table (ProteinData.java):
#Entity
#Table(name = "ptproteindata")
public class ProteinData {
#Id
#GeneratedValue(generator = "generator")
#GenericGenerator(name = "generator", strategy = "foreign", parameters = #Parameter(name = "property", value = "ptuser"))
int userID;
int total;
int goal;
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
User user;
//getter & setter
}
DAO layer implementation is like the following:
public class UserDAO {
public void addUserWithProteinData(String name, int goal, int total) {
Session session = HibernateUtilities.getSessionFactory().openSession();
session.beginTransaction();
User user = new User();
user.setName(name);
ProteinData proteinData = new ProteinData();
proteinData.setGoal(goal);
proteinData.setTotal(total);
user.setProteinData(proteinData);
proteinData.setUser(user);
session.saveOrUpdate(user);
session.getTransaction().commit();
session.close();
}
}
I tried to insert data using the following code:
UserDAO uDAO = new UserDAO();
uDAO.addUserWithProteinData("abc", 100, 10);
But I am unable to insert data using the above codes. Can you tell me where I am doing mistake. When I tried to execute this I am getting the following error:
Exception in thread "main"
java.lang.NullPointerException
at mydomain.UserDAO.addUserWithProteinData(UserDAO.java:43)
at mydomain.UserDAO.main(UserDAO.java:65)
Add the first line to your code, because you need to save proteinData first and then set in user object. Finally, you can save user.
session.saveOrUpdate(proteinData); // you need to save 'proteinData' first
user.setProteinData(proteinData);
proteinData.setUser(user);
session.saveOrUpdate(user);
Also amend these
// On User Class
#Entity
#Table(name = "ptuser")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userID")
int userID;
#Column(name = "name", unique = true, nullable = false, length = 45)
String name;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "ptuser", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
ProteinData proteinData;
}
// On ProteinData
#Entity
#Table(name = "ptproteindata")
public class ProteinData {
#Id
#GeneratedValue(generator = "generator")
#GenericGenerator(name = "generator", strategy = "foreign", parameters = #Parameter(name = "property", value = "ptuser"))
int userID;
int total;
int goal;
#OneToOne(fetch = FetchType.LAZY, mappedBy="ProteinData")
#PrimaryKeyJoinColumn
User user;
}
Add
(optional=false)
#JoinColumn(name="userID", unique=true, nullable=false, updatable=false)
What is the primary key of the second table ProteinData

Categories

Resources