I have a confusing problem which I haven't figured out how to solve. if you can offer a suggestion of how I can fix my problem I would be grateful.
So I have the following entity relationship model here.
The mapping of User.class is:
#Entity
#Table(name = "CRM_USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USER_ID")
private Long id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "BIRTHDATE")
private Date birthDate;
#Column(name = "EMAIL")
private String email;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private UserAdditionalInfo additionalInfo;
#ManyToOne
#JoinColumn(name = "TEAM_FK")
private Team team;
#ManyToOne
#JoinColumn(name = "JOB_FK")
private Job job;
#ManyToOne
#JoinColumn(name = "ORGANIZATION_FK", nullable = false)
private Organization organization;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Security security;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "INFO_FILE_FK")
private InfoFile profilePicture;
#ManyToOne
#JoinColumn(name = "COUNTRY_FK")
private Country country;
// Getters and Setters
}
The mapping of Comment.class is:
#Entity
#Table(name = "CRM_COMMENT")
public class Comment implements Serializable {
private static final long serialVersionUID = -104145851368148154L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "COMMENT_ID")
private Long id;
#ManyToOne
#JoinColumn(name = "ARTICLE_ID")
private Article article;
#ManyToOne
#JoinColumn(name = "USER_FK", nullable = false)
private User createdUser;
#Column(nullable = false)
private String comment;
#Column(name = "CREATION_DATE", nullable = false)
private Date creationDate;
#Column(name = "MODIFICATION_DATE")
private Date modificationDate;
#OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true)
#JoinTable(name = "CRM_COMMENT_LIKE",
joinColumns = {#JoinColumn(name = "COMMENT_ID")},
inverseJoinColumns = {#JoinColumn(name = "USER_ID")})
private Set<User> fans = new LinkedHashSet<>();
// Getters and Setters
}
The mapping of Article.class is:
#Entity
#Table(name = "CRM_ARTICLE")
public class Article implements Serializable {
// other properties
#OneToMany(mappedBy = "article", cascade = {CascadeType.ALL}, orphanRemoval = true)
#OrderBy("id DESC")
private Set<Comment> comments = new LinkedHashSet<>();
// Getters and Setters
}
The problem is related to my ManyToMany relation between the Comment and User - CRM_COMMENT_LIKE.
Actually, when I add some new 'fan' into Comment, there is no problem.
#Override
public boolean giveAnLikeToComment(Long commentId, User fan) {
Comment comment = commentDao.get(commentId);
if (Objects.isNull(comment)|| BooleanUtils.isTrue(comment.getFans().contains(fan))) {
return false;
}
comment.getFans().add(fan);
commentDao.update(comment);
return true;
}
The problem arises when I try to delete some comment, which has at least one 'like'/'fan' to it.
#Override
public boolean deleteCommentById(final Long commentId) {
Comment comment = commentDao.get(commentId);
if (Objects.nonNull(comment)) {
Article article = comment.getArticle();
article.getComments().remove(comment);
comment.setFans(null); // This line fix the problem
articleDao.update(article);
return true;
}
return false;
}
So in this case, I manage the relation between an Article ( which is parent of a Comment) and the comment itself. This is easy, because the connection between them is bidirectional. But what about the fans? I can't remove the connection between a Comment and CRM_COMMENT_LIKE relation, because the User doesn't know about the CRM_COMMENT_LIKE or about the Comments. Something more, I want, when I remove a Comment, to remove and all created relations in CRM_COMMENT_LIKE. But I'm prevent, because Hibernate throws an exception which says:
deleted object would be re-saved by cascade (remove deleted object from associations):
[crm.alltogether.core.admin.model.User#1]; nested exception is
org.hibernate.ObjectDeletedException: deleted object would be re-saved
by cascade (remove deleted object from associations):
[crm.alltogether.core.admin.model.User#1]
This is my issue, so if you have a suggestion, I would be glad to read it :)
Best Regards,
You need to have a orphanRemoval=true cascading between Article and Comment. Then you would do this.
if (Objects.nonNull(comment)) {
Article a = comment.getArticle();
a.getComments().remove(comment);
articleDao.saveOrUpdate(a);
return true;
}
This will take care of deleting orphan comment, as you already have a cascade all on fans it would delete that association as well. I would suggest you to play around with cascade orphanRemoval=true on fans as well.
Related
I have two entities BookingLegEntity and BookingEntity which reference each other. But anytime I try to retrieve them from the database (e.g. via findByUuid), BookingLegEntity.belongsTo remains null.
Here are my entities:
#Entity
#Table(name = "BOOKING_LEG")
#SQLDelete(sql = "UPDATE BOOKING_LEG SET deleted = true WHERE id=?")
#Where(clause = "deleted=false")
public class BookingLegEntity {
#Id
#Column(name = "ID", unique = true, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "UUID", nullable = false)
private UUID uuid;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "BELONGS_TO")
private BookingEntity belongsTo;
// ..
#ManyToOne
#JoinColumn(name = "DISTRIBUTOR")
private DistributorEntity distributor;
#Column(name = "TRANSPORT_TYPE")
#Convert(converter = TripTypeEnumConverter.class)
private TripTypeEnum transportType;
// ...
}
#Entity
#Table(name="BOOKINGS")
#SQLDelete(sql = "UPDATE BOOKINGS SET deleted = true WHERE id=?")
#Where(clause = "deleted=false")
public class BookingEntity {
#Id
#Column(name="ID", unique=true, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name="BOOKING_ID")
#Convert(converter = BookingIdConverter.class)
private BookingId bookingId;
#ManyToOne
#JoinColumn(name ="BOOKED_BY")
private UserEntity bookedBy;
// ..
#OneToMany(mappedBy = "belongsTo", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<BookingLegEntity> bookingLegs = new HashSet<>();
// ...
}
Here is my repository:
#Repository
public interface BookingLegRepository extends JpaRepository<BookingLegEntity, Long> {
Optional<BookingLegEntity> findByUuid(UUID id);
// ...
}
The values in the database itself look correct:
What is really strange is that this has worked before (belongsTo was not null) but suddenly stopped working. Does anyone has any idea as to what we might do wrong here?
Do not use cascade = CASCADEType.ALL on your ManyToOne annotation, because removing one BookingLeg will cause a removal of all in corresponding Booking
The solution should be to use
cascade = CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}) in its stead.
I would Truncate Cascade or Delete from Bookings where original_itinerary is null before i move on to using the new entities.
Sincerely hope it helps. (No hate if it doesn't pls)
Edit : i didnt see that comment by #dey, its my own. :P saw his comment after posting my ans
I'm stuck at deal with this problem. I have 'Review Entity', and 'Heart Entitiy'. And I tried to show them homepage and detailpage separately!
Long countHeartByBookReviewId(Long bookReview_id);
i used jpa query method for showing how many heart it gets in details page..
and now i want to show review descending related to heart count in main page!
how can i make the code..?
#Entity
public class BookReview extends Timestamped {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
...
#Column
private String review;
#JoinColumn(name = "member_id", nullable = false)
#ManyToOne(fetch = FetchType.EAGER)
private Member member;
#OneToMany(mappedBy = "bookReview" , cascade = CascadeType.REMOVE)
private List<Comment> comment;
#JsonIgnore
#OneToMany(mappedBy = "bookReview", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Heart> heart;
and the other entitiy is here.
public class Heart {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bookReview_id")
private BookReview bookReview;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "member_id")
private Member member;
and this is function for get menthod...
public ResponseDto<?> getHome() {
List<BookReview> book_review = book_reviewRepository.findAllByOrderByHeartDesc();
List<HomeResponseDto> book_reviewResponseDtoList = new ArrayList<>();
for (BookReview home : book_review) {
book_reviewResponseDtoList.add(HomeResponseDto.builder()
.id(home.getId())
.username(home.getMember().getUsername())
.thumbnail(home.getThumbnail())
.title(home.getTitle())
.author(home.getAuthor())
.publisher(home.getPublisher())
.review(home.getReview())
.heart(heartRepository.countHeartByBookReviewId(home.getId()))
.createdAt(home.getCreatedAt())
.modifiedAt(home.getModifiedAt())
.build()
);
}
return ResponseDto.success(book_reviewResponseDtoList);
}
please help me ......
Am doing one small activity of Teach and address relationship for one to many and in address block there will be one to one relationship between country, district, tahasil etc. Whenever am hitting api and to save it it's not updating or inserting Address in address table.
Detail is
#Entity
#Table(name = "teachers")
#PrimaryKeyJoinColumn(name = "user_id")
public class Teacher extends User {
#Size(min = 3, max = 50)
#Column(name = "first_name")
private String firstName;
#Size(min = 3, max = 50)
#Column(name = "middle_name")
private String middleName;
#Size(min = 3, max = 50)
#Column(name = "last_name")
private String lastName;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "teacher")
#JsonManagedReference
private Set<Address> addresses = new HashSet<>(0);
Getter Setter...
}
Then Address Entity
#Entity
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "address_id")
private Long addressId;
#ManyToOne (fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
#JsonBackReference
private Teacher teacher;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "country_id", referencedColumnName = "country_id")
private Country country;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "state_id", referencedColumnName = "state_id")
private State state;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "district_id", referencedColumnName = "district_id")
private District district;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "tahasil_id", referencedColumnName = "tahasil_id")
private Tahasil tahasil;
#Column(name = "line_one")
private String lineOne;
#Column(name = "line_two")
private String lineTwo;
#Column(name = "landmark")
private String landmark;
#Column(name = "pincode")
private Integer pincode;
public Country getCountry() {
return country;
}
Other Getter Setter
The Country example same to state, district and tahasil
#Entity
#Table(name = "countries", uniqueConstraints = { #UniqueConstraint(columnNames = { "country_name" }) })
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "country_id")
private Long countryId;
#NotBlank
#Size(min = 3, max = 50)
#Column(name = "country_name")
private String countryName;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "country")
private Address address;
Getter Setter
Finally in controller am doing like
Optional<Teacher> teacher = teacherRepo.findByUserId(id);
if (!teacher.isPresent())
return ResponseEntity.notFound().build();
teacher.get().setUserId(id);
teacher.get().setFirstName(teacherUpdateForm.getFirstName());
teacher.get().setMiddleName(teacherUpdateForm.getMiddleName());
teacher.get().setLastName(teacherUpdateForm.getLastName());
teacher.get().setAddresses(teacherUpdateForm.getAddresses());
userRepository.save(teacher.get());
Tried so may ways by referring multiple sites and readouts, but still not able to see any insert or update to address table. Please help me to get my mistake.
Regards,
Chetan
You need to cascade the persist of the Teacher entity.
Update the definition of the attribute Address inside the Teacher entity:
#OneToMany(fetch = FetchType.LAZY,mappedBy = "teacher", cascade = CascadeType.ALL)
#JsonManagedReference
private Set<Address> addresses = new HashSet();
You can play with the cascade type value as you want.
I'm having a problem to map a relationship between instances of one single entity. Let me give you the JPA entities first.
Article entity
#Entity
#Table(name = "article")
public class Article {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "article", orphanRemoval = true)
private Collection<Keyword> keywords;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "article1", orphanRemoval = true)
private Collection<RelatedArticles> relatedArticles;
#Column(name = "content", nullable = false)
#Lob
private String content;
...
}
RelatedArticle entity
#Entity
#Table(name = "related_articles")
public class RelatedArticles {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#JoinColumn(name = "article1_id")
#ManyToOne(optional = false)
private Article article1;
#JoinColumn(name = "article2_id")
#ManyToOne(optional = false)
private Article article2;
private Float weightedJaccardIndex;
...
}
Further explanation
An article can be related to other articles which is realized by the RelatedArticle entity. The article can be referenced by article1 or article2. That means the collection relatedArticles in Article should contain all instances of RelatedArticle where the ID either matches article1 or article2.
Question
How can I map a single collection of RelatedArticles in my Article entity where the origin Article is either article1 or article2?
Alternative solutions are welcome!
I have two classes Student and Subject:
Student
#Entity
#Table(name = "students")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "full_name")
private String fullName;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "students")
private Set<Subject> subject;
//Getters & setters
}
Subject
#Entity
#Table(name = "subjects")
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "subject_name")
private String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Student> students;
//Getters & setters
}
When I am trying to save a student registered with more than one subject, I am not getting any records in the JoinTable.
My code for saving the entiers:
Subject subject = new Subject();
subject.setName("JAVA");
Subject subject2 = new Subject();
subject2.setName("C");
Subject subject3 = new Subject();
subject3.setName("C++");
Student student = new Student();
student.setFullName("dibya");
Set<Subject> subjects = new HashSet<>();
subjects.add(subject2);
subjects.add(subject);
subjects.add(subject3);
student.setSubject(subjects);
session.saveOrUpdate(student);
Please tell me where am I doing wrong.
Found the solution to my problem.
It was not with the configuration. With just mappedBy attribute ManyToMany relationship can be accomplished.
In my program I have added session.saveOrUpdate(subject); after saving saving the students.
This solved my problem. :)
A many-to-many relationship is modeled as a join table. This is a table with 2 columns, both are foreign keys to the tables of the repationship (Student and Subject in your case).
In Hibernate if you use #ManyToMany you need to either #JoinTable or #JoinColumn to define the relationship. Once it's done your update will work.
See
http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch07.html
and
http://www.dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
EDIT:
Applied to your code it should look like (UNTESTED):
In Student:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "student_id")
private int id;
....
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "students_subject", joinColumns = { #JoinColumn(name = "student_id") }, inverseJoinColumns = { #JoinColumn(name = "subject_id") })
private Set<Subject> subject;
In Subject
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "subject_id")
private int id;
...
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "students_subject", joinColumns = { #JoinColumn(name = "subject_id") }, inverseJoinColumns = { #JoinColumn(name = "student_id") })
private Set<Student> students;
Your case might be falling into mixing JPA and Hibernate cascading. Try annotate your #ManyToMany with additional
#Cascade({CascadeType.SAVE_UPDATE})
you can also consult this two links to check if this is really your case:
Confusion between JPA and Hibernate cascading
http://www.mkyong.com/hibernate/cascade-jpa-hibernate-annotation-common-mistake/