How can I save ordered collection in JPA/Hibernate? - spring

I have an main entity:
#Data
#Entity
#Table(name = "MAINS")
public class Main {
...
#OneToMany(mappedBy = "main", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = LAZY)
private List<Chield> children;
And I have a child entity:
#Data
#Entity
#Table(name = "CHILDS")
public class Child {
#Id
#Column(name = "GUID")
private String guid;
#Column(name = "NAME")
private String name;
#Column(name = "Age")
private Integer age;
And I try to save one Main with many Child:
List<Child> children = new ArrayList<>();
for (int i = 0, i< 10; i++) {
Child child = new Child();
child.setId(i);
child.setName("Name"+i);
child.setAge(10+i);
children.add(child);
}
main.setChildren(children);
MainRepository.save(main);
But I need to save these children ordered. I can add a new column to Child - order and set child.setOrder(order++); and when I select this list I can sort it by order field.
But can I do it differently? Without adding of the new column?

If I get your question correctly it could be helpful to check such options:
OrderColumn
#OrderColumn(name = "index_id")
private List<Child> changes = new ArrayList<>();
OR
#OrderBy("id")
private Set<Child>list = new LinkedHashSet<>();
OR
#SortNatural
private SortedSet<Child> children = new TreeSet<Child>();
DOCS: naturalSort
OR for custom comparator: #SortComparator(SortById.class)
DOCS: customSort
public class SortById implements Comparator<Child> {
Logger log = Logger.getLogger(SortById.class.getSimpleName());
#Override
public int compare(Child o1, Child o2) {
log.info("Child.compare");
return o1.getId().compareTo(o2.getId());
}
}

Related

ManyToOne with jsonb Colum

There are my classes:
Parent
#Entity
#NoArgsConstructor
#Data
#AllArgsConstructor
public class Parent {
#Id
private String id;
private String name;
private String ssn;
#Valid
#Type(type = "json")
#Column(columnDefinition = "jsonb")
#OneToMany(mappedBy="parent ", cascade = CascadeType.ALL)
private List<Child> children;
}
Child
#Entity
#NoArgsConstructor
#Data
#AllArgsConstructor
public class Child implements Serializable {
#Id
private String name;
private String parentId;
#Valid
#Type(type = "json")
#Column(columnDefinition = "jsonb")
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "parentId", referencedColumnName = "id", insertable = false, updatable = false)
private Parent parent;
}
Parent Mapper
#Mapper
public interface ParentMapper {
#Mapping(target = "id", expression = "java(setId(parent.getName(), parent.getSsn()))")
ParentEntity toParentEntity(CreateParent parent);
Parent toParent(ParentEntity parentEntity);
default String setId(String name, String ssn) {
if (StringUtils.isNotEmpty(name) && StringUtils.isNotEmpty(ssn)) {
return String.format("%s+%s", name, ssn);
} else {
return null;
}
}
}
child mapper
#Mapper
public interface ChildMapper {
ChildEntity toChildEntity(CreateChild child, String parentId, String chaildName);
Child toChild(ChildEntity childEntity);
}
so there is an issue, creating and saving child object doesn't persist parent property of child object
Parent p = new Parent("somename","123456");
parentRepository.save(p);
Child c = new Child("somename");
c.setParentId(p.getId());
ChildEntity saved = childRepository.save(c);
saved.getParent() => returns null
ParentEntity savedParent = parentRepository.findById(p.getId());
savedParent.getChildren() => return null
Not sure what i done wrong ...

Hibernate - Spring - ConstraintViolationException - UniqueConstraint

I'm trying to make some fixtures for my Profile model but every time I'm trying to save it "again" after I did an update, I get this message:
nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
This is my Profile class:
#Entity
#Data
#Builder
#ToString(of = {"birthday", "discordId", "description", "spokenLanguages"})
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "profile", uniqueConstraints = #UniqueConstraint(columnNames = "discordId"))
public class Profile implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idProfile;
private Date birthday;
#Column(name="discordId", insertable=true, updatable=false)
private String discordId;
private String description;
#ElementCollection(fetch = FetchType.EAGER)
private Set<String> spokenLanguages = new LinkedHashSet<String>();
#JsonIgnore
#OneToMany(fetch = FetchType.EAGER)
private Set<ProfileGame> profileGames = new LinkedHashSet<>();
#OneToOne(mappedBy = "profile", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
private User user;
#ManyToOne
private TimeSlot timeSlot;
}
Here is the call:
#Order(7)
#Test
void fillProfileGame() {
List<Profile> profileList = this.profileRepository.findAll();
for (Profile profile : profileList) {
List<Game> gameList = this.gameRepository.findAll();
Collections.shuffle(gameList);
int rndNbGame = new Random().ints(1, 5).findFirst().getAsInt();
for (int i = 1; i <= rndNbGame; i++) {
int rndLevel = new Random().ints(1, 100).findFirst().getAsInt();
int rndRanking = new Random().ints(1, 3000).findFirst().getAsInt();
Game rndGame = gameList.get(0);
gameList.remove(0);
ProfileGame profileGames = new ProfileGame(profile, rndGame, "level-" + rndLevel,
"ranking-" + rndRanking);
this.profileGameRepository.save(profileGames);
this.gameRepository.save(rndGame);
}
this.profileRepository.save(profile);
}
}
So what I understand is that Hibernate won't let me update this object because it has a unique contraint field ?
How do we proceed when we want a field to be unique and still being able to update other fields ?
From the code snippet, what I see is that there are some unique constraints applied on the column 'discordId'.
#Table(name = "profile", uniqueConstraints = #UniqueConstraint(columnNames = "discordId"))
and
#Column(name="discordId", insertable=true, updatable=false)
private String discordId;
As you can see, there is a parameter 'updatable' which is set to false. Therefore, when you are trying to update an already existing object, hibernate is throwing UniqueConstraintViolationException.
To fix this, set 'updatable=true' or remove it altogether and it should work fine.
#Column(name="discordId", insertable=true, updatable=true)
private String discordId;

can i retrieve child entity containing parent entity id by using Spring Data Jpa one-to-many unidirectional relationship

Here is my parent entity:
#Entity
public class Parent {
#Id
int parentId;
String name;
#OneToMany()
#JoinColumn(name="parent_id")
List<Child> childList;
}
public class Child {
int childId;
//if i am taking this property as non-transient application won't run. but i need parent Id without changing the class structure..
#Transient
int parentId;
// ... some other properties
}
Insertion is successful as two tables are created : parent(id,name),
child(id,name,parent_id).
But when I retrieve the Parent record then in the Child object, the
parentId property remains 0.
i found a way to retrieve the parentid from the child entity by doing a bidirectional mapping of the Parent-child relationship. You can get the ParentId by using a getter method from the child entity that returns the parentId.
Parent Entity
#Entity
#Table(name = "Parent")
public class Parent implements Serializable {
#Column(name = "ID", nullable = false, length = 10)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int Id;
#Column(name = "Name")
public String name;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private Set<Child> children = new HashSet<>();
public Parent() {
}
//getters and setters omitted for brevity
}
//Child Entity
#Entity
#Table(name = "Child")
public class Child implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int Id;
#Column(name = "Name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parentId", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
public Child() {
}
public int getId() {
return Id;
}
public void setId(int id) {
Id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonIgnore
public Parent getParent() {
return parent;
}
//getter method to retrieve the parent id in the child entity
public int getParent_Id(){
return parent.getId();
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
Notice the method getParent_Id() which returns the parent Id, since getter methods are used to return object, the parentId would be returned as part of the child entity anytime it is fetched.
Also note the use of #JsonIgnore on the getParent() method, this to avoid an infinite recursion going on during serialization since Parent refers to Child and Child refer to Parent.

Spring Data JPA - ManyToOne unable to delete child without modifying parent list

I struggle for a week with the following problem:
How is it possible to delete a child entity through a repository without modifying the List on the owning (parent) side of the relation?
Thanks in advance.
I am hoping for some answers!
The child class:
#Entity
#Table(name = "child")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#ManyToOne
private Parent parent;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
And the parent class:
#Entity
#Table(name = "parent")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Child> children = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<Child> getChildren() {
return children;
}
public void setChildren(Set<Child> children) {
this.children = children;
}
public Parent addChild(Child child) {
this.children.add(child);
child.setParent(this);
return this;
}
public Parent removeChild(Child child) {
this.children.remove(child);
child.setParent(null);
return this;
}
}
And here the test:
#Test
#Transactional
public void testParentToChildRelationShip() {
Parent parent = new Parent();
Child child = new Child();
parent.addChild(child);
parent.addChild(new Child());
parent.addChild(new Child());
parent.addChild(new Child());
parentRepository.save(parent);
Assertions.assertThat(parentRepository.count()).isEqualTo(1L);
Assertions.assertThat(childRepository.count()).isEqualTo(4L);
childRepository.delete(child);
Assertions.assertThat(parentRepository.count()).isEqualTo(1L);
// fails
Assertions.assertThat(childRepository.count()).isEqualTo(3L);
parentRepository.delete(parent.getId());
Assertions.assertThat(parentRepository.count()).isEqualTo(0L);
Assertions.assertThat(childRepository.count()).isEqualTo(0L);
}
The test would work if I insert before deleting the child,
child.getParent().removeChild(child);
but I want to avoid calling this.
Is there a way to make it work with just calling the Child-JPA-Repository.delete method? Or other annotations that I missed?
Since child has association with parent you are facing this issue, you need to remove the link between child and parent either using
parent.removeChild(child);
or
child.getParent().removeChild(child);
Remove these lines from your parent class and also setter and getter of children
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Child> children = new HashSet<>();
I think you can remove child mapping from your parent class so you can easily delete the child row using ChildRepository delete() method but problem is that you have to save your child manually using ChildRepository save(). You can not save child object with parent object using ParentRepository. Change your Test code like below for saving child and parent
Parent parent = new Parent();
Parent parent = parentRepository.save(parent);
Child child = new Child();
child.setParent(parent);
childRepository.save(child);

MIssing parent reference in a bidirectional hibernate mapping

I have a spring rest backend with two entities with a bidirectional relationshop (one-to-many, many to one). To overcome nested fetching issues, #JsonManagedReference/#JsonBackReference has been used for a perent/child relationship between entities.
The entites look as this:
#Entity
#Table(name = "Parent")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Parent implements java.io.Serializable {
private Integer id;
private List<Child> childList;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
#JsonManagedReference
public List<Child> getChildList() {
return childList;
}
public void setChildListe(List<Child> childListe) {
this.childList = childList;
}
}
#Entity
#Table(name = "Child")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Child implements java.io.Serializable {
private Integer id;
private Parent parent;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ParentID")
#JsonBackReference
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
This works fine when fetching the Parent element, the childset is then fetched alongside and displayed as an json-array.
However, there is no reference to parent in the child element due to the usage of jsonbackreferance.
How can solve this issue ? I need parent reference when fetching child
That would lead to an infinite loop when serializing to JSON. That's the whole reason we don't do bi-direction JSON relationships.
What I would do is add an additional column to the child entity if you need the ID alone.
private Integer parentId;
#Column(name = "ParentID", insertable=false, updateable=false)
public Integer getParentId() {
return parentId;
}

Resources