How to search mySql database table by foreign key using JPA? - java

I have 2 classes (BusinessAccount and Projects (shown below) that are mapped to a MySql database) where a 1:M relationship exists between BusinessAccounts and Projects. I am successfully inserting data to the database but am having a problem when it comes to querying the database. The problem that I am having is that I have no getter or setter for the foreign key, 'contractor_id' in the Projects class. The query that I want to carry out is to return the list of the names of all projects for a given BusinessAccount, by searching by the foreign key reference in the Projects table. I can do this no problem in mySQL but as there is no reference to the contractor_id as a java entity in the Projects class, I'm not sure how to do this search from within my java class. (Note: I tried to declare the foreign key along with getters and setters in the Projects class but as I have these mapped by the 1:Many relationship in the class already, it wouldn't compile as they were flagged as duplicate entities.) I'm sure it's something obvious that I'm missing but any help is much appreciated!
public List<Projects> getProjectList() {
factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
EntityManager em = factory.createEntityManager();
List<Projects> projectList = new ArrayList<Projects>();
em.getTransaction().begin();
String sessionEmail=Util.getEmail();
Query myQuery = em.createQuery("SELECT u FROM BusinessAccount u WHERE u.email=:email");
myQuery.setParameter("email", sessionEmail);
List<BusinessAccount> userList=myQuery.getResultList();
BusinessAccount account =userList.get(0);
Query myQuery2 = em.createQuery("SELECT distinct p.* FROM BusinessAccount u "
+ "INNER JOIN Projects p ON p.contractor_id=:userID");
/*Note p.contractor_id above refers to the entity in the
mysql database (and won't work obviously), I want to refer
to it's java equivalent but am not sure how to do that*/
myQuery2.setParameter("userID", account.getId());
projectList=myQuery2.getResultList();
em.getTransaction().commit();
em.close();
return projectList;
}
#Entity
#Table(name = "business_accounts")
public class BusinessAccount {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "first_name")
private String firstName;
#Column(name = "surname")
private String surname;
#OneToMany(mappedBy = "businessAccount", fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
private List<Projects> projects;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSurname() {
return surname;
}
public List<Projects> getProjects()
{
if (projects == null)
{
projects = new ArrayList<Projects>();
}
return projects;
}
public void setProjects(List<Projects> projects)
{
this.projects = projects;
}
}
#Entity
#Table(name = "projects")
public class Projects {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int project_id;
#Column(name = "project_name")
private String projectName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({ #JoinColumn(name = "contractor_id", referencedColumnName="id") })
private BusinessAccount businessAccount;
public BusinessAccount getBusinessAccount() {
if (businessAccount == null) {
businessAccount = new BusinessAccount();
}
return businessAccount;
}
public void setBusinessAccount(BusinessAccount businessAccount) {
this.businessAccount = businessAccount;
}
public int getProject_id() {
return project_id;
}
public void setProject_id(int project_id) {
this.project_id = project_id;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
}

The JPA query would be something like (you need to use the relation property, but no need for the foreign key itself - please try, it may need some tweaking):
SELECT p FROM BusinessAccount u, IN(u.projects) p WHERE u.id=:userId
But do you really need the query? You can get the related projects from the property:
BusinessAccount account = ...
List<Projects> projectList = account.getProjects();

Try this:
Query myQuery2 = em.createQuery("SELECT distinct p.* FROM BusinessAccount u "
+ "INNER JOIN Projects p ON p.businessAccount=:businessAccount");
myQuery2.setParameter("businessAccount", account);

Related

Need help turning standard SQL query to JPA Criteria Query

I have two tables:
Employee
id
firstName
lastName
.
.
.
Training
id
employeeId
trainingName
trainingSuspsnseDate
trainingComplete
When I perform a standard SQL query in MySQL Workbench, it looks like this:
SELECT e.id, e.firstName, e.lastName, t.trainingName, t.trainingSuspenseDate, t.trainingComplete
FROM Employee e
JOIN Training t on t.employeeId = e.id
WHERE t.trainingSuspenseDate < CURDATE()
order by t.trainingSuspenseDate;
Now, I want to create a criteria query of the same SQL query, but I am having trouble with the join. This is what I have tried based on my googling:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> employeeQuery = builder.createQuery(Employee.class);
Root<Employee> employee = employeeQuery.from(Employee.class);
Join<Employee, Training> training = employee.join(Employee_.ID);
employeeQuery.select(builder.construct(Employee.class,
employee.get(Employee_.ID),
employee.get(Employee_.firstName),
employee.get(Employee_.lastName),
training.get(Training_trainingName),
training.get(Training_trainingSuspsnseDate),
training.get(Training_trainingComplete)));
However, I am getting the error:
incompatible types: inference variable Y has incompatible equality constraints Templates,Integer where Y,X are type-variables:
Y extends Object declared in method <Y>join(SingularAttribute<? super X,Y>)
X extends Object declared in interface From
I have tried other permutations of the JOIN, but I get different errors. I cannot seem to find the exact "secret" to creating this query.
Join<Employee, Training> training = training.join(Training_.employeeId);
or
Join<Employee, Training> training = training.join(Training_.employeeId).join(Employee_.ID);
or
Join<Training, Employee> training = training.join(Training_.employeeId);
or
Join<Training, Employee> training = training.join(Training_.employeeId).join(Employee_.ID);
or
.
.
.
EDIT: Added my model classes
Employee.java
#Entity
#Table(name = "employee")
#XmlRootElement
#NamedQueries(
{
#NamedQuery(name = "Employee.findAll", query = "SELECT e FROM Employee e"),
#NamedQuery(name = "Employee.deleteAll", query = "DELETE FROM Employee e"),
#NamedQuery(name = "Employee.countAll", query = "SELECT COUNT(e.ID) FROM Employee e")
})
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#GeneratedValue
#Column(name = "id")
private Integer ID;
#Basic(optional = true)
#Column(name = "name_first")
private String firstName;
#Basic(optional = true)
#Column(name = "name_last")
private String lastName;
#Basic(optional = true)
#Column(name = "created_date")
private String employeeDate;
#Basic(optional = true)
#Column(name = "personal_type")
private String personnelType;
public Employee() {
ID = 0;
}
public Employee(Integer id) {
this.ID = id;
}
public Integer getID() {
return ID;
}
public void setID(Integer id) {
this.ID = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmployeeDate() {
return employeeDate;
}
public void setEmployeeDate(String employeeDate) {
this.employeeDate = employeeDate;
}
public String getPersonnelType() {
return personnelType;
}
public void setPersonnelType(String personnelType) {
this.personnelType = personnelType;
}
}
Training.java
#Entity
#Table(name = "training")
#XmlRootElement
#NamedQueries(
{
#NamedQuery(name = "Training.findAll", query = "SELECT t FROM Training t"),
#NamedQuery(name = "Training.deleteAll", query = "DELETE FROM Training t"),
#NamedQuery(name = "Training.countAll", query = "SELECT COUNT(t.ID) FROM Training t")
})
public class Training implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#GeneratedValue
#Column(name = "ID")
private Integer ID;
#Basic(optional = false)
#Column(name = "employee_id")
private String employeeId;
#Basic(optional = false)
#Column(name = "training_name")
private String trainingName;
#Basic(optional = false)
#Column(name = "training_suspense_date")
private Date trainingSuspenseDate;
#Basic(optional = false)
#Column(name = "training_complete")
private Boolean trainingComplete;
public Integer getID() {
return ID;
}
public void setID(Integer ID) {
this.ID = ID;
}
public String getEmployeeId() {
return employeeId;
}
public void setEmployeeId(String employeeId) {
this.employeeId = employeeId;
}
public void setTrainingName(String trainingName) {
this.trainingName = trainingName;
}
public String getTrainingName() {
return trainingName;
}
public void setTrainingSuspenseDate(Date trainingSuspsenseDate) {
this.trainingSuspsenseDate = trainingSuspsenseDate;
}
public Date getTrainingSuspenseDate() {
return trainingSuspsenseDate;
}
public void setTrainingComplete(Boolean trainingComplete) {
this.trainingComplete = trainingComplete;
}
public Boolean getTrainingComplete() {
return trainingComplete;
}
}
I can figure out that You have a meta-model generated for your query.
So the best way will be to extends your entity definition as follow:
You have to add mapping in your Training class:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "employeeId")
private Employee employee;
Then in your Employee class add oposite reference:
#OneToMany(mappedBy = "employee")
private Set<Training> trainings = new HashSet<>();
Then change your criteria query to:
Join<Employee, Training> training = employee.join(Employee_.trainings);
You can try cross join. Native sql is a bit differerent but the result is as axpected
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> employeeQuery = builder.createQuery(Employee.class);
Root<Employee> employee = employeeQuery.from(Employee.class);
Root<Employee> training= employeeQuery.from(Training.class);
employeeQuery.select(builder.construct(Employee.class,
employee.get(Employee_.ID),
employee.get(Employee_.firstName),
employee.get(Employee_.lastName),
training.get(Training_.trainingName),
training.get(Training_.trainingSuspsnseDate),
training.get(Training_.trainingComplete)))
.where(builder.equal(employee.get(Employee_.ID), training.get(Training_.employeeId)));
Looking at your model classes, the entities are not related directly (even though the employeeId in Training is supposed to be a foreign key, it is not defined as such in the entity relationship. So, if you wish to work with the existing entities, without changing them, you would need the following -
A POJO (for example EmpRes) which maps the attributes as per the select clause. criteriaQuery should be initialized on this POJO as -
CriteriaQuery<EmpRes> criteriaQuery = builder
.createQuery(EmpRes.class);
As the entities are unrelated, the generated query will have a cross join.
The code would look like -
criteriaQuery.select(builder.construct(EmpRes.class, employee
.get(Employee_.getAttribute("ID").getName()), employee
.get(Employee_.getAttribute("firstName").getName()), employee
.get(Employee_.getAttribute("lastName").getName()), training
.get(Training_.getAttribute("trainingName").getName()),
training.get(Training_.getAttribute("trainingSuspenseDate")
.getName()), training.get(Training_.getAttribute(
"trainingComplete").getName())));
criteriaQuery.where(builder.equal(employee.get("ID"), training.get("employeeId")));
List<EmpRes> employees = entityManager.createQuery(criteriaQuery).getResultList();
However, if the entities can be changed (as should be the ideal design), an Employee has Training(s). So, a #OneToMany relationship between Employee and Training model classes should be defined as follows -
Employee.java
#OneToMany(mappedBy="employee")
private Set<Training> trainings = new HashSet<>();
Training.java
#ManyToOne
#JoinColumn(name = "employeeId")
private Employee employee;
CriteriaQuery related code -
Join<Employee, Training> trainingJoin = employee.join(Employee_.getAttribute("trainings").getName());
criteriaQuery.select(builder.construct(EmpRes.class, employee
.get(Employee_.getAttribute("ID").getName()), employee
.get(Employee_.getAttribute("firstName").getName()), employee
.get(Employee_.getAttribute("lastName").getName()),
trainingJoin.get(Training_.getAttribute("trainingName")
.getName()), trainingJoin.get(Training_.getAttribute(
"trainingSuspenseDate").getName()), trainingJoin
.get(Training_.getAttribute("trainingComplete")
.getName())));
You can then add the additional where clause based on your requirements.
A good reference to Criteria API is here.
This error message - incompatible types: inference variable Y has incompatible equality constraints - is an indication you need to carefully review DATA TYPES of the columns you are joining. The should be same data types on both sides of the = for performance and high speed comparisons.

How to find the reference when key to the find(Object.Class, {CompositeKey}) method is a composite key?

How to find a reference when we have composite key(two or more columns) to pass on as second parameter to the JPA entityManager.find(Object.class, compositeKey)?
My try-
I have created an Arraylist and added the values forming compositeKey it and then passing this list to the find method.
For ex: In my situation, userid and projectid together is the key for the UserProject table and these two have been added to the arraylist named as list, which will be passed as a second parameter to the entityManager find method as shown below:
List<Integer> list = new ArrayList<Integer>();
list.add(userProjectDO.getUserid());
list.add(userProjectDO.getProjectid());
UserProject userProject = em.find(UserProject.class,list);
But this always return as null, even though userid and projectId exists on the table. Has anyone been into similar issue? Solution?
JPA's EntityManager#find doesn't accept arrays as key but Object. Since you are talking about composite key you should implement your key in a separate class which will represent the composite key by listing all the key separate properties. You can achieve this using EmbeddedId for instance.
For example:
You should define the composite key class and annotate with #Embeddable:
public class UserProjectKey implements Serializable{
private String userId;
private String projectId;
//constructors, getters, setters
}
and use it as #EmbeddedId in your entity.
To search by the key you can do:
UserProjectKey key = new UserProjectKey("userIdExample", "projectIdExample");
em.find(UserProject.class, key);
I have found another approach i.e. writing namedQuery to search the table. Posting the implementation just in case it helps anyone.
final Query query = em.createNamedQuery("UserProject.findByAll");
UserProject Entity class:
#Entity
#Table(name = "userproject", schema = "public")
#NamedQueries({ #NamedQuery(name = "UserProject.findByAll", query = "SELECT a FROM UserProject a where a.userid = :userid and a.projectid = :projectid"),
#NamedQuery(name = "UserProject.findByUserId", query = "SELECT a FROM UserProject a where a.userid = :userid"),
#NamedQuery(name = "UserProject.findById", query = "SELECT a FROM UserProject a where a.id = :id" )})
public class UserProject implements Serializable {
private static final long serialVersionUID = 1L;
#Id #GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "userid")
private Integer userid;
#Column(name = "projectid")
private Integer projectid;
#Column(name = "created")
private Timestamp created;
#Column(name = "modified")
private Timestamp modified;
#Column(name = "modifiedbyid")
private Integer modifiedbyid;
#Column(name = "role")
private String role;
public Integer getId() {
return id;
}
public void setId(final Integer id) {
this.id = id;
}
public Integer getUserid() {
return userid;
}
public void setUserid(final Integer userid) {
this.userid = userid;
}
public void setProjectid(final Integer projectid) {
this.projectid = projectid;
}
public Timestamp getCreated() {
return created;
}
public void setCreated(final Timestamp created) {
this.created = created;
}
public Timestamp getModified() {
return modified;
}
public void setModified(final Timestamp modified) {
this.modified = modified;
}
public Integer getModifiedbyid() {
return modifiedbyid;
}
public void setModifiedbyid(final Integer modifiedbyid) {
this.modifiedbyid = modifiedbyid;
}
public String getRole() {
return role;
}
public void setRole(final String role) {
this.role = role;
}
}
And finally set the query parameters i.e. compositeKey values(userid,projectid) as :
final Query query = em.createNamedQuery("UserProject.findByAll");
query.setParameter("userid",userProjectDO.getUserid());
query.setParameter("projectid",userProjectDO.getProjectid());
List<UserProject> userProjectList = query.getResultList();
userProjectList would contain the row which matches the compositeKey (userId,projectId)
One advantage I see with this approach is that I can write N number of named queries inside the entity class as per the need/requirement. For ex: If we need to work on a view created out of this table. It can be easily achieved by first creating the view and then write another named query to work on it.

Persisting a #OneToOne child entity with #MapsId throws "error:detached entity passed to persist" in Hibernate

I read https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/.
I tried suggestion config like(using spring data JPA,hibernate 5.0 as vendor ):
public class PaperSubjectType{
#Id
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private PaperSetting paperSetting;
..
}
class PaperSetting{
#Id
#GeneratedValue
private Long id;
..
}
first I tried the example:
PaperSetting paperSettingInDb = paperSettingRepository.findOne(1);
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setSubjectCode("91");
paperSubjectType.setPaperSetting(paperSettingInDb);
paperSubjectTypeRepository.save(paperSubjectType);
error:detached entity passed to persist:PaperSetting.
it seems hibernate take PaperSetting as detached when cascade
2 if I want to create both PaperSubjectType and PaperSetting together,do I need to do this:
PaperSetting paperSetting = new PaperSetting();
paperSetting.setxx;
PaperSetting paperSettingInDbNew = paperSettingRepository.save(paperSetting);
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setPaperSetting(paperSettingInDbNew);
paperSubjectTypeRepository.save(paperSubjectType);
or I should use bidirectional in this situation?
thank you!
I think you may have forgotten to wrap the logic in a #Transactional block
#Transactional
PaperSetting paperSettingInDb = paperSettingRepository.findOne(1);
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setSubjectCode("91");
paperSubjectType.setPaperSetting(paperSettingInDb);
paperSubjectTypeRepository.save(paperSubjectType);
without that crudRepository.findOne() will open it's own short lived transaction so when you get the return of findOne() the entity is already detached, hence the error
I tried it Hibernate 5.2 and it works like a charm.
Assuming you have these entities:
#Entity(name = "Person")
public static class Person {
#Id
#GeneratedValue
private Long id;
#NaturalId
private String registrationNumber;
public Person() {}
public Person(String registrationNumber) {
this.registrationNumber = registrationNumber;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRegistrationNumber() {
return registrationNumber;
}
}
#Entity(name = "PersonDetails")
public static class PersonDetails {
#Id
private Long id;
private String nickName;
#OneToOne
#MapsId
private Person person;
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
And this data access logic:
Person _person = doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person( "ABC-123" );
entityManager.persist( person );
return person;
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = entityManager.find( Person.class, _person.getId() );
PersonDetails personDetails = new PersonDetails();
personDetails.setNickName( "John Doe" );
personDetails.setPerson( person );
entityManager.persist( personDetails );
} );
The test passes just fine in Hibernate ORM.
Maybe it was a bug in 5.0 that got fixed, so you are better of upgrading.
1) Add cascading option:
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#MapsId
private PaperSetting paperSetting;
2) Having that in place, you can save only PaperSubjectType while creating both entities anew:
PaperSetting paperSetting = new PaperSetting();
paperSetting.setxx;
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setPaperSetting(paperSettingInDbNew);
paperSubjectTypeRepository.save(paperSubjectType);

Hibernate ManyToMany deleted object would be re-saved by cascade

For some reason I can't delete an object that belongs to a many to many relationship. I get the following error:
Exception in thread "main" org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [edu.cs157b.hibernate.AppointmentRequest#11]
at org.hibernate.internal.SessionImpl.forceFlush(SessionImpl.java:1232)
Here are my three classes that map the many to many relationship. Essentially, Doctor has many Patients through AppointmentRequest & vice versa. Here are the classes
Doctor
package edu.cs157b.hibernate;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.*;
#Entity
#Table(name="DOCTOR_INFO")
#NamedQueries (
{
#NamedQuery(name = "Doctor.getAll", query = "from Doctor"),
#NamedQuery(name = "Doctor.findByName", query = "from Doctor where name = :name")
}
)
public class Doctor implements Person {
private int id;
private String name;
private Specialty specialty;
private List<AppointmentRequest> appointmentRequests = new ArrayList<AppointmentRequest>();
#Id
#GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Column(unique=true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#ManyToOne (fetch = FetchType.EAGER, cascade= CascadeType.PERSIST)
#JoinColumn(name="specialty_id")
public Specialty getSpecialty() {
return specialty;
}
public void setSpecialty(Specialty specialty) {
this.specialty = specialty;
}
#OneToMany(mappedBy="doctor", targetEntity = AppointmentRequest.class,
fetch=FetchType.EAGER, orphanRemoval=true, cascade= CascadeType.ALL)
public List<AppointmentRequest> getAppointmentRequests() {
return this.appointmentRequests;
}
public void setAppointmentRequests(List<AppointmentRequest> appointmentRequests) {
this.appointmentRequests = appointmentRequests;
}
#Transient
public List<Patient> getPatients() {
List<Patient> patients = new ArrayList<Patient>();
for(AppointmentRequest appointment:appointmentRequests) {
patients.add(appointment.getPatient());
}
return patients;
}
}
Patient
package edu.cs157b.hibernate;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.*;
#Entity
#Table(name="PATIENT_INFO")
#NamedQueries (
{
#NamedQuery(name = "Patient.getAll", query = "from Patient"),
#NamedQuery(name = "Patient.findByName", query = "from Patient where name = :name")
}
)
public class Patient implements Person {
private int id;
private String name;
private String medical_record;
private List<AppointmentRequest> appointmentRequests = new ArrayList<AppointmentRequest>();
public String getMedical_record() {
return medical_record;
}
public void setMedical_record(String medical_record) {
this.medical_record = medical_record;
}
#Id
#GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Column(unique=true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(mappedBy="patient", targetEntity = AppointmentRequest.class,
fetch=FetchType.EAGER, orphanRemoval=true, cascade= CascadeType.ALL)
public List<AppointmentRequest> getAppointmentRequests() {
return this.appointmentRequests;
}
public void setAppointmentRequests(List<AppointmentRequest> appointmentRequests) {
this.appointmentRequests = appointmentRequests;
}
#Transient
public List<Doctor> getDoctors() {
List<Doctor> doctors = new ArrayList<Doctor>();
for(AppointmentRequest appointment:appointmentRequests) {
doctors.add(appointment.getDoctor());
}
return doctors;
}
}
ApppointmentRequest
package edu.cs157b.hibernate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.TimeZone;
import javax.persistence.*;
import org.hibernate.annotations.Type;
import java.util.List;
#Entity
#Table(name="APPOINTMENT_REQUEST")
#NamedQueries (
{
#NamedQuery(name = "AppointmentRequest.getAll", query = "from AppointmentRequest"),
#NamedQuery(name = "AppointmentRequest.findByDoctorId", query = "from AppointmentRequest where doctor_id = :doctor_id"),
#NamedQuery(name = "AppointmentRequest.findByPatientId", query = "from AppointmentRequest where patient_id = :patient_id"),
#NamedQuery(name = "AppointmentRequest.findByID", query = "from AppointmentRequest where id = :id")
}
)
public class AppointmentRequest {
private int id;
private Doctor doctor;
private Patient patient;
private boolean fulfilled = false;
private Calendar time;
private final SimpleDateFormat timestampFormat = new SimpleDateFormat("MM/dd/yyyy h a");
public Calendar getTime() {
return time;
}
#Transient
public String getFormattedTime() {
String result = timestampFormat.format(time.getTime());
return result;
}
public void setTime(Calendar time) {
this.time = time;
}
public boolean isFulfilled() {
return fulfilled;
}
public void setFulfilled(boolean fulfilled) {
this.fulfilled = fulfilled;
}
#Id
#GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#ManyToOne (fetch = FetchType.EAGER, cascade= CascadeType.PERSIST)
#JoinColumn(name="doctor_id")
public Doctor getDoctor() {
return doctor;
}
public void setDoctor(Doctor doctor) {
this.doctor = doctor;
}
#ManyToOne (fetch = FetchType.EAGER, cascade= CascadeType.PERSIST)
#JoinColumn(name="patient_id")
public Patient getPatient() {
return patient;
}
public void setPatient(Patient patient) {
this.patient = patient;
}
}
Doctor Delete Method
public void deleteDoctor(String doctor_name) {
Session session = sessionFactory.openSession();
Doctor doctor = new Doctor();
try {
session.beginTransaction();
Query query = session.getNamedQuery("Doctor.findByName");
query.setString("name", doctor_name);
doctor = (Doctor) query.uniqueResult();
if(doctor == null) {
throw new NullPointerException();
}
List<AppointmentRequest> appointments = doctor.getAppointmentRequests();
for(AppointmentRequest appointment:appointments) {
appointment.setDoctor(null);
}
session.delete(doctor);
session.getTransaction().commit();
}
finally {
session.close();
}
}
What this exception really means is you are telling Hibernate to remove object from database but at the same time this object still exist (that means still exist in java or database) in mapped collection via Persistent entity which has CascadeType.PERSIST annotated over it.
It's like having something tied through elastic rubber on the window and then poke it hoping it will drop. Hibernate is smart it is saving you from doing meaningless stuff, it tells you what to do
deleted object would be re-saved
by cascade (remove deleted object from associations)
Sine you are doing appointment.setDoctor(null); it will remove object from collection (only in java as you are not explicitly or implicitly updating appointment).You have CascadeType.PERSIST on doctor that means when hibernate is going to commit the transaction it will find that appointment has association to doctor you just deleted that means if you remove that doctor from table, hibernate has to go and create same doctor as you have not told him to make appropriate changes in appointment as he follows all the entity rules set by you. Since hibernate is smart he knows this and he will throw a exception for you saying don't be an oxymoron and do the right thing.
Now there are more than one solution that I can think of here
Use cascade={CascadeType.PERSIST,CascadeType.REMOVE} or cascade=CascadeType.ALL on getDoctor() in AppointmentRequest
As mentioned in hibernate document here
It doesn't usually make sense to enable cascade on a #ManyToOne or
#ManyToMany association. Cascade is often useful for #OneToOne and
#OneToMany associations.
remove cascade from getDoctor
Since you have FetchType.EAGER on getDoctor() with cascade specified it is little complicated for me interpret the behaviour of hibernate but in this questions they have solved by using FetchType.LAZY am not sure if it will work out for you.
You can do session.saveOrUpdate(appointment) on all the AppointmentRequest which has this doctor and then go for session.delete(doctor);
Hope you this would solve your problem.

Hibernate - OneToMany - Several Columns

I have those 2 tables Teacher and Contact, a teacher can have x Contacts. So here we are looking at a #OneToMany association.
Tables Structure:
User [userid, username, email,...]
Contact [contactid, contactname, ref, reftype,...]
I want to load from my User Class all the user's contacts. To do that I would do a query like
Select * from contact as c WHERE c.ref=8240 AND c.reftype='T';
8240 being a random userid and reftype T being for Teacher. As this contact table is used as well for school contacts and/or anyother type of customer we could have. The problem is I have no idea how to do this with Hibernate. Should I use embedbedId? Or a JoinColumns?
What I have done so far is to link my teacher to contacts having contact.ref=teacher.teacherid but what I want is :
contact.ref=teacher.teacherid AND contact.reftype='T'
How do I do that?
Here is my code
Teacher.class
private Integer teacherid;
private Set<Contact> contact;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "teacherid", unique = true, nullable = false)
public Integer getTeacherId() {
return teacherid;
}
#OneToMany(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name="ref"),
})
public Set<Contact> getContact() {
return contact;
}
public void setContact(Set<Contact> contact) {
this.contact = contact;
}
Contact.class
#Entity
#Table(name = "contact")
public class Contact implements java.io.Serializable {
private Integer contactid;
private String contactname;
private String contacttype;
private String reftype;
private int ref;
/*private Teacher teacher;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "ref"),
#JoinColumn(name = "reftype")
})
public Teacher getTeacher() {
return teacher;
}
public void setTeacher (Teacher teacher) {
this.teacher= teacher;
}
*/
private Set<ContactItem> contactItems;
private Set<ContactAddress> contactAddressess;
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="contactid")
public Set<ContactItem> getContactItems(){
return contactItems;
}
public void setContactItems(Set<ContactItem> contactItems) {
this.contactItems = contactItems;
}
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="contactid")
public Set<ContactAddress> getContactAddressess(){
return contactAddressess;
}
public void setContactAddressess(Set<ContactAddress> contactAddressess) {
this.contactAddressess = contactAddressess;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "contactid", unique = true, nullable = false)
public Integer getContactid() {
return this.contactid;
}
public void setContactid(Integer contactid) {
this.contactid = contactid;
}
#Column(name = "contactname", nullable = false)
public String getContactname() {
return this.contactname;
}
public void setContactname(String contactname) {
this.contactname = contactname;
}
#Column(name = "contacttype", nullable = false)
public String getContacttype() {
return this.contacttype;
}
public void setContacttype(String contacttype) {
this.contacttype = contacttype;
}
#Column(name = "reftype", nullable = false, length = 1)
public String getReftype() {
return this.reftype;
}
public void setReftype(String reftype) {
this.reftype = reftype;
}
#Column(name = "ref", nullable = false)
public int getRef() {
return this.ref;
}
public void setRef(int ref) {
this.ref = ref;
}
public String toString(){
return "\n#"+this.contactname+" : ("+this.ref+"-"+this.reftype+") \n"
+"#Items-----\n"+getContactItems()+"\n"
+"#Address---\n"+getContactAddressess()+"\n";
}
}
Assuming that Teacher is a User, and that every user has contacts.
User.class
#OneToMany(mappedBy = "user", targetEntity = Contact.class, orphanRemoval=true)
#Cascade(CascadeType.ALL)
private Set<Contact> contacts = new ConcurrentSkipListSet<Contact>();
//No setContacts here.
Contact.class
#ManyToOne
private User user;
public void setUser(User user){
this.user = user;
}
That's it.
First, since there's a User table and no Teacher table (teachers seem to be a sub-set of user rows, denoted by a 'type' column) I wouldn't have a table of User and a Teacher model. I would have only a User model instead. Hibernate is much easier if you do things the Hibernate way, which is one model per table with the model having the same name. For example, if you do this, you can use a tool to auto-generate (reverse engineer) all your model classes. This means a Hibernate tool will look at your tables, foreign keys, etc and generate appropiate Java code for your tables. Very very convenient when you start making table changes.
Normally you'll reverse engineer the model classes. Since these are machine-generated you don't want to change them because the changes will be over-written the next time to reverse-engineer the models. What I do for conditions such as yours is to create a class called a DAO - Data Access Object, or DAO.
public class UserDAO {
public static User getTeacher(EntityManager em, Long id) {
try {
IForgotTheType query = em.createQuery("User from User user, Contact contact where contact.ref=user.teacherid AND contact.reftype='T' and User.id=:id");
query.setParameter("id", id);
return (User) query.getSingleResult();
} catch (NoResultException e) {
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Obviously I am not sure about your table structure and column names but you get the idea. You can see where I inserted your code into the query above. Now you can get a teacher by simply calling UserDAO.getTeacher(). Use DAOs - otherwise you'll have Hibernate code everywhere in your code making maintenance more difficult.
Check out section 3.4 of this.

Categories