JPA ManyToMany deleting - java

I have two classes:
public class Keyword {
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "keywords")
private Set<Thesis> theses = new HashSet<Thesis>();
}
public class Thesis {
#ManyToMany(cascade = CascadeType.ALL)
private Set<Keyword> keywords = new HashSet<Keyword>();
}
Now I want to be able to delete a keyword and delete a thesis without associeted objects. How shopuld I do that? I tried with #OnDelete(action=OnDeleteAction.NO_ACTION), did not work, I tried with
#PreRemove
void onPreRemove() {
this.getTheses().clear();
this.persist();
this.flush();
}
and didn't sucess. What's the correct way to do it?
Ok, seams my solution to set the Set<> to null was not enought. This way it seams to work:
#PreRemove
void onPreRemove() {
log.debug("in preRemove");
//this.getTheses().clear();
for (Thesis s : this.getTheses()) {
s.getKeywords().remove(this);
s.persist();
s.flush();
}
this.persist();
this.flush();
entityManager.flush();
}

Related

How to properly clone an entity with some modified fields and persist into database?

Lets say I have an entity like this,
#Entity(name = "Post")
#Table(name = "post")
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
FetchType.LAZY
)
#JsonManagedReference
private List<PostComment> comments = new ArrayList<>();
#OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private PostDetails details;
}
I want clone this entity, modified a few fields and then persist the new cloned entity in the database. What's the best approach to achieve this?
You can use a copy using constructor or the builder pattern, or to set the properties using simple setters. Then, persist the new entity instance using the EntityManager's persist() method. To avoid issues with duplicated id values, you must not copy the id field and instead let the JPA generate a new id when the entity is persisted.
Code example
public class Post {
// constructor, getters and setters are omitted
public Post(Post post) {
//Don’t copy id!
this.title = post.getTitle();
this.comments = new ArrayList<>(post.getComments());
this.details = new PostDetails(post.getDetails());
}
}
public class PostDetails {
// constructor, getters and setters are omitted
public PostDetails(PostDetails details) {
// Don’t copy id!
this.description = details.getDescription();
// copy other fields as needed
}
}
// Usage
public class PostsRepository{
#Autowired
EntityManager em;
public void saveCopy(Post originalPost){
Post clonedPost = new Post(originalPost);
em.persist(clonedPost);
}
}
Also I would highly recommend you to write tests in order to check that you are doing everything fine. Moreover you will more space for experiments.

Using cascade.all in a many-to-many with extra columns association

After searching for quite a long time I'm wondering if my code is wrong or if it's simply impossible in Hibernate.
I'll use a fake example to explain my problem. Let's say I have three tables in my database, post, posttag and tag. A post can have multiple tags and a tag can be used my multiple posts, so it's a many-to-many association but there's also extra columns in the table between (posttag).
example entity relationship diagram
So, in my code, I have 3 entity and one more class for the composite key of posttag.
PostEntity:
#Entity
#Table(name = "post", catalog = "fakeExample")
public class PostEntity implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "IDPOST", nullable = false)
private Integer idpost;
#OneToMany(mappedBy = "post", targetEntity = PostTagEntity.class, cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostTagEntity> listOfPostTag = new ArrayList<>();
public void setIdpost(Integer idpost)
{
this.idpost = idpost;
}
public Integer getIdpost()
{
return this.idpost;
}
public void setListOfPostTag(List<PostTagEntity> listOfPostTag)
{
if (listOfPostTag != null)
{
this.listOfPostTag.clear();
this.listOfPostTag.addAll(listOfPostTag);
}
}
public List<PostTagEntity> getListOfPostTag()
{
return this.listOfPostTag;
}
}
TagEntity:
#Entity
#Table(name = "tag", catalog = "fakeExample")
public class TagEntity implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "IDTAG", nullable = false)
private Integer idtag;
#OneToMany(mappedBy = "tag", targetEntity = PostTagEntity.class, cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostTagEntity> listOfPostTag = new ArrayList<>();
public void setIdtag(Integer idtag)
{
this.idtag = idtag;
}
public Integer getIdtag()
{
return this.idtag;
}
public void setListOfPostTag(List<PostTagEntity> listOfPostTag)
{
if (listOfPostTag != null)
{
this.listOfPostTag.clear();
this.listOfPostTag.addAll(listOfPostTag);
}
}
public List<PostTagEntity> getListOfPostTag()
{
return this.listOfPostTag;
}
}
PostTagEntity:
#Entity
#Table(name = "posttag", catalog = "fakeExample")
public class PostTagEntity implements Serializable
{
#EmbeddedId
private PostTagEntityKey compositePrimaryKey = new PostTagEntityKey();
#Column(name = "EXTRACOLUMN")
private Integer extraColumn;
#ManyToOne
#JoinColumn(name = "IDPOST", referencedColumnName = "IDPOST", nullable = false)
#MapsId("idpost")
private PostEntity post;
#ManyToOne
#JoinColumn(name = "IDTAG", referencedColumnName = "IDTAG", nullable = false)
#MapsId("idtag")
private TagEntity tag;
public void setIdpost(Integer idpost)
{
this.compositePrimaryKey.setIdpost(idpost);
}
public Integer getIdpost()
{
return this.compositePrimaryKey.getIdpost();
}
public void setIdtag(Integer idtag)
{
this.compositePrimaryKey.setIdtag(idtag);
}
public Integer getIdtag()
{
return this.compositePrimaryKey.getIdtag();
}
public void setExtraColumn(Integer extraColumn)
{
this.extraColumn = extraColumn;
}
public Integer getExtraColumn()
{
return this.extraColumn;
}
public void setPost(PostEntity post)
{
this.post = post;
}
public PostEntity getPost()
{
return this.post;
}
public void setTag(TagEntity tag)
{
this.tag= tag;
}
public TagEntity getTag()
{
return this.tag;
}
}
PostTagEntityKey:
#Embeddable
public class PostTagEntityKey implements Serializable
{
#Column(name = "IDPOST", nullable = false)
private Integer idpost;
#Column(name = "IDTAG", nullable = false)
private Integer idtag;
public PostTagEntityKey()
{
}
public PostTagEntityKey(Integer idpost, Integer idtag)
{
this.idsalle = idsalle;
this.idequipement = idequipement;
}
public void setIdpost(Integer value)
{
this.idpost = value;
}
public Integer getIdpost()
{
return this.idpost;
}
public void setIdtag(Integer value)
{
this.idtag = value;
}
public Integer getIdtag()
{
return this.idtag;
}
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (this.getClass() != obj.getClass())
{
return false;
}
PostTagEntityKey other = (PostTagEntityKey) obj;
if (this.idpost == null ? other.idpost != null : !this.idpost.equals((Object) other.idpost))
{
return false;
}
if (this.idpost == null ? other.idpost != null
: !this.idtag.equals((Object) other.idtag))
{
return false;
}
return true;
}
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((idpost == null) ? 0 : idpost.hashCode());
result = prime * result + ((idtag == null) ? 0 : idtag.hashCode());
return result;
}
}
Also, I am using Spring, so here are the few class involved when I do an insert or something else. I don't think the problem come from here but just in case.
PostService:
public interface PostService
{
public List<PostEntity> findAll();
public Optional<PostEntity> findById(int var1);
public PostEntity save(PostEntity var1);
public void deleteById(int var1);
}
PostImpl:
#Service
public class PostImpl implements PostService
{
#Autowired
private PostRepository repository;
#Override
public List<PostEntity> findAll()
{
return this.repository.findAll();
}
#Override
public Optional<PostEntity> findById(int id)
{
return this.repository.findById(id);
}
#Override
public PostEntity save(PostEntity toSave)
{
return (PostEntity) this.repository.save(toSave);
}
#Override
public void deleteById(int id)
{
this.repository.deleteById(id);
}
}
PostRepository:
#Repository
public interface PostRepository extends JpaRepository<PostEntity, Integer>, JpaSpecificationExecutor<PostEntity>, PagingAndSortingRepository<PostEntity, Integer>
{
}
So when I need to insert a post, I just use something like this:
#Autowired
PostService postService;
public PostEntity createPost(PostEntity post)
{
return this.postService.save(post);
}
For me, the expected behaviour of Hibernate would be:
when I insert a post, to insert the post and insert every postTag in listOfPostTag
when I update a post, to remove every missing postTag in listOfPostTag, to add every new postTag in listOfPostTag, to update the change postTag in listOfPostTag and to update the post
when I delete a post, to delete every postTag in listOfPostTag and to delete the post
However, when I try to insert a post, I have an error. And from the many tests I've done, it seems that Hibernate insert the post successfully and then tries to insert the postTags, but fails because the idPost in PostTagEntityKey is still null. I would have expected that Hibernate updated it with the id from the inserted post.
So my question is can hibernate do that in the case I described? Or do I have to do it by hand (by not using the cascade mode for insert/update)?
The explanation might be that it's impossible with composite keys, bidirectional, something else or that it's just not something hibernate is supposed to do. I'd like to know if it's possible and if it is, what did I do wrong?
If it's not possible, I wonder what is the point of inserting things in cascade if you can't even do it for a thing as common as an intermediary table.
I haven't tried to code this fake example but I believe it would have the same result as I changed almost nothing from the original. Also I skipped the part where I create the postEntity because in my case it's parsed from JSON. I used the debugger and tried different things, so I'm almost sure the problem doesn't come from here. Every field is filled, even the idTag in PostTagEntityKey. It's just the idPost in PostTagEntityKey that is null because the post hasn't been inserted yet. And hibernate doesn't update it after inserting the post. I start to believe there's no way for the cascade mode to update it and maybe it's the case.
EDIT :
So, thanks to the comment of #a.ghavidel, I realised I've never tried to set the post in posttag.
So I've changed the function setListOfPostTag in post entity like this :
public void setListOfPostTag(List<PostTagEntity> listOfPostTag)
{
if (listOfPostTag != null)
{
this.listOfPostTag.clear();
for(PostTagEntity postTag : listOfPostTag)
{
postTag.setPost(this);
this.listOfPostTag.add(postTag);
}
}
}
and the problem has changed as well. Before that modification, it was telling me that it couldn't insert postTag because idPost was null. And now it's telling me "org.hibernate.PersistentObjectException: detached entity passed to persist: com.fakeexample.TagEntity".
So I think, to retreive the primary key, the entity postTag needed the variable Post to be set. The List listOfPostTag in Post wasn't enough. So my original problem is now fixed I think.
My new problem is that hibernate seems to consider that the tag in postTag is "detached" and I'm not sure what that mean.
In my case, the tag in postTag doesn't come from a function of hibernate, it has been parsed from json so maybe that's why. However I don't need it to be persisted, in theory I just need its id to insert the postTag and there is no cascade in postTag.
I've tried replacing CascadeType.All with CascadeType.Merge cause some people said it worked for them but when I do this it just doesn't insert any postTag when I insert a post. However it seems to work very well when I update a post.
I think I'm very close to the solution. I'm going to try a few things and I will edit this post if I find the answer.
EDIT 2 :
So, I've tried a few things and made some progress. The object is detached because it hasn't been created by hibernate. There is no problems during a merge but it can't be persisted.
A solution might be to do everything by hand in the create function...
But the proper solution would be to use the function getReference() from entityManager. It doesn't generate any unwanted select or update, it just create a proxy object and only require the id in parameter.
However the entityManager is not accessible in spring I think, but we can use the function getOne() from a repository which is mapped to the getReference() method. Basically, if I understood correctly, the function getOne() is supposed to be using lazy loading, so the object isn't loaded as long as we don't need it to be loaded.
I tried to use this function and indeed my code is now working correctly.
But the problem is : the function getOne() is deprecated.
The function has been replaced by getById() but I'm really not sure it use lazy loading too because I'm already using this function a lot and not to create proxy object at all. Also, I know the attribute "fetch = FetchType.LAZY" cant be put in #OneToMany, so, if I didn't put it in my code I suppose I'm not using lazy loading and it will do a lot of unwanted select. Also I don't think I should be using lazy loading all the time neither, I heard it can be troublesome by sometime generating one select for each entity of a collection instead of a single select with lazy loading...
So I still need to make some research to know how to do it with non derprecated function.
EDIT 3 :
Okay so no, the function I was using was findById and not getById. So getById is probably the solution to my problem. I'm gona check the doc to confirm and test it out.
FINAL EDIT :
Well it's working for the insert but I have now a problem during the update. Basically it tells me the posttag already exists in the DB. Probably because I replace the listOfPostTag from the findById with a list I created myself by parsing it. The solution is probably to edit the objects in this list. Now that I understand how the things work in hibernate I need to review the entirety of my code to apply these principles.
In the end, it seems that hibernate is not very friendly restful apis and we have to process every object so hibernate can recognize them.
To summerize, my problems were :
first : consistency. I didn't set the post in postTag object.
Second : attach and detach objects. I didn't used getById function to create an Object recognized by hibernate for the Taf Object.
Third : updating. When I updated a post, I didn't update the objects in listOfPostTag, I just replaced it with another list, with only objects not recognized by hibernate. I should have updated the list Object by Object I guess.
You should use this method in PostEntity when you are creating one
private void addPostTag(PostTagEntity postTag){
postTag.setPost(this);
this.listOfPostTag.add(postTag);
}
I think this will fix your problem

Spring data JPA #PreRemove ConcurrentModificationException when removing from parent enity

I have a case where a participant can register courses.
Basically I have the following entity configuration (getters and setters omitted as well as other useless properties) :
#Entity
#Table(name = "course")
public class Course {
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "course")
private Set<Registration> registrations;
}
#Entity
#Table(name = "participant")
public class Participant {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "participant")
private Set<Registration> registrations;
}
#Entity
#Table(name = "registration")
public class Registration {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "course_id")
private Course course;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "participant_id")
private Participant participant;
#PreRemove
private void removeRegistrationFromHolderEntities() {
course.getRegistrations().remove(this);
participant.getRegistrations().remove(this);
}
}
Then I can from my viewmodel delete a registration or a course (I have also removed unnecessary stuff) :
#Command
public void deleteRegistration(Registration reg) {
registrationMgr.delete(reg);
}
#Command
public void deleteCourse(Course crs) {
courseMgr.delete(crs);
}
Problem :
If I delete a registration, I need the #PreRemove function so I can remove the references. Without this the remove is ignored (no error, simply ignored)
If I delete a course, I have to remove the #PreRemove function else I get a ConcurrentModificationException (evidently...)
I also cannot remove references from the deleteRegistration method (instead of #PreRemove) because participant registrations are lazily loaded (would raise failed to lazily initialize a collection of role: ..., could not initialize proxy - no Session exception).
What is the best approach here ?
I use Java 11 with Spring Boot 1.0.4 (and spring-boot-starter-data-jpa).
EDIT :
The managers/repositories or defined this way (same for registration and participant) so it should be transactional (I don't have #EnableTransactionManagement on my main class but it should not be required as I don't use transactions outside of repositories) :
#Transactional
#Component("courseMgr")
public class CourseManager {
#Autowired
CourseRepository courseRepository;
public void saveOrUpdate(Course course) {
courseRepository.save(course);
}
public void delete(Course course) {
courseRepository.delete(course);
}
}
public interface CourseRepository extends CrudRepository<Course, Long> {
...
}
EDIT2 :
I think I have found a pretty simple solution :
I have removed the #PreRemove method from the entity, then instead of removing the references like this in the deleteRegistration method (which I had tried but was causing failed to lazily initialize a collection of role exception) :
#Command
public void deleteRegistration(Registration reg) {
reg.getCourse().getRegistrations().remove(reg);
reg.getParticipant.getRegistrations().remove(reg);
registrationMgr.delete(reg);
}
I simply set parents to null, I don't care as it will be deleted...
#Command
public void deleteRegistration(Registration reg) {
reg.setCourse(null);
reg.setParticipant(null);
registrationMgr.delete(reg);
}
So now I can also delete a course without triggering the ConcurrentModificationException in the #PreRemove.
EDIT3 : My bad, registration was not removed with the solution above (still no error but nothing happens). I ended with this instead, which finally works :
#Command
public void deleteRegistration(Registration reg) {
// remove reference from course, else delete does nothing
Course c = getRegistration().getCourse();
c.getRegistrations().remove(getRegistration());
courseMgr.saveOrUpdate(c);
// delete registration from the database
registrationMgr.delete(reg);
}
No need to remove reference from participant...
You have setup your repositories incorrectly. You need a composite PK for Registration and you need to understand that bidirectional mappings are really for query only. Further, bidirectional mappings in Course and Participate present challenges because the ManyToOne relationship through the Registration entity is FetchType.EAGER by default. With all the cascade and fetch annotations you have you are asking for a complicated combination of things from JPA and it seems like you really haven't sorted it all out yet. Start with the basics, be sure to print your SQL statements, and proceed from there if you want to try to finesse more from JPA.
#Entity
#Data
public class Course {
#Id
private Integer id;
private String name;
}
#Entity
#Data
public class Participant {
#Id
private Integer id;
private String name;
}
#Entity
#Data
public class Registration {
#EmbeddedId
private RegistrationPK id;
#ManyToOne
#MapsId("participant_id")
private Participant participant;
#ManyToOne
#MapsId("course_id")
private Course course;
}
#Embeddable
#Data
public class RegistrationPK implements Serializable {
private static final long serialVersionUID = 1L;
private Integer course_id;
private Integer participant_id;
}
Is your basic Entities. The RegistrationRepository needs an additional query.
public interface RegistrationRepository extends JpaRepository<Registration, RegistrationPK> {
Set<Registration> findByCourse(Course c);
}
And to use all this in an example:
#Override
public void run(String... args) throws Exception {
create();
Course c = courseRepo.getOne(1);
Set<Registration> rs = read(c);
System.out.println(rs);
deleteCourse(c);
}
private void create() {
Course c1 = new Course();
c1.setId(1);
c1.setName("c1");
courseRepo.save(c1);
Participant p1 = new Participant();
p1.setId(1);
p1.setName("p1");
participantRepo.save(p1);
Registration r1 = new Registration();
r1.setId(new RegistrationPK());
r1.setCourse(c1);
r1.setParticipant(p1);
registrationRepo.save(r1);
}
private Set<Registration> read(Course c) {
return registrationRepo.findByCourse(c);
}
private void deleteCourse(Course c) {
registrationRepo.deleteAll( registrationRepo.findByCourse(c) );
courseRepo.delete(c);
}
OK solution was pretty simple.
I indeed need to remove the references from the deleteRegistration method. This is what I had tried but was causing failed to lazily initialize a collection of role exception :
#Command
public void deleteRegistration(Registration reg) {
reg.getCourse().getRegistrations().remove(reg);
reg.getParticipant.getRegistrations().remove(reg);
registrationMgr.delete(reg);
}
The trick is that I also have to save the course entity before trying to delete the registration.
This works :
#Command
public void deleteRegistration(Registration reg) {
// remove reference from course, else delete does nothing
Course c = getRegistration().getCourse();
c.getRegistrations().remove(getRegistration());
courseMgr.saveOrUpdate(c);
// delete registration from the database
registrationMgr.delete(reg);
}
No need to remove reference from participant...
#PreRemove was doing the job, but that way I can now also delete a course without triggering the ConcurrentModificationException.

persist JavaFX-ObservableList with JPA

I'm trying to persist an ObservableList with JPA using the eclipselink API Version 2.6 with derby. JPA needs POJOs to persist. For JavaFX-Properties I can use this:
#Transient // avoid persisting
private StringProperty street = new SimpleStringProperty();
#Access(AccessType.PROPERTY) //
public String getStreet() {
return street.get();
}
public void setStreet(final String newStreet) {
street.set(newStreet);
}
Works fine!
Well, the first guess is to use it in the same way for an ObservableList. My first try was to use a normal List:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Address> adrList;
public List<Address> getAdrList() {
return adrList;
}
public void setAdrList(final List<Address> adrList) {
this.adrList = adrList;
}
Works well! Let's change the List to an ObservableList. According to the StringProperty, I would use something like this:
private ObservableList<Address> adrList;
#Access(AccessType.PROPERTY)
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public List<Address> getAdrList() {
return adrList;
}
public void setAdrList(final List<Address> adrList) {
this.adrList.setAll(adrList);
}
This works until the point I access the adrList. I get a NullPointerException cause the adrList is Null.
My Question is: How can I use my ObervableList with JPA? I thought I could cast the ObservableList to a List as long it's just an Implementation of List. But It seems not to work. Has anyone an idea how to solve this problem?
Thanks in advance! :)

JPA delete fails (integrity constraint violation: foreign key no action) - data model too convoluted?

I'm experiencing a problem I cannot get my head around. I really hope someone out there can help.
Entity model
This might get a little meta as my data model represents programming objects. I've got three entities: Program, Clazz and Method. Classes can have multiple methods and occasionally inherited some other methods (from other classes).
#Entity
public class Program {
#OneToMany(mappedBy="program", fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#OrderBy("name ASC")
private Set<Clazz> clazzes = new TreeSet<Clazz>();
}
#Entity
public class Clazz {
#ManyToOne
private Program program;
#OneToMany(mappedBy="clazz", fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#OrderBy("name ASC")
private Set<Method> methods = new TreeSet<Method>();
#ManyToMany(fetch=FetchType.LAZY, mappedBy="inheritedBy")
#OrderBy("name ASC")
private Set<Method> inheritedMethods = new TreeSet<Method>();
#PreRemove
private void tearDown() {
inheritedMethods.clear();
}
}
#Entity #Table(name="sf_method")
public class Method {
#ManyToOne(optional=true)
private Clazz clazz;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name = "sf_method_inherited_by_class",
joinColumns = { #JoinColumn(name = "sf_method_inherited_by_class") },
inverseJoinColumns = { #JoinColumn(name = "sf_class_inherits_method") }
)
private Set<Clazz> inheritedBy = new HashSet<Clazz>();
#PreRemove
public void tearDown() {
inheritedBy.clear();
}
}
My problem: what I do
What I need to do is to delete a program, including all its classes and methods. I use Spring-JPA repositories, so my code is:
#Autowired ProgramRepository programs;
#Transactional(readOnly=false)
public void forceDelete(Program p) {
Set<Clazz> myClasses = p.getClazzes();
for (Clazz c : myClasses) {
logger.info("Clazz " + c.getName());
for (Method m : c.getMethods()) {
m.setClazz(null);
methods.save(m);
}
}
programs.delete(p);
}
What happens
The delete code works only in some circumstances. Sometimes I get the following error.
SqlExceptionHelper - integrity constraint violation: foreign key no
action; FKIBMDD2FV8TNJAF4VJTOEICS73 table: SF_METHOD AbstractBatchImpl
- HHH000010: On release of batch it still contained JDBC statements
Any idea? I spent so many hours on this and I failed every single resolution attempt. What am I doing wrong?
Here you are trying to delete the parent without deleting the child which have foreign key referencing to the parent.
Here, before you delete p you have to delete children for p.
You can get that by :
p.getClazzes();
Again before deleting Clazz, you have to delete children for it(in this case, Method) as above...

Categories