Why unidirectional #OneToMany update is not working - java

I am completely new to JPA and ORM concept, so I am hoping someone can lucidly explain what might be the problem with my code.
#Entity
#Table(name = "PERSISTENCE_customer")
public class Customer implements Serializable {
private static final long serialVersionUID = 1005220876458L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
#OneToMany (cascade = CascadeType.ALL, orphanRemoval = true)
private List<CustomerOrder> orders;
}
#Entity
#Table(name = "PERSISTENCE_ORDER")
public class CustomerOrder implements Serializable{
private static final long serialVersionUID = 199102142021L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
String status;
#NotNull
#OneToMany (cascade = CascadeType.ALL, orphanRemoval = true)
private List<LineItem> lineItems = new ArrayList();
#NotNull
private String orderNumber;
................
................
}
#Entity
#Table(name = "PERSISTENCE_LINEITEM")
public class LineItem implements Serializable {
private static final long serialVersionUID = 1991217202100959L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
private Integer quantity;
#NotNull
private Part part;
}
Initially, the Customer entity is created through the user interface and persisted successfully. Later, the customer has an order and I update the Customer with CustomerOrder as follow:
private void UpdateCustomer(Customer customer) {
FacesContext fc = FacesContext.getCurrentInstance();
List<ShoppingCartItem> shoppingCart = getShoppingCart();
CustomerOrder order = new CustomerOrder();
List<CustomerOrder> orders = customer.getOrders();
order.setLastUpdated(new Date());
order.setOrderNumber(getInvoiceNumber());
List<LineItem> lineItems = shoppingCart
.stream()
.map(e -> (new LineItem(e.getPart(), e.getQuantity())))
.collect(Collectors.toList());
order.setLineItems(lineItems);
order.setStatus("Pending Shipment");
order.setTotal(getTotal());
orders.add(order);
customer.setOrders(orders);
try {
updateOrders(customer, orders);
fc.addMessage(null,
new FacesMessage("Customer order added successfuly"));
} catch (ListServiceException e) {
FacesMessage errMsg = new FacesMessage(FacesMessage.SEVERITY_FATAL,
"Error while adding customer order: ", e.getMessage());
fc.addMessage(null, errMsg);
}
}
private void updateOrders(Customer cust, List<CustomerOrder> orders) throws ListServiceException {
try { //em is the EntityManager injected as the field member
if (em != null) {
if (em.isOpen()) {
Customer c = getCustomer(cust.getId());
c.setOrders(orders);
em.merge(c);
} else {
logger.severe("Entity manager is closed");
}
else {
logger.severe("Entity manager is NULL");
}
} catch (Exception e) {
throw ThrowListServiceException.wrapException(e);
}
}
Once the EntityManage merges I get the following exception. I was under the impression that I don't need to explicitly persist the LineItem and CustomerOrder entities myself. I thought that the JPA will persist all the entities in the object graph. Why am I getting this exception? (I am using GlassFish 5.1 server with EclipseLink JPA)
Thanks in advance.
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: Column 'ORDERS_ID' cannot accept a NULL value.
Error Code: 30000
Call: INSERT INTO PERSISTENCE_CUSTOMER_PERSISTENCE_ORDER (orders_ID, Customer_ID) VALUES (?, ?)
bind => [2 parameters bound]
Query: DataModifyQuery(name="orders" sql="INSERT INTO PERSISTENCE_USER_PERSISTENCE_ORDER (orders_ID, User_ID) VALUES (?, ?)")
at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:331)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:905)
...............................
.................................
Caused by: java.sql.SQLIntegrityConstraintViolationException: Column 'ORDERS_ID' cannot accept a NULL value.
Update
Using the IDE (Netbeans) debugger, I stepped through the code, and as I predicted, during the entity merge the JPA does not add new entities that are part of the object graph to the persistence context. For instance, in the updateOrders() method when I try to update the existing Customer object with a list of new CustomerOrder object, the JPA doesn't figure out that the elements in the List are not part of the persistence context yet and they need to be added. As a result, I had to modify my code to first add the List to the persistence context and then merge the Customer object with the newly persisted List. By doing so I no longer get the exception.
By the way, at moment, all the mapping relationships are unidirectional because I didn't see any reasons to use bidirectional mapping. However, would I gain anything by making these mappings bidirectionals?

Your OneToMany mapping is missing join specification or mappedBy value

I noticed that.
Firstly You should commit new order to database.Then you should link it with user.I'm not sure if this solves your problem but this is a problem.Can you check it ?

In my opinion, if you keep customer information in your Order entity, it may solve this problem.
#ManyToOne ()
private Customer customer;
And in your Customer entity you should put mappedBy=customer for orders field.
After that, instead of giving orders for customer, you can give customer for a specific order. In my opinion it will achieve a better relationship mapping;
order.setCustomer(customer);
I hope i understood it right and this will solve your problem. When you give customer detail for order, you dont need to give orderlist detail for the same customer. Only one of them should be enough.

Related

Why do we need bidirectional synchronized methods?

As stated in the topic. Why do we need bidirectional synchronized methods? What real world use case does it solve? What happens if I don't use them?
In Hibernate's User Guide:
Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times.
The addPhone() and removePhone() are utility methods that synchronize both ends whenever a child element is added or removed.
Source - Hibernate User Guide
In one of Vlad's blog posts:
However, we still need to have both sides in sync as otherwise, we break the Domain Model relationship consistency, and the entity state transitions are not guaranteed to work unless both sides are properly synchronized.
Source - Vlad Mihalcea Blog
Lastly, in Vlad's book - High Performance Java Persistance, page 216:
For a bidirectional #ManyToMany association, the helper methods must be added to the entity that is more likely to interact with. In our case, the root entity is the Post, so the helper methods are added to the Post entity
However, if I use simple generated setters, Hibernate seems to work just fine as well. Furthermore, synchronized methods might lead to performance degredation.
Synchronized methods:
public void joinProject(ProjectEntity project) {
project.getEmployees().add(this);
this.projects.add(project);
}
Generates this:
Hibernate:
select
employeeen0_.id as id1_0_0_,
projectent2_.id as id1_2_1_,
teamentity3_.id as id1_3_2_,
employeeen0_.first_name as first_na2_0_0_,
employeeen0_.job_title as job_titl3_0_0_,
employeeen0_.last_name as last_nam4_0_0_,
employeeen0_.team_id as team_id5_0_0_,
projectent2_.budget as budget2_2_1_,
projectent2_.name as name3_2_1_,
projects1_.employee_id as employee1_1_0__,
projects1_.project_id as project_2_1_0__,
teamentity3_.name as name2_3_2_
from
employees.employee employeeen0_
inner join
employees.employee_project projects1_
on employeeen0_.id=projects1_.employee_id
inner join
employees.project projectent2_
on projects1_.project_id=projectent2_.id
inner join
employees.team teamentity3_
on employeeen0_.team_id=teamentity3_.id
where
employeeen0_.id=?
Hibernate:
select
projectent0_.id as id1_2_,
projectent0_.budget as budget2_2_,
projectent0_.name as name3_2_
from
employees.project projectent0_
where
projectent0_.id=?
Hibernate:
select
employees0_.project_id as project_2_1_0_,
employees0_.employee_id as employee1_1_0_,
employeeen1_.id as id1_0_1_,
employeeen1_.first_name as first_na2_0_1_,
employeeen1_.job_title as job_titl3_0_1_,
employeeen1_.last_name as last_nam4_0_1_,
employeeen1_.team_id as team_id5_0_1_
from
employees.employee_project employees0_
inner join
employees.employee employeeen1_
on employees0_.employee_id=employeeen1_.id
where
employees0_.project_id=?
Hibernate:
insert
into
employees.employee_project
(employee_id, project_id)
values
(?, ?)
Notice additional select for Employee right after Projects were fetched. If I use simply employeeEntity.getProjects().add(projectEntity);, it generates:
Hibernate:
select
employeeen0_.id as id1_0_0_,
projectent2_.id as id1_2_1_,
teamentity3_.id as id1_3_2_,
employeeen0_.first_name as first_na2_0_0_,
employeeen0_.job_title as job_titl3_0_0_,
employeeen0_.last_name as last_nam4_0_0_,
employeeen0_.team_id as team_id5_0_0_,
projectent2_.budget as budget2_2_1_,
projectent2_.name as name3_2_1_,
projects1_.employee_id as employee1_1_0__,
projects1_.project_id as project_2_1_0__,
teamentity3_.name as name2_3_2_
from
employees.employee employeeen0_
inner join
employees.employee_project projects1_
on employeeen0_.id=projects1_.employee_id
inner join
employees.project projectent2_
on projects1_.project_id=projectent2_.id
inner join
employees.team teamentity3_
on employeeen0_.team_id=teamentity3_.id
where
employeeen0_.id=?
Hibernate:
select
projectent0_.id as id1_2_,
projectent0_.budget as budget2_2_,
projectent0_.name as name3_2_
from
employees.project projectent0_
where
projectent0_.id=?
Hibernate:
insert
into
employees.employee_project
(employee_id, project_id)
values
(?, ?)
No more fetching of employee.
Full code.
Controller.
#RestController
#RequestMapping(path = "${application.endpoints.projects}", produces = MediaType.APPLICATION_JSON_VALUE)
#Validated
public class ProjectsEndPoint {
#PostMapping("add-employee")
#ApiOperation("Add employee to project")
public void addEmployeeToProject(#RequestBody #Valid EmployeeProjectRequest request) {
LOGGER.info("Add employee to project. Request: {}", request);
this.projectsService.addEmployeeToProject(request);
}
}
EmployeeProjectRequest.
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public record EmployeeProjectRequest(
#NotNull #Min(0) Long employeeId,
#NotNull #Min(0) Long projectId) {
}
ProjectService.
#Service
public class ProjectsService {
private final ProjectRepo projectRepo;
private final EmployeeRepo repo;
public ProjectsService(ProjectRepo projectRepo, EmployeeRepo repo) {
this.projectRepo = projectRepo;
this.repo = repo;
}
#Transactional
public void addEmployeeToProject(EmployeeProjectRequest request) {
var employeeEntity = this.repo.getEmployee(request.employeeId())
.orElseThrow(() -> new NotFoundException("Employee with id: %d does not exist".formatted(request.employeeId())));
var projectEntity = this.projectRepo.getProject(request.projectId())
.orElseThrow(() -> new NotFoundException("Project with id: %d does not exists".formatted(request.projectId())));
//This line can be changed with employeeEntity.joinProject(projectEntity);
employeeEntity.getProjects().add(projectEntity);
}
}
ProjectRepo.
#Repository
public class ProjectRepo {
private final EntityManager em;
public ProjectRepo(EntityManager em) {
this.em = em;
}
public Optional<ProjectEntity> getProject(Long id) {
var result = this.em.createQuery("SELECT p FROM ProjectEntity p where p.id = :id", ProjectEntity.class)
.setParameter("id", id)
.getResultList();
return RepoUtils.fromResultListToOptional(result);
}
}
EmployeeRepo.
#Repository
public class EmployeeRepo {
private final EntityManager em;
public EmployeeRepo(EntityManager em) {
this.em = em;
}
public Optional<EmployeeEntity> getEmployee(Long id) {
var employees = this.em.createQuery("""
SELECT e FROM EmployeeEntity e
JOIN FETCH e.projects p
JOIN FETCH e.team t
WHERE e.id = :id""", EmployeeEntity.class)
.setParameter("id", id)
.getResultList();
return Optional.ofNullable(employees.isEmpty() ? null : employees.get(0));
}
}
EmployeeEntity.
#Entity
#Table(name = "employee", schema = "employees")
public class EmployeeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#Enumerated(EnumType.STRING)
private JobTitle jobTitle;
#ManyToOne(fetch = FetchType.LAZY)
private TeamEntity team;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(schema = "employees", name = "employee_project",
joinColumns = #JoinColumn(name = "employee_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "project_id", referencedColumnName = "id"))
private Set<ProjectEntity> projects = new HashSet<>();
public EmployeeEntity() {
}
public void joinProject(ProjectEntity project) {
project.getEmployees().add(this);
this.projects.add(project);
}
public void leaveProject(ProjectEntity project) {
project.getEmployees().remove(this);
this.projects.remove(project);
}
... Getters and Setters ...
}
ProjectEntity.
Entity
#Table(name = "project", schema = "employees")
public class ProjectEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal budget;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "projects")
private Set<EmployeeEntity> employees = new HashSet<>();
public ProjectEntity() {
}
... Getters and Setters ...
}
If there are really many elements on the Many side, then you probably should not use OneToMany at all. Fetching large collections implies using some kind of pagination\filtering, but OneToMany loads the whole set.
First of all, you need to update an owning entity(where FK resides) to store it in the DB. And what Vlad and Hibernate guide mean about consistency, refers to updating entity objects inside current session. Those objects have transitions during lifecycle, and when you have bidirectional association, if you don't set inverse side, then that inverse side entity won't have the field updated, and would be inconsistent with an owning side entity(and probably with the DB ultimately, after TX commits) in the current session.
Let me illustrate on OneToMany example.
If we get 2 managed entities Company and Employee:
set employee.company = X -> persist(employee) -> managed List<Employee> company.employees gets inconsistent with db
And there might be different types of inconsistencies, like getting from company.employees field after and arising side-effects(guess it was not empty, but just without employee you just added), and if there is Cascade.ALL, you might miss or falsely remove\update\add entities through broken relationships, because your entities are in a ambigious state, and hibernate deals with it in a defensive but sometimes unpredictable way:
Delete Not Working with JpaRepository
Also, you might find interesting this answer: https://stackoverflow.com/a/5361587/2924122

How to delete relation between two tables with one-to-many unidirectional

I have two entities bound with one-to-many relationship. But one entity can exist without the other. So the relationship is uni-directional. As this;
#Entity
public class TransportationOrderProduct {
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private List<WarehousePackage> selectedWarehousePackages;
}
#Entity
public class WarehousePackage{
}
And hibernate created these tables;
TransportationOrderProduct
id
TransportationOrderProductSelectedWarehousePackages
transportationOrderProductId
selectedWarehousePackageId
WarehousePackage
id
When fetching the collection (selectedWarehousePackages) everything works fine.
But when I clear the TransportationOrderProduct.selectedWarehousePackages list, and add new ones, Hibernate throws DuplicateEntry exception. Saying that transportationOrderProductId in the TransportationOrderProductSelectedWarehousePackages table cannot be inserted twice.
I think this is because Hibernate doesn't delete the relation in the TransportationOrderProductSelectedWarehousePackages table when I call;
TransportationOrderProduct.selectedWarehousePackages.clear()
And add some entities after ;
TransportationOrderProduct.selectedWarehousePackages.add(newPackage)
TransportationOrderProduct.selectedWarehousePackages.add(newPackage)
.
.
Can somebody help?
Its sounds the relation is one-Many as I understand
Don’t use unidirectional one-to-many associations
avoid unidirectional one-to-many associations in your domain model. Otherwise, Hibernate might create unexpected tables and execute more SQL statements than you expected and this certainly described why hibernate create 3 entities while your implementation should be 2 entities with relation one-2-many.
The definition of uni-directional sounds not an issue in our case if we use directional it's OKAY it will serve our purpose you just need attribute to map our association
using annotation
#Entity
public class TransportationOrderProduct {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private Set<WarehousePackage> packages= new HashSet<>();
public Set<WarehousePackage> getPackages() {
return packages;
}
public void setPackages(Set<WarehousePackage> packages) {
this.packages = packages;
}
public void addPackages (WarehousePackage value) {
this.packages.add(value);
}
public void clearPackages (WarehousePackage value) {
this.packages.clear();
}
...
}
#Entity
public class WarehousePackage{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
}
You can save transportation order with association you need
TransportationOrderProduct transportation = new TransportationOrderProduct ();
Set<WarehousePackage> packages = new HashSet <> () ;
WarehousePackage package1 = new WarehousePackage () ;
WarehousePackage package2 = new WarehousePackage () ;
packages.add(package1);
packages.add(package2);
transportation.setPackages(packages) ;
session.save(transportation);

EJB/JPA: Foreign Key Constraint violation when trying to persist entity in many-many relationship

I have an EJB many-to-many (bi-directional) relation between classes (entity-classes) Person and Hobby. There are corresponding tables in the database, called PERSON and HOBBY, as well as a table PERSON_HOBBY for the many-to-many relationship.
As I will detail below, the problem is that whenever I try to persist a person with hobbies, I run into a Foreign Key constraint violation. This is because the entityManager tries to save new rows into PERSON_HOBBY that contain references to a person-entity with ID=0, which doesn’t exist in the PERSON table. I’ll come back to that later, but first I’ll show the relevant parts of the entity classes.
First, here is entity class Person:
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String email;
#ManyToMany(cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER)
/* Note: I used to have CascadeType.PERSIST in the above line as well, but
it caused "Detached object passed to persist" exceptions whenever I tried to
persist a person with hobbies. So I suppose I was right in deleting
CascadeType.PERSIST...? */
#JoinTable(name = "PERSON_HOBBY",
joinColumns = #JoinColumn(name="personId", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="hobbyId", referencedColumnName="id"))
private List<Hobby> hobbies = new ArrayList<Hobby>();
public List<Hobby> getHobbies () {
return hobbies;
}
public void setHobbies (List<Hobby> hobbies) {
this.hobbies = hobbies;
for(Hobby h:hobbies) { // this is to maintain bi-directionality
h.addPerson(this);
}
}
// other getters and setters omitted here.
Then entity class Hobby:
#Entity
public class Hobby {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String description;
#ManyToMany(mappedBy = "hobbies", fetch = FetchType.EAGER)
private List<Person> persons;
public Hobby() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
// getter and setter for Description omitted here.
public List<Person> getPersons () {
return persons;
}
public void setPersons (List<Person> persons) {
this.persons = persons;
}
public void addPerson (Person p) {
this.persons.add(p);
}
}
I also have a stateless session bean, that’s shown here as far as relevant to the issue:
#Stateless
#Default
public class PersonRepositoryImpl implements PersonRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public Person create(Person p) {
entityManager.persist(p);
entityManager.flush();
return p;
}
#Override
public Person createPersonWithHobbies(Person p, List<Hobby>hobbyLijst) {
p = create(p); // I've also tried simply: create(p);
System.out.println("The newly assigned ID for the persisted
person is: " + p.getId());
// That last line **always** prints the person-ID as being 0 !!!!
p.setHobbies(hobbyLijst);
entityManager.merge(p); // This should save/persist the person's hobby's!
entityManager.flush();
return p;
}
}
Now from my servlet, I've been trying in two different ways. First, I tried calling method create(p) on the above session bean. That is, after creating a new Person instance p, setting all its non-relational fields, AND calling setHobbies on it (with a non-zero list of Hobby objects taken from the database), I called:
personRepo.create(p);
But this resulted in the Foreign Key (FK) exception:
INSERT on table 'PERSON_HOBBY' caused a violation of foreign key
constraint 'FK_EQAEPVYK583YDWLXC63YB3CXK' for key (0). The statement
has been rolled back.”
The FK-constraint mentioned here is the one in PERSON_HOBBY referring to PERSON.
The second way I tried was to make the following call from the servlet:
personRepo.createPersonWithHobbies(p, hobbyLijst);
where, just like before, p is the new person object; and hobbyLijst is that person's list of hobbies. And this resulted in the exact same FK-exception as the earlier call to personRepo.create(p).
Importantly, the println statement within method createPersonWithHobbies, calling getId() on the newly persisted person-object, ALWAYS gives that object's ID as being 0. Which I suppose does explain the FK-exception, since there's no person entity/row in the PERSON table with an ID of 0, nor is there supposed to be one. But of course the getId() call should not output 0. Instead, it should output the newly generated ID of the newly persisted person entity. (And yes, it IS persisted correctly in the PERSON tabel, with a correctly generated ID>0. So the correct ID is there in the PERSON-table - it just seems to be invisible to the entityManager and/or the container.)
Thanks.

How to correctly add records to jpa onetomany join table

I'm developing a filing system where I have 3 tables. PROJECTS table consists of projectid, project name and other details (see below). This is an existing class and populated schema and I do not want to modify this part of the application if possible.
Folders table (Called ProjectClassification) consists of folderid and foldername and is the owning side of a unidirectional onetomany relationship.
Project_Folders is a join table. I'm using JPA 2.0 (EclipseLink) and JSF 2.0 as my web framework.
My basic problem is I can't add duplicate records to the join table using a merge operation. MERGE is good for adding records until the owning key already exists, after which point it will only update the join table. I know this is the way it's supposed to work but I need to add new records even if there's a duplicate of the owning key. This will allow me to store different projects in the same folder.
I've looked through some other questions here such as:
onetomany unidirectional with jointable setup using jpa
This says what is needed to add one entity to the other in a join table but i need to know more about how to correctly persist or merge the added entity to the database.
The folder entity class:
#Entity
#Table(name = "PROJECTCLASSIFICATIONS")
public class ProjectClassifications implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int proclassid;
private int projectid;
private String classification;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "PROJECT_CLASSF_JOIN",
joinColumns = #JoinColumn(name = "proclassid", referencedColumnName = "proclassid"),
inverseJoinColumns = #JoinColumn(name = "projectid", referencedColumnName = "projectid", unique = true))
private Collection<Projects> projects;
public ProjectClassifications() {
}
public ProjectClassifications(String classification) {
this.classification = classification;
}
public ProjectClassifications(int proclassid, int projectid) {
this.proclassid = proclassid;
projects = new ArrayList<Projects>();
}
public ProjectClassifications(Projects newProject) {
projects = new ArrayList<Projects>();
}
public void addProject(Projects newProject) {
if(!getProjects().contains(newProject))
getProjects().add(newProject);
}
....
....
The Project entity class is a pre existing code and I do not want to modify at all if possible:
#Entity
#Table(name = "PROJECTS")
public class Projects {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int projectid;
private String projectName;
private String projectDescription;
#Temporal(javax.persistence.TemporalType.DATE)
private Date startDate;
#Temporal(javax.persistence.TemporalType.DATE)
private Date endDate;
private String commnts;
// foreign keys to parent tables
private int fk_countryid;
private int fk_companyid;
private int fk_employeeid;
#ManyToOne(optional = true)
#JoinColumn(name = "countryid")
private Country country;
....
....
I then use two html select lists to select values for projectid and proclassid which call the following methoid using a JSF managed bean:
public String makeProClassRecord() {
newProClass = new ProjectClassifications(proclassid, projectid);
newProject = proServ.findByProjectId(projectid);
newProClass.addProject(newProject);
facade.update(newProClass);
//facade.save(newProClass);
return showProclass();
}
My questions are:
1) Is MERGE the correct operation used to add records into a join table?
2) Is there a way to add records that contain duplicate keys (foreign keys represented as new records in the join table) using MERGE?
3) Should PERSIST be used to achieve question 2?
4) Would it be better to create an entity for the join table itself and simply use a PERSIST method to insert the records?
Many thanks
So I solved this myself a couple of weeks ago and thought of sharing the answer. Instead of doing merge or persist operations on any of the target entities, I created a Join table and unidirectional OneToMany relationship from the Project entity to the below ProjectFileSystem join table entity and simply did the persist operation using that entity. I need to add duplicate folders for different projects (or store many projects under a single folder item) so it seems more efficient to do the CRUD operations in the actual join table entity rather than from the target entity. Hope this helps:
#Entity
#Table(name = "PROFOLDERSYS_JOIN")
public class ProjectFileSystem implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int foldersysid;
private int proclassid;
private int projectid;
private String projectName;
private String folderName;
public ProjectFileSystem() {
}
public ProjectFileSystem(int proclassid, int projectid,
String projectName, String folderName) {
this.proclassid = proclassid;
this.projectid = projectid;
this.projectName = projectName;
this.folderName = folderName;
}
// getters and setters
}
The method in the bean would be:
public String makeProSys() {
newProSys = new ProjectFileSystem(proclassid, projectid, classification, projectName);
newProject = proServ.findByProjectId(projectid);
projectName = newProject.getProjectName();
newProSys.setProjectName(projectName);
newProClass = facade.findByContactId(proclassid);
classification = newProClass.getClassification();
newProSys.setFolderName(classification);
profilFacade.save(newProSys);
return showProSys();
}

Many to One Relationship While removing child object it's throwing exception

I am doing Many To One relationship using JPA . While deleting child object from Child table it's throwing exception.
Below is My code:
Project.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="projectName")
private String projectName;
#Column(name="projectDesc")
private String projectDesc;
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="companyId")
Company.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="compName")
private String compName;
#Column(name="address")
private String address;
Below is Insert code:
InserAction.java
public static void main(String[] args) {
Company comp2 = new Company();
comp2.setCompName("IBM");
comp2.setAddress("Bangalore");
Project pro2 = new Project();
pro2.setProjectName("Management System");
pro2.setProjectDesc("System");
pro2.setCompany(comp2);
EntityManager entityManager = EntityManagerUtil.getEmf().createEntityManager();
try{
EntityTransaction entr = entityManager.getTransaction();
entr.begin();
entityManager.persist(pro2);
entr.commit();
}
}
DeleteAction.java
EntityManager entityManager = EntityManagerUtil.getEmf()
.createEntityManager();
try {
EntityTransaction entr = entityManager.getTransaction();
entr.begin();
Project project = entityManager.find(Project.class,5);
entityManager.remove(project);
entr.commit();
}
Exception is
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`prabha`.`project`, CONSTRAINT `FK_project_companyId` FOREIGN KEY (`companyId`) REFERENCES `company` (`id`))
Error Code: 1451
Call: DELETE FROM company WHERE (id = ?)
bind => [1 parameter bound]
Query: DeleteObjectQuery(com.demo.manytoone.Company#301db5ec)
While deleting project object from Project table it' throwing above exception how can I over come this.
#ManyToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST}, fetch=FetchType.EAGER)
The above code will resolve your issue. If you observe source code of annotation ManyToOne it has an array of cascade type so you can map multiple cascade types
You should not use CascadeType.ALL, try using CascadeType.MERGE
The meaning of CascadeType.ALL is that the persistence will propagate (cascade) all EntityManager operations (PERSIST, REMOVE, REFRESH, MERGE, DETACH) to the relating entities.
It seems in your case to be a bad idea, as removing an Project would lead to removing the related Company when you are using CAscadeType.ALL. As a Company can have multiple projects, the other projects would become orphans. However the inverse case (removing the Company) would make sense - it is safe to propagate the removal of all projects belonging to a Company if this company is deleted.
You can also use various CascadeTypes, for e.g.
cascade = {CascadeType.PERSIST,CascadeType.MERGE}.
So use all those that applied to you.
For more info.
You got MySQLIntegrityConstraintViolationException. It means there are tables in the database you're binding. You should map when you set up the many to one relationship tables. Company should be able to get more than one project. So you have to define the list of projects.
Project.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="projectName")
private String projectName;
#Column(name="projectDesc")
private String projectDesc;
#ManyToOne
#JoinColumn(name="companyId")
private Company company;
Company.java
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="compName")
private String compName;
#Column(name="address")
private String address;
#OneToMany(mappedBy="company", fetch=FetchType.EAGER)
private List<Project> projects;

Categories