Spring Boot + JPA (Hibernate) - ConcurrentModificationException when deleting entity in OneToMany Bidirectional - spring-boot

I want to remove some objects from my collection:
public class Path {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "path", cascade = CascadeType.ALL, orphanRemoval = true)
List<Point> points;
public void removePoint(Point point) {
point.setPath(null);
this.getPoints().remove(point);
}
public class Point{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "point", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<PointOperation> operations;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "path_id", foreignKey = #ForeignKey(name = "FK_point_path"), nullable = false)
private Path path;
public void removePointOperation(PointOperation pointOperation) {
pointOperation.setPoint(null);
this.getOperations().remove(pointOperation);
}
When I want to delete all points with nested operations I get NPE error:
private void removeOldPoints(Window window) {
if (window.getPath().getPoints() != null) {
window.getPath().getPoints().forEach(s -> {
if (s.getOperations() != null && !s.getOperations().isEmpty()) {
s.getOperations().forEach(s::removePointOperation); // NPE here!
}
// Optional.ofNullable(s.getOperations()).ifPresent(s::removePointOperations);
window.getPath().removePoint(s);
});
}
}
Why? Because I remove items from collection that is in use? If yes how can I solve my problem?
stack trace:
java.util.ConcurrentModificationException: null
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)
at org.hibernate.collection.internal.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:883)
at java.base/java.lang.Iterable.forEach(Iterable.java:74)
at pl.xxx.window.service.WindowPathService.lambda$removeOldPoints$0(WindowPathService.java:64)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
UPDATE
Unfortunately after implementing this solution with Iterator -> Iterating through a Collection, avoiding ConcurrentModificationException when removing objects in a loop
private void removeOldPoints(Window window) {
if (window.getPath().getPoints() != null) {
for (Iterator<Point> pointIterator = window.getPath().getPoints().iterator(); pointIterator.hasNext(); ) {
Point point = pointIterator.next();
if (point.getOperations() != null && !point.getOperations().isEmpty()) {
for (Iterator<PointOperation> operationIterator = point.getOperations().iterator(); operationIterator.hasNext(); ) {
PointOperation pointOperation = operationIterator.next();
point.removePointOperation(pointOperation);
operationIterator.remove();
}
}
window.getPath().removePoint(point);
pointIterator.remove();
}
}
}
I have java.util.ConcurrentModificationException: null in line:
operationIterator.remove();

Related

Spring boot - many to many association not removing join table data

I have an issue with a many-to-many relation in Spring Boot. Code is as follows:
public class Task {
#Id
#GeneratedValue
private Long id;
#ManyToMany(cascade = {PERSIST, MERGE}, fetch = EAGER)
#JoinTable(
name = "task_tag",
joinColumns = {#JoinColumn(name = "task_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id", referencedColumnName = "id")}
)
#Builder.Default
private Set<Tag> tags = new HashSet<>();
public void addTags(Collection<Tag> tags) {
tags.forEach(this::addTag);
}
public void addTag(Tag tag) {
this.tags.add(tag);
tag.getTasks().add(this);
}
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getTasks().remove(this);
}
public void removeTags() {
for (Iterator<Tag> iterator = this.tags.iterator(); iterator.hasNext(); ) {
Tag tag = iterator.next();
tag.getTasks().remove(this);
iterator.remove();
}
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Task)) return false;
return id != null && id.equals(((Task) o).getId());
}
#Override
public int hashCode() {
return id.intVal();
}
}
and
public class Tag {
#Id
#GeneratedValue
private Long id;
#NotNull
#Column(unique = true)
private String name;
#ManyToMany(cascade = {PERSIST, MERGE}, mappedBy = "tags", fetch = EAGER)
#Builder.Default
private final Set<Task> tasks = new HashSet<>();
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
return Objects.equals(name, tag.name);
}
#Override
public int hashCode() {
return id.intVal();
}
}
Of course, I have the task_tag table where, after inserting a tag in a task and saving that task, an entry appears. However, when I delete a tag (or clear them), the entries do not get deleted from the join table. This is the test:
#Test
void entityIntegration() {
Task task = taskRepo.save(...);
Tag tag1 = Tag.builder().name(randomString()).build();
Tag tag2 = Tag.builder().name(randomString()).build();
Tag tag3 = Tag.builder().name(randomString()).build();
Tag tag4 = Tag.builder().name(randomString()).build();
final List<Tag> allTags = Arrays.asList(tag1, tag2, tag3, tag4);
tagRepo.saveAll(allTags);
task.addTag(tag1);
taskRepo.save(task);
final Long task1Id = task.getId();
assertTrue(tag1.getTasks().stream().map(Task::getId).collect(Collectors.toList()).contains(task1Id));
task.clearTags();
task = taskRepo.save(task);
tag1 = tagRepo.save(tag1);
assertTrue(task.getTags().isEmpty());
assertTrue(tag1.getTasks().isEmpty());
task.addTags(allTags);
task = taskRepo.save(task); // FAILS, duplicate key ...
}
I delete tag1 but when I try to add it back to the task, I get
The task_tag table does have a composite index formed on those two (and only) columns.
What am I doing wrong? I followed each and every suggestion and advice - using set instead of lists, having helper methods, cleaning up etc...
I can't find the bug.
Thank you!
The biggest thing that sticks out to me is that your Tag's equals and hash-code aren't matched with each other.
Your "equals" drives equality based on the object's name being the same, which makes logical sense to the mantra of "A tag is a name". But the hash-code drives based on the "id" being equivalent and doesn't use the name at all.
Forgetting JPA/Hibernate for a moment, just plain old Collections themselves get very unpredictable when these two are out of synch.
You can read more about that here and specifically why a hash-code that doesn't match equality would end up hashing to the wrong bucket and resulting in confusing keeping it all straight in HashSets: Why do I need to override the equals and hashCode methods in Java?
There are many ways to put them back in synch (using libraries like Lombok and code-generation tools in your IDE come to mind), but instead of prescribing one, I will simply point to this web-resource that, conveniently, created a Tag with the exact same concept for his example, so I suspect you can just use this exact same pattern yourself.
https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/
Here's another helpful SO thread I found that talks about relationships and identity/equals/hashCode as it impacts JPA: The JPA hashCode() / equals() dilemma
Kindly add DELETE keyword to the cascade property of many to many annotation . And i believe ur annotation for task property of Tag class should be changed as below .
You can give this below mapping a try
public class Task {
#Id
#GeneratedValue
private Long id;
#ManyToMany(cascade = {PERSIST, MERGE,DELETE}, fetch = EAGER)
#JoinTable(
name = "task_tag",
joinColumns = {#JoinColumn(name = "task_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id", referencedColumnName = "id")}
)
#Builder.Default
private Set<Tag> tags = new HashSet<>();
public void addTags(Collection<Tag> tags) {
tags.forEach(this::addTag);
}
public void addTag(Tag tag) {
this.tags.add(tag);
tag.getTasks().add(this);
}
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getTasks().remove(this);
}
public void removeTags() {
for (Iterator<Tag> iterator = this.tags.iterator(); iterator.hasNext(); ) {
Tag tag = iterator.next();
tag.getTasks().remove(this);
iterator.remove();
}
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Task)) return false;
return id != null && id.equals(((Task) o).getId());
}
#Override
public int hashCode() {
return id.intVal();
}
}
public class Tag {
#Id
#GeneratedValue
private Long id;
#NotNull
#Column(unique = true)
private String name;
#ManyToMany(cascade = {PERSIST, MERGE,DELETE}, mappedBy = "tags", fetch = EAGER)
#JoinTable(
name = "task_tag",
joinColumns = {#JoinColumn(name = "tag_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "task_id", referencedColumnName = "id")}
)
#Builder.Default
private final Set<Task> tasks = new HashSet<>();
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
return Objects.equals(name, tag.name);
}
#Override
public int hashCode() {
return id.intVal();
}
}

Update the Foreign Key with JPA

I created 2 entities :
#Entity
#Table(name="products")
public class ProductEntity {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String productKeyId;
// many to one relationship with category
#ManyToOne
#JoinColumn(name = "category_id")
private CategoryEntity category;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private double price;
#Column(nullable = false)
private int qty;
private String imgPath;
// getters & setters
}
And :
#Entity
#Table(name="categories")
public class CategoryEntity {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(length = 30, nullable = false)
private String categoryKeyId;
#Column(nullable = false)
private String name;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name="parent_id", nullable=true)
private CategoryEntity parentCategory;
// allow to delete also subcategories
#OneToMany(mappedBy="parentCategory", cascade = CascadeType.ALL)
private List<CategoryEntity> subCategories;
//Here mappedBy indicates that the owner is in the other side
#OneToMany(fetch = FetchType.EAGER, mappedBy = "category", cascade = CascadeType.REMOVE)
private List<ProductEntity> products;
}
I have datas in the database generated :
Here is my Product table
And the category table
My issue is the following. I use a REST API to update the product and the category (if needed).
{
"name": "Pizza12",
"price": 25.0,
"qty": 15,
"imgPath": "anotherpathImage",
"category": {
"categoryKeyId": "VMz7EM6tNfoOAQtO1SHPYcH14jj0Cy",
"name": "Fish"
}
}
In my service I try to update both part separatelly :
#Override
public ProductDto updateProduct(String productKeyId, ProductDto productDto) {
// create a return object of type Product
ProductDto returnValue = new ProductDto();
// create Entity objects to request on the database
ProductEntity productEntity = productRepository.findByProductKeyId(productKeyId);
CategoryEntity categoryEntity = categoryRepository.findCategoryEntityByProductKeyId(productKeyId);
ModelMapper modelMapper = new ModelMapper();
if (productEntity == null)
throw new ApplicationServiceException(ErrorMessages.NO_RECORD_FOUND.getErrorMessage());
productEntity.setProductKeyId(productKeyId);
productEntity.setName(productDto.getName());
productEntity.setPrice(productDto.getPrice());
productEntity.setQty(productDto.getQty());
productEntity.setImgPath(productDto.getImgPath());
// update the category
CategoryEntity updatedCategory = categoryRepository.save(categoryEntity);
productEntity.setCategory(updatedCategory);
// productEntity.setCategory(categoryEntity);
System.out.println("product entity : " + productEntity.toString());
ProductEntity updatedProduct = productRepository.save(productEntity);
updatedProduct.setCategory(updatedCategory);
returnValue = modelMapper.map(updatedProduct, ProductDto.class);
return returnValue;
}
Unfortunatelly, it doesn't seem to work as expected. The product is updated, the category remains the same.
I finally solved my Issue thanks to Janar and Repoker.
#Override
public ProductDto updateProduct(String productKeyId, ProductDto productDto) {
// create a return object of type Product
ProductDto returnValue = new ProductDto();
// create Entity objects to request on the database
ProductEntity productEntity = productRepository.findByProductKeyId(productKeyId);
CategoryEntity categoryEntity = categoryRepository.findByCategoryKeyId(productDto.getCategory().getCategoryKeyId());
//CategoryEntity categoryEntity = categoryRepository.findCategoryEntityByProductKeyId(productKeyId);
ModelMapper modelMapper = new ModelMapper();
if (productEntity == null)
throw new ApplicationServiceException(ErrorMessages.NO_RECORD_FOUND.getErrorMessage());
productEntity.setProductKeyId(productKeyId);
productEntity.setName(productDto.getName());
productEntity.setPrice(productDto.getPrice());
productEntity.setQty(productDto.getQty());
productEntity.setImgPath(productDto.getImgPath());
// update the category
CategoryEntity updatedCategory = categoryRepository.save(categoryEntity);
productEntity.setCategory(productEntity.getCategory());
// productEntity.setCategory(categoryEntity);
System.out.println("product entity : " + productEntity.toString());
ProductEntity updatedProduct = productRepository.save(productEntity);
updatedProduct.setCategory(updatedCategory);
returnValue = modelMapper.map(updatedProduct, ProductDto.class);
return returnValue;
}
I was not persisting the new values entered but the values that were initially set...

Spring Data rest how to perform CRUD on #manytomany relation ,composite table with extra column

I am unable to perform CRUD via json POST from restful client Postman on Composite table having extra column .I am using Spring boot ,spring data rest and spring JPA.
I have 3 tables in data base
-user
-competency
-user_competency (join/composite table with extra column)
Here are my classes
User
#Entity
#Table(name = "\"user\"", schema = "public")
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "userId")
public class User implements java.io.Serializable {
private Long userId;
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", unique = true, nullable = false)
public Long getUserId() {
return this.userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
private Set<UserCompetency> userCompetencies = new HashSet<UserCompetency>(0);
#OneToMany(fetch = FetchType.EAGER,cascade = {CascadeType.ALL}, mappedBy = "user")
public Set<UserCompetency> getUserCompetencies() {
return this.userCompetencies;
}
public void setUserCompetencies(Set<UserCompetency> userCompetencies) {
this.userCompetencies = userCompetencies;
}
}
Competency
#Entity
#Table(name = "competency", schema = "public")
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "competencyId")
public class Competency implements java.io.Serializable {
private Long competencyId;
private Set<UserCompetency> userCompetencies = new HashSet<UserCompetency>(0);
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "competency_id", unique = true, nullable = false)
public Long getCompetencyId() {
return this.competencyId;
}
public void setCompetencyId(Long competencyId) {
this.competencyId = competencyId;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "competency")
public Set<UserCompetency> getUserCompetencies() {
return this.userCompetencies;
}
public void setUserCompetencies(Set<UserCompetency> userCompetencies) {
this.userCompetencies = userCompetencies;
}
}
UserCompetency
#Entity
#Table(name = "user_competency", schema = "public")
#JsonIdentityInfo(
generator =ObjectIdGenerators.IntSequenceGenerator.class,
property = "id")
public class UserCompetency implements java.io.Serializable {
private UserCompetencyId id;
private Level level;
private User user;
private Competency competency;
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "competencyId", column = #Column(name = "competency_id", nullable = false)),
#AttributeOverride(name = "userId", column = #Column(name = "user_id", nullable = false)) })
public UserCompetencyId getId() {
return this.id;
}
public void setId(UserCompetencyId id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "level_id")
public Level getLevel() {
return this.level;
}
public void setLevel(Level level) {
this.level = level;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false, insertable = false, updatable = false)
public User getUser() {
return this.user;
}
public void setUser(User user) {
this.user = user;
}
#ManyToOne(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#JoinColumn(name = "competency_id", nullable = false, insertable = false, updatable = false)
public Competency getCompetency() {
return this.competency;
}
public void setCompetency(Competency competency) {
this.competency = competency;
}
}
UserCompetencyId
#Embeddable
public class UserCompetencyId implements java.io.Serializable {
private Long competencyId;
private Long userId;
public UserCompetencyId() {
}
public UserCompetencyId(Long competencyId, Long userId) {
this.competencyId = competencyId;
this.userId = userId;
}
#Column(name = "competency_id", nullable = false)
public Long getCompetencyId() {
return this.competencyId;
}
public void setCompetencyId(Long competencyId) {
this.competencyId = competencyId;
}
#Column(name = "user_id", nullable = false)
public Long getUserId() {
return this.userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public boolean equals(Object other) {
if ((this == other))
return true;
if ((other == null))
return false;
if (!(other instanceof UserCompetencyId))
return false;
UserCompetencyId castOther = (UserCompetencyId) other;
return (this.getCompetencyId() == castOther.getCompetencyId()) && (this.getUserId() == castOther.getUserId());
}
}
Suppose i have already record in User and Competency tables and i want to assocaite both i am trying to post like this ,but it give me error of 405 Method Not Allowed.
help required ,what should be structure of json to be posted User will already exist and competency will might exist or new can be added and associated with existing user.
With this code I was able to post a new relation:
UserCompetency.class
#Entity
#Table(name = "user_competency")
#IdClass(UserCompetencyId.class)
public class UserCompetency implements java.io.Serializable {
#Id #ManyToOne
#JoinColumn(name = "competency_id", nullable = false, insertable = false, updatable = false)
private Competency competency;
#Id #ManyToOne
#JoinColumn(name = "user_id", nullable = false, insertable = false, updatable = false)
private User user;
UserCompetencyId.class
public class UserCompetencyId implements java.io.Serializable {
private Long competency;
private Long user;
public UserCompetencyId() {
}
public UserCompetencyId(Long competency, Long user) {
this.competency = competency;
this.user = user;
}
UserCompetencyRepository.class
public interface UserCompetencyRepository extends JpaRepository<UserCompetency, UserCompetencyId> {
}
POST http://localhost:8080/userCompetencies
{
"competency": "/competencies/2"
, "user": "/user/4"
}
Apparently there seems to be no "natural/easy" way to get what you want. But there is a promissing project for integrating embeddables by extending the serialization process: https://github.com/gregturn/embeddable-spring-data-rest
UserCompetencyIdJacksonModule, UserCompetencyIdSerializer, ..
Then you should be able PATCH (not POST) your JSON from above.

Jpa - Hibernate ManyToMany do many insert into join table

I have follows ManyToMany relationship between WorkDay(has annotation ManyToMany) and Event
WorkDay entity
#Entity
#Table(name = "WORK_DAY", uniqueConstraints = { #UniqueConstraint(columnNames = { "WORKER_ID", "DAY_ID" }) })
#NamedQueries({
#NamedQuery(name = WorkDay.GET_WORK_DAYS_BY_MONTH, query = "select wt from WorkDay wt where wt.worker = :worker and to_char(wt.day.day, 'yyyyMM') = :month) order by wt.day"),
#NamedQuery(name = WorkDay.GET_WORK_DAY, query = "select wt from WorkDay wt where wt.worker = :worker and wt.day = :day") })
public class WorkDay extends SuperClass {
private static final long serialVersionUID = 1L;
public static final String GET_WORK_DAYS_BY_MONTH = "WorkTimeDAO.getWorkDaysByMonth";
public static final String GET_WORK_DAY = "WorkTimeDAO.getWorkDay";
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "WORKER_ID", nullable = false)
private Worker worker;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DAY_ID", nullable = false)
private Day day;
#Column(name = "COMING_TIME")
#Convert(converter = LocalDateTimeAttributeConverter.class)
private LocalDateTime comingTime;
#Column(name = "OUT_TIME")
#Convert(converter = LocalDateTimeAttributeConverter.class)
private LocalDateTime outTime;
#Enumerated(EnumType.STRING)
#Column(name = "STATE", length = 16, nullable = false)
private WorkDayState state = WorkDayState.NO_WORK;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "WORK_DAY_EVENT", joinColumns = {
#JoinColumn(name = "WORK_DAY_ID", nullable = false)}, inverseJoinColumns = {
#JoinColumn(name = "EVENT_ID", nullable = false)})
#OrderBy(value = "startTime desc")
private List<Event> events = new ArrayList<>();
protected WorkDay() {
}
public WorkDay(Worker worker, Day day) {
this.worker = worker;
this.day = day;
this.state = WorkDayState.NO_WORK;
}
}
Event entity
#Entity
#Table(name = "EVENT")
public class Event extends SuperClass {
#Column(name = "DAY", nullable = false)
#Convert(converter = LocalDateAttributeConverter.class)
private LocalDate day;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "TYPE_ID", nullable = false)
private EventType type;
#Column(name = "TITLE", nullable = false, length = 128)
private String title;
#Column(name = "DESCRIPTION", nullable = true, length = 512)
private String description;
#Column(name = "START_TIME", nullable = false)
#Convert(converter = LocalDateTimeAttributeConverter.class)
private LocalDateTime startTime;
#Column(name = "END_TIME", nullable = true)
#Convert(converter = LocalDateTimeAttributeConverter.class)
private LocalDateTime endTime;
#Enumerated(EnumType.STRING)
#Column(name = "STATE", nullable = false, length = 16)
private EventState state;
protected Event() {
}
}
Attached UI form for clarity
When I push Clock with run icon first time, it means "create event and start work day" in bean, calling the following methods:
public void startEvent() {
stopLastActiveEvent();
Event creationEvent = new Event(workDay.getDay().getDay(), selectedEventType, selectedEventType.getTitle(),
LocalDateTime.now());
String addEventMessage = workDay.addEvent(creationEvent);
if (Objects.equals(addEventMessage, "")) {
em.persist(creationEvent);
if (workDay.isNoWork()
&& !creationEvent.getType().getCategory().equals(EventCategory.NOT_INFLUENCE_ON_WORKED_TIME)) {
startWork();
}
em.merge(workDay);
} else {
Notification.warn("Невозможно создать событие", addEventMessage);
}
cleanAfterCreation();
}
public String addEvent(Event additionEvent) {
if (!additionEvent.getType().getCategory().equals(NOT_INFLUENCE_ON_WORKED_TIME)
&& isPossibleTimeBoundaryForEvent(additionEvent.getStartTime(), additionEvent.getEndTime())) {
events.add(additionEvent);
changeTimeBy(additionEvent);
} else {
return "Пересечение временых интервалов у событий";
}
Collections.sort(events, new EventComparator());
return "";
}
private void startWork() {
workDay.setComingTime(workDay.getLastWorkEvent().getStartTime());
workDay.setState(WorkDayState.WORKING);
}
In log I see:
insert into event table
update work_day table
insert into work_day_event table
on UI updated only attached frame. Always looks fine.. current WorkDay object have one element in the events collection, also all data is inserted into DB.. but if this time edit event row
event row listener:
public void onRowEdit(RowEditEvent event) {
Event editableEvent = (Event) event.getObject();
LocalDateTime startTime = fixDate(editableEvent.getStartTime(), editableEvent.getDay());
LocalDateTime endTime = fixDate(editableEvent.getEndTime(), editableEvent.getDay());
if (editableEvent.getState().equals(END) && startTime.isAfter(endTime)) {
Notification.warn("Невозможно сохранить изменения", "Время окончания события больше времени начала");
refreshEvent(editableEvent);
return;
}
if (workDay.isPossibleTimeBoundaryForEvent(startTime, endTime)) {
editableEvent.setStartTime(startTime);
editableEvent.setEndTime(endTime);
workDay.changeTimeBy(editableEvent);
em.merge(workDay);
em.merge(editableEvent);
} else {
refreshEvent(editableEvent);
Notification.warn("Невозможно сохранить изменения", "Пересечение временых интервалов у событий");
}
}
to the work_day_event insert new row with same work_day_id and event_id data. And if edit row else do one more insert and etc.. In the result I have several equals rows in work_day_event table. Why does this happen?
link to github project repository(look ver-1.1.0-many-to-many-problem branch)
Change CascadeType.ALL to CascadeType.MERGE for events in the WorkDay entity
Use this code
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
instead of
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
Do not use ArrayList, use HashSet. Because ArrayList allows duplicates.
For more info about CasecadeType, follow the tutorial:
Hibernate JPA Cascade Types
Cascading best practices
I think the simple solution is to remove the cascade on many to many relationship and do the job manually ! . I see you already doing it redundantly anyway . So try removing you CascadeType.ALL
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
How to persist #ManyToMany relation - duplicate entry or detached entity

JpaRepository delete child elements

I'm trying to do a very simple delete operation, but somehow it doesn't work since I updated the DAOs to JpaRepository. Basically it's this:
A a = aRepository.findOne(id);
a.setSomeField("someNewString");
List<B> bList = a.getBs();
bList.clear();
aRepository.saveAndFlush(a);
The field get's updated as expected, but the bList stays unchanged. I've even tried:
A a = aRepository.findOne(id);
a.setSomeField("someNewString");
List<B> bList = a.getBs();
for(B b : bList) {
bRepository.delete(b);
}
bRepository.flush();
bList.clear();
aRepository.saveAndFlush(a);
Still the same...
Class A looks like this:
#Entity
#Table(name = "A")
public class A implements Serializable {
private static final long serialVersionUID = -1286451120913657028L;
#Column(name = "id", length = 16, nullable = false, updatable = false)
#GenericGenerator(name = "uuid", strategy = "uuid2")
#GeneratedValue(generator = "uuid")
#Basic(fetch = FetchType.EAGER)
#Id
protected UUID id;
#OneToMany(mappedBy = "a", fetch = FetchType.EAGER)
#Cascade({ CascadeType.ALL })
List<B> bList;
// getter + setter
}
What am I doing wrong?!
Class B:
#Entity
#Table(name = "B")
public class B implements Serializable {
#Column(name = "id", length = 16, nullable = false, updatable = false)
#GenericGenerator(name = "uuid", strategy = "uuid2")
#GeneratedValue(generator = "uuid")
#Basic(fetch = FetchType.EAGER)
#Id
protected UUID id;
#ManyToOne(optional = false)
#JoinColumns({ #JoinColumn(name = "A_id", referencedColumnName = "id", nullable = false) })
#Valid
A a;
// setter + getter
}
Setters and getters are all just as simple as possbile:
public List<B> getBList() {
return bList;
}
public void setBList(List<B> bList) {
this.bList = bList;
}
Some more information:
spring 3.2.2
hibernate 4.2.2
spring-data-commons 1.5.1
spring-data-jpa 1.3.2
Update the A.bList property as follows:
public class A {
#OneToMany(mappedBy = "a", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
List<B> bList;
}
The orphanRemoval = true annotation attribute will tell the underlying JPA implementation to delete B records which don't have any parent left.
Also, Since the B side manages the association, you should clear its a attribute when breaking the relationship. To make this easier to read and to remove the burden of such implementation details from the caller, you should introduce management methods in A :
public class A {
public void clearBList() {
for (B b : bList) {
b.releaseA();
}
bList.clear();
}
}
public class B {
void releaseA() {
this.a = null;
}
}
You should avoid exposing collections directly and instead return an immutable version of it to prevent clients to the A class of modifying the collection directly without the A class knowing it. A manages the B list, so it should have full control over it!
public class A {
public List<B> getBList() {
return Collections.unmodifiableList(bList);
}
}
Hope that helps.

Resources