Persisting already persisted object within a relationship dependency - java

I have a relationship n/n between Product and Order
So I have a third table ProductOrder, because I need new columns when they are created.
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ORDER_SEQ")
#Column(unique = true, nullable = false, updatable = false)
private Long idOrder;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ProductOrder> productOrder;
//get and setter
here is the ProductOrder:
#Entity
#IdClass(ProductOrderId.class)
public class ProductOrder implements Serializable {
private static final long serialVersionUID = 3943799614725570559L;
#Id
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "product_id")
private Product product;
#Id
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "order_id")
private Order order;
private Integer qtdProduct;
private Double unitValueProductOrder;
//get and setter
also My ProcutOrderId (just in case)
public class ItemCompraId implements Serializable {
private Long compra;
private Long produto;
//get and set
and my Order entity:
#Entity
#SequenceGenerator(name = "ORDER_SEQ", sequenceName = "s_compra", initialValue = 1, allocationSize = 1)
public class Order implements Serializable {
private static final long serialVersionUID = 3943799614725570559L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ORDER_SEQ")
#Column(unique = true, nullable = false, updatable = false)
private Long idOrder;
private Double orderValue;
private Date creatingDate;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ProductOrder> productOrder;
So basically I have any Products ALREADY persisted in db... I just got a list of them when some order is about to be ordered. So I wanna persist a new object (Order) based on some already persisted object (products). This is the method invoked on managedbean to persist an Order.
public String doOrder() throws Exception {
try {
Order order = new Order();
compra.setCreatingDate(new Date());
compra.setOrderValue(null);
if (compra.getProductOrder() == null)
compra.setProductOrder(new HashSet<ProductOrder>());
for (Product product : listOfMyCartOfProducts) {
ProductOrder productOrder = new ProductOrder();
productOrder.setQtdProduct(100);
productOrder.unitValueProductOrder(null);
productOrder.setOrder(order);
productOrder.setProduct(product); //I THINK THAT THE PROBLEM IT'S HERE
order.getOrderProduct().add(productOrder);
}
ejbInvoke.persist(order); //tryed .merge and it doesn't work aswell
return "stuff";
Any ideas?
I'm desperate.. I need this working for yesterday..
Any help please??
Btw I'm using JSF 2.0, Hibernate with JPA 2.0 and Postgres.
Regards,

You have set the order->productOrder->product relationships to cascade the persist (included in cascadeType.ALL). When you call persist on order, you are in effect calling persist on ProductOrder and Product as well, which is expected to throw an exception if any of them already exist in the database.
Either
1) remove the cascade persist option on the productOrder->product relationship so that persist is not getting called - with the draw back that you will have to manually call persist if you ever associate new Products through a new Order.
2) Call em.find using the product pk, and associate the instance returned to the productOrder->product relationship
3) use em.merge instead which will cascade over each relationship and decide on its own if the entity exists or needs to be inserted. This will cause changes made within the product instance though to be merged as well.

Related

Avoid updating entities with JPA based on timestamp column

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/

Spring jpa onetomany returns only one element

I am trying to build a bidirectional one to many relationship with the spring data jpa but the list annotated with #onetomany always return one element.
Here is the code for my entities(setters and getters omitted):
#Entity
#Table(name = "sdk_sdk")
public class SDKEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String version;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "sdk")
#OrderBy("order ASC")
private List<SDKFileEntity> fileEntities;
}
And the second entity:
#Entity
#Table(name = "sdk_file")
public class SDKFileEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String fileType;
private Integer sdkId;
public SDKFileEntity() {
}
#ManyToOne
#JoinColumn(name = "id", insertable = false, updatable = false)
private SDKEntity sdk;
I am trying to have a manytoone mapping where the sdkId corresponds to the id from the SDKEntity class.
Whenever I try to get the sdkfiles from the sdkEntity using spring's repository, the size of the list is always 1.
So for example:
SDKEntity entity=repository.findOne(foo);
List<SDKFileEntity> files=entity.getFileEntities();
here the size of files is 1, I have to delete the first element from the database to obtain the second element.
For me the reason here was that a parent entity implemented equals and hashcode
and unfortunately in a way that all existing entities were equal.
And non of the child entities implemented it herself.
So then the #OneToMany relation returned only the first element.
Took me quite some time.
This part of Code looks suspicious
#ManyToOne
#JoinColumn(name = "id", insertable = false, updatable = false)
private SDKEntity sdk;
name = "id" it should be actual column name as written in database column name like this
#JoinColumn(name = "VISIT_ID", referencedColumnName = "ID")
#ManyToOne
private Visit visitId;

Child entity not deleting on removal of parent entity

When deleting a parent entity I also want to remove the associated child entities (from the database). I have tried to make use of cascade on remove as seen below but I must be doing something incorrectly.
When calling remove on the parent entity object, I recieve the error message: "The entity is still referenced elsewhere in the database". I can confirm that the only place where the entity is referenced elsewhere in the database is in the two tables below (if I manually delete the child row from the database, the remove call on the parent works fine). I have been reading about entity objects and trying different things for the last 9 hours. What am I doing wrong?
Here is my parent table:
#Entity
#Table(name = "TURTLE_LOOKUP")
public class TurtleLookup implements Serializable
{
#Basic(optional = false)
#Column(name = "TURTLEID")
private int turtleid;
#Basic(optional = false)
#Column(name = "TURTLE")
private String turtle;
#OneToMany(mappedBy = "turtleType", cascade = CascadeType.REMOVE)
List<TurtleReview> turtleReviews;
...
}
Here is my child table:
#Entity
#Table(name = "TURTLE_REVIEW")
public class TurtleReview implements Serializable
{
#Column(name = "TURTLE_REVIEW_ID")
private int turtleReviewId;
#Column(name = "TURTLE_YEAR")
private int turtleYear;
#ManyToOne(cascade = CascadeType.REMOVE, optional = false)
#JoinColumn(name = "TURTLE_ID", referencedColumnName = "TURTLEID")
private TurtleLookup turtleType;
#Column(name = "IS_COMPLETE")
private short isComplete;
...
}
EDIT/UPDATE:
If I change CascadeType.REMOVE to CascadeType.ALL, the TurtleReview entities are successfully deleted from the database when deleting the parent TurtleLookup entity object. However, when calling the below function to create a new TurtleReview entity object, JPA tries to insert a new TurtleLookup entity in to the database, which throws the exception: "Entry already resides within the DB. Transaction rolled back". Below is the code executed when creating a new TurtleReview entity.
public void setDatasetReviewComplete(TurtleLookup turtle, Short year, boolean isComplete)
{
TurtleReview turtleReview = getTurtleReview(turtle, year);
if (turtleReview == null)
{
turtleReview = new TurtleReview();
turtleReview.setTurtleYear(year)
turtleReview.setTurtleType(new a.b.entity.TurtleLookup(turtle.getId(), turtle.getValue()));
}
turtleReview.setIsComplete(isComplete ? (short)1 : 0);
entityManager.persist(turtleReview);
}
try change cascade value to all or all-delete-orphan
#OneToMany(mappedBy = "turtleType", cascade = CascadeType.REMOVE)
List<TurtleReview> turtleReviews;
...
}
There might be an issue with your domain model, a part that is left out in the question. Do you possibly have circular cascades? If you have a circle of cascades and some of them are CascadeType.REMOVE and some are CascadeType.PERSIST, then Hibernate (not sure about other JPA implementation) will just do.... nothing when you call the remove() method. Without an error or exception message.
Try with hibernate #Cascade annotation:
#Cascade(value = CascadeType.ALL)
#OneToOne(mappedBy = "turtleReview") // mappedBy name of TurtleRewiew object field in TurtleLookup entity class
private TurtleLookup turtleType;
If your relationship is oneToOne you can't have oneToMany to the other side and you can't have List<TurtleReview>. If your relationship is oneToMany then your entities will be for example:
#Entity
#Table(name = "TURTLE_LOOKUP")
public class TurtleLookup implements Serializable
{
#Basic(optional = false)
#Column(name = "TURTLEID")
private int turtleid;
#Basic(optional = false)
#Column(name = "TURTLE")
private String turtle;
#OneToMany(mappedBy = "turtleType") // or add cascade = javax.persistence.CascadeType.ALL and remove #Cascade if you are not using hibernate
#Cascade(value = CascadeType.ALL)
List<TurtleReview> turtleReviews;
...
}
#Entity
#Table(name = "TURTLE_REVIEW")
public class TurtleReview implements Serializable
{
#Column(name = "TURTLE_REVIEW_ID")
private int turtleReviewId;
#Column(name = "TURTLE_YEAR")
private int turtleYear;
#ManyToOne
#JoinColumn(name = "TURTLE_ID", referencedColumnName = "TURTLEID")
private TurtleLookup turtleType;
#Column(name = "IS_COMPLETE")
private short isComplete;
...
}

Update database when some entries were removed JPA

I'm developing a logic in the server side of my project that will update an Entity in the database. But this entity has reference to a list of another entity.
So, for example I have an Entity Test like this one:
#Entity
public class Test {
#Id
#Column(name = "idTest", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy = "idTest", targetEntity = Test2.class, fetch = FetchType.LAZY)
private Collection<Test2> test2Collection;
}
which has references to Test2 Entity.
#Entity
public class Test2 {
#Id
#Column(name = "idTest2", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String field1;
private String field2;
#ManyToOne
#JoinColumn(name = "idTest", referencedColumnName = "idTest")
private Test idTest;
}
In my case, I'm updating the Collection test2Collection of Test entry and eventually I'm removing some of Test2 entries of it.
The simple merge method from EntityManager does not detect that some entries has been deleted when I call it passing the new updated Test entry.
I was thinking to track the removed entries from test2Collection by making a diff between the Test entry before updating the database with the new Test entry to be updated.
But I'm not sure this is the only (and best) way of doing this. Anyone else has another idea?
In your relationship, you have to add orphanRemoval, then if you remove one element of the collection and persist Test, the element deleted on the collection will be automatically delete of the Test2 table.
#OneToMany(orphanRemoval = true, mappedBy = "idTest", targetEntity = Test2.class, fetch = FetchType.LAZY)
private Collection<Test2> test2Collection;

Save Hibernate entity with parend child relationships fails with foreign key error

I am getting violated - parent key not found while trying to save Hibernate Entity
I have parent entity:
#Entity
#Table(name = "parents")
public class Parent implements Serializable {
private static final long serialVersionUID = 1246376778314918671L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
#SequenceGenerator(name = "seq", sequenceName = "PARENT_ID_SEQ", allocationSize = 1)
#Column(name = "parent_id")
private long parentId;
#Column(name = "display_name")
#Size(min = 1, max = 128)
#NotBlank
private String displayName;
#JsonManagedReference("childAssignments")
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval=true)
private Set<ChildAssignment> childAssignments = new HashSet<ChildAssignment>(0);
//regular getters and setters here
}
and child entity looks like (in database it has foreign key on parent_id field from parents table):
Entity
#Table(name = "child_assignments")
public class ChildAssignment implements Serializable {
private static final long serialVersionUID = -5949955576511639261L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
#SequenceGenerator(name = "seq", sequenceName = "CHILD_ASSIGNMENT_ID_SEQ", allocationSize = 1)
#Column(name = "child_assignment_id")
private long childAssignmentId;
#Column(name = "parent_id")
private long parentId; // getting error because after creating new parent it has not been set
#Column(name = "site_id")
private long siteId;
#ManyToOne
#JoinColumn(name = "parent_id", insertable = false, updatable = false)
private Parent parent;
// regular getters and setters here
}
After parent object and childAssignments object have been created I am adding childAssignment to parent
ChildAssignment ca = new ChildAssignment();
ca.setSiteId(1);
// I do not set parent_id here since I do not know it and expecting Hibernate to figure it out
parent.getChildAssignments().add(childAssignment);
session.save(parent);
Expected result is to save new parent entry with ID and after use this id to save child but seems like hibernate does not know about parent_id at the time of saving, how should I build my association to make it work? or some annotations on parent_id field?
UPDATED
I tried to remove parent_id or set it to #Transient on childAssignment entity, and get new error cannot insert NULL into table, it's obvious that Hibernate is trying to insert parent_id but do not populate it,
setting parent on a child does not help either
ChildAssignment ca = new ChildAssignment();
ca.setSiteId(1);
ca.setParent(parent)
parent.getChildAssignments().add(childAssignment);
session.save(parent);
What I am missing?
SOLVED
I solved a problem changing my childAssignment entity
#Transient -- add transient (just to have it)
#Column(name = parent_id", insertable = false, updatable = false) -- add insertable and updatable both equals to false
private parentId;
.....
#ManyToOne
#JoinColumn(name = "parent_id") -- remove insertable = false, updatable = false options
private Parent parent;
You need to do:
ca.setParent(parent);
as well.
If you specify a two sided relationship then you must assign both sides in Java code, just as you would if it was pure Java.
Secondly, your declaration of a 'parentId' column is redundant. Hibernate will create that for you automatically.

Categories

Resources