JPA delete child without deleting parent - java

I am using Spring Boot 2.3.0.
I have a ManyToOne relationship on one side and a OneToMany relationship on the other side. One parent to many children, but many children to one parent. I am trying to be able to delete children without affecting the parent. I have nullable = false on the child side for the parent field because I don't want to end up with accidental nulls for parent in the parent_to_child table. I want things like that to be enforced and get caught.
When I do readerRepository.save(reader) after removing one of the TBRList items (this is the child) from the List<TBRList> in the Reader object (this is the parent), I keep getting an error about the parent field not being able to be null when trying to delete the child. If I set nullable to false on the parent field in the child object, my parent disappears.
I thought I understood how this was supposed to work, but obviously not.
I have:
#Entity //parent
public class Reader implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#JsonIgnore
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "reader", orphanRemoval = true)
Set<TBRList> tbrLists = new HashSet<>();
//other fields, getters, setters, etc.
}
#Entity(name = "tbr") //child
public class TBRList implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(nullable = false)
private String name;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "reader_id", nullable = false)
private Reader reader;
//other fields, getters, setters, etc
}
In the below snippet, readerRepository.save(reader) is where the org.hibernate.PropertyValueException: not-null property references a null or transient value : com.me.project.entity.TBRList.reader exception is happening.
if (reader.hasTBRList(tbrListName)) {
Iterator<TBRList> it = reader.getTbrLists().iterator();
while (it.hasNext()) {
TBRList tbrList = it.next();
if (tbrList.getName().equals(tbrListName)) {
it.remove();
readerRepository.save(reader);
break;
}
}
}
I tried to also set reader to null in TBRList and delete it via the tbrListRepository, but the same thing happened. In fact, I've tried too many things to remember them all (I try to ask questions as a last result after hours of searching and trying things).
What am I doing wrong with trying to have a Parent/Child relationship, where I don't want Child.parent to be null, and I want to be able to delete a child from the parent without deleting the parent in the process?

I created the same classes and i get this result to execute as you want:
#Entity(name = "tbr")
#Data
public class TBRList {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String name;
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "reader_id", nullable = false)
private Reader reader;
}
#Entity
#Data
public class Reader {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "reader", orphanRemoval = true)
Collection<TBRList> tbrLists ;
}

Related

Hibernate inserts null even when field is not null in the object being saved

I am facing a weird issue where even though all fields are set in the java object, when I save the object hibernate tries to insert null values in the fields.
When I further debugged, I saw that while merging the new entity at this line hibernate generates an empty object and sets to the target instead of setting given entity to the target. This results in insert query with null values.
Am I missing some configuration here? Below are the example entities having associations similar to my case.
class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false)
#Enumerated(EnumType.STRING)
#EqualsAndHashCode.Include
private VehicleType vehicleType;
#OneToOne(mappedBy="vehicle", fetch=FetchType.LAZY)
private Car car;
#OneToOne(mappedBy="vehicle", fetch=FetchType.LAZY)
private Truck truck;
}
class Car {
#Id
private Integer id;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#MapsId
#JoinColumn(name = "vehicle_id")
private Vehicle vehicle;
...
}
class Truck {
#Id
private Integer id;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#MapsId
#JoinColumn(name = "vehicle_id")
private Vehicle vehicle;
...
}
I encountered the same problem, in my case I have an application with:
public class Claim extends BaseEntity<Integer> implements Serializable {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "claimdetailsid", referencedColumnName = "id")
private ClaimDetails claimDetails;
#OneToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "beneficiaryid", referencedColumnName = "id")
private Beneficiary beneficiary;
....
}
When I saved the Claim entity, the Claim and ClaimDetails objects were inserted correctly. The other entities had all the fields null, except the id and the creation date.
I tried changing CascadeType.PERSIST to CascadeType.ALL, that solved my insert problem.
But the delete cascade doesn't work now.

How to use #JsonManagedReference and #JsonBackReference for Many to Many relationship with best practice?

I will be much apprecaite for you to explain my questions.
I have 2 entity and 1 bridge table entity,
Let's say they are Team, User and TeamUser.
TeamEntity:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "TEAM_ID")
private Integer id;
#JsonManagedReference
#OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<TeamUsers> team_users = new HashSet<>();
UserEntity:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#JsonBackReference
#OneToMany(mappedBy = "user")
private Set<TeamUsers> team_users = new HashSet<>();
TeamUserEntity(bridge table):
#EmbeddedId
private TeamUsersId id;
#ManyToOne
#MapsId("teamId")
#JoinColumn(name = "team_id")
#JsonBackReference
private Team team;
#ManyToOne
#MapsId("userId")
#JoinColumn(name = "user_id")
#JsonManagedReference
private User user;
#Column(name = "active")
private int active;
As you can see I used #JsonManagedReference and #JsonBackReference
to telling the program the direction for the query of the Entity and avoid infinite recrusive.
Now if I run get repo.findAll() on Team CRUDrepository I will get all Team object, and within the content I will get all bridge table data and it also include User details information.
But Let's say sometimes if I want to query the data in oppisite way, I want to get All User information and the object should contain all Team information, looks like the annotation #JsonManagedReference and #JsonBackReference block the result.
In real world development, how should we manage here?
The #JsonManagedReference and #JsonBackReference annotations are used to handle circular references. They can be used to create a JSON structure in a bidirectional way. The #JsonManagedReference annotation is used on a child reference of the target POJO, which means it is a forward reference that includes during the serialization process whereas #JsonBackReference annotation is a backreference that omits during the serialization process, usually used in the corresponding child class.
Hence, The property annotated with #JsonBackReference here, which is the Team in the TeamUsers, won't be serialized. That is why when you try to get all the users having Team inside the TeamUsers, it won't work. Also, if it did, it would violate the purpose of the annotations that they're used for, recursive access mapping.
If you want to fetch data in either way, you should use #JsonIgnoreProperties instead of those two annotations. Change your entity classes as follows and you'll get your desired output.
In Team class, set #JsonIgnoreProperties("team") on the team_users field to ignore mapping team inside this field again to avoid recursive mapping. Change your Team class to:
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "TEAM_ID")
private Integer id;
#OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonIgnoreProperties("team")
private Set<TeamUsers> team_users = new HashSet<>();
}
Similarly, in User class, set #JsonIgnoreProperties("user") on the team_users field to ignore mapping user inside this field again to avoid recursive mapping. Change your User class to:
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnoreProperties("user")
private Set<TeamUsers> team_users = new HashSet<>();
}
And finally, in TeamUsers class, set #JsonIgnoreProperties("team_users") on both the team and user field to ignore mapping team_users inside these field again to avoid recursive mapping. Change your TeamUsers class to:
public class TeamUsers {
#EmbeddedId
private TeamUserId id;
#ManyToOne
#MapsId("teamId")
#JoinColumn(name = "team_id")
#JsonIgnoreProperties("team_users")
private Team team;
#ManyToOne
#MapsId("userId")
#JoinColumn(name = "user_id")
#JsonIgnoreProperties("team_users")
private User user;
#Column(name = "active")
private int active;
}
Now you can fetch the data in either way without having recursive mapping.

Unable to remove childs in OneToMany Mapping and add new childs [Hibernate]

I know this question has been asked many times but none of the solution is working for me.
So I have a Parent class :
class User{
#Id
#NotNull
#Column(name = "`UserId`", nullable = false)
private Long userId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "`UserId`")
private Set<Phone> phoneList;
}
And a child class:
class Phone {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "`UserId`")
private User user;
}
Now when I received a update for User class with new phone list, I want to remove all the old phones and add new phones. Please note that this all operation is happening in same #Transactional.
Solution I tried:
user.getPhoneList().clear()
user.getPhoneList().addAll(new phone list)
When I try the above logic, Hibernate is trying to set old phone with userId as null. At this position I am getting DataIntegrityViolation as userId in Phone table is non null column.
Please provide any appropriate solution which can work here.
Hhhmmm... I have the exact same logic and it works fine by me. Here are my classes
#Data
#Entity
public class ProductReference {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(mappedBy = "productReference", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE, orphanRemoval = true)
private Set<Attribute> attributes = new HashSet<>();
}
The only difference I see is the CascadeType.REMOVE
#Data
#Entity
public class Attribute {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne
private ProductReference productReference;
}
My deletion:
productReference.getAttributes().clear();
Which Hibernate version you have? by me it is org.hibernate.Version - HHH000412: Hibernate Core {5.4.10.Final}

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;
...
}

Hibernate #ManyToOne cascade children constraint exception

Im facing a little problem here.
I have two entities: Parent and Child, Parent has a List annotated #OneToMany.
The problem is when I try to insert a new Parent, it crashes when persisting the children, because the Parent Id was not generated yet.
Is that a fix for it?
#Entity
#Table(name = "PRODUTO")
public class Parent extends BaseEntity
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID_PRODUTO")
private Integer produtoId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "produtoId", orphanRemoval = true)
// #JoinTable(name = "PRODUTO_TAMANHO", joinColumns = #JoinColumn(name = "ID_PRODUTO"))
#OrderBy("preco ASC")
private List<Child> children;
}
#Entity
#IdClass(Child.PrimaryKey.class)
#Table(name = "PRODUTO_TAMANHO")
public class Child extends BaseEntity
{
public static class PrimaryKey extends BaseEntity
{
private static final long serialVersionUID = -2697749220510151526L;
private Integer parentId;
private String tamanho;
//rest of implementation
}
#Id
#Column(name = "ID_PRODUTO")
private Integer parentId;
#Id
#Column(name = "TAMANHO")
private String tamanho;
#ManyToOne
#JoinColumn(name = "ID_PRODUTO", insertable = false, updatable = false)
private Parent parent;
}
I think if I persist firstly the parent, than persist the children would be a bad approach.
Is that a way to persist the children, when persisting Parent?
Thanks!
Guys, the exception that occurs when persisting Parent is:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'ID_PRODUTO' cannot be null
I found a guy facing the same problem: #OneToMany and composite primary keys? (maybe it's better explained)
Here is my insertion code
Parent parent = new Parent();
Child child1 = new Child();
child1.setTamanho("Tamanho 1");
child1.setParent(parent);
Child child2 = new Child();
child2.setTamanho("Tamanho 1");
child2.setParent(parent);
List<Child> children = parent.getChildren();
children.add(child1);
children.add(child2);
save(parent);
//all of this instances, is coming from a view.jsp binded by spring, I can confirm it is exactly like this, with parentId as null
//when updating, it goes perfectly
There are few problems with your entity class.
mappedBy attribute in Parent entity should be set to parent: mappedBy="parent".
In child entity, below field is not required.
#Id
#Column(name = "ID_PRODUTO", nullable = true)
private Integer parentId;
Updated entity is like this.
#Entity
#Table(name = "PRODUTO")
public class Parent extends BaseEntity
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID_PRODUTO")
private Integer produtoId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "parent", orphanRemoval = true)
// #JoinTable(name = "PRODUTO_TAMANHO", joinColumns = #JoinColumn(name = "ID_PRODUTO"))
#OrderBy("preco ASC")
private List<Child> children;
}
#Entity
#IdClass(Child.PrimaryKey.class)
#Table(name = "PRODUTO_TAMANHO")
public class Child extends BaseEntity
{
public static class PrimaryKey extends BaseEntity
{
private static final long serialVersionUID = -2697749220510151526L;
private Integer parentId;
private String tamanho;
//rest of implementation
}
/* #Id
#Column(name = "ID_PRODUTO", nullable = true)
private Integer parentId; */ // Not required.
#Id
#Column(name = "TAMANHO")
private String tamanho;
#ManyToOne
#JoinColumn(name = "ID_PRODUTO", insertable = false, updatable = false)
private Parent parent;
}
Also I do not understand child inner class for primary key. Use proper primary as you have used parent.
And while inserting set both parent to child and child to parent. See my blog for more details.Here

Categories

Resources