1

I have the following entities:

@Entity
public class License  {

     // ...

    @OneToMany(mappedBy = "license", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
    private Set<Service> services = new HashSet<>();

    // ...

    protected void setServices(Set<String> services) {
        this.services.clear();
        if (services != null) {
            this.services.addAll(services.stream().map(s -> new Service(this, s)).collect(toSet()));
        }
    }
}

@Entity
@Table(uniqueConstraints = @UniqueConstraint(name = "UQ_lic_service", columnNames = { "license_id", "service" }))
public class Service {

    // ...

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "license_id", nullable = false)
    private License license;

    @Column(nullable = false, length = 255)
    private String service;

    protected Service(License license, String service) {
        this.license = license;
        this.service = service;
    }
}

Now I need to update the License like:

    License license = getSomeExistingLicenseWithServices(...);
    // The existing license has two services "firstService", "secondService"
    HashSet<String> services = new HashSet<>();
    services.add("firstService");
    license.setServices(services);
    licenseRepository.save(license); // A spring-data-jpa-repository

Running this, I get the following exception:

Caused by: org.h2.jdbc.JdbcSQLException: Eindeutiger Index oder Primärschlüssel verletzt: "UQ_LIC_SERVICE_INDEX_8 ON PUBLIC.SERVICE(LICENSE_ID, SERVICE) VALUES (1, 'firstService', 1)"
Unique index or primary key violation: "UQ_LIC_SERVICE_INDEX_8 ON PUBLIC.SERVICE(LICENSE_ID, SERVICE) VALUES (1, 'firstService', 1)"; SQL statement:
insert into service (id, lock_no, license_id, service) values (null, ?, ?, ?) [23505-196]

I expected all 'old' (='orphaned') Services to become deleted, when I call setServices with a new HashSet. What I am doing wrong? TIA!

t777
  • 3,099
  • 8
  • 35
  • 53

2 Answers2

4

Just calling .clear() on the set may not be enough. Likely its requiring an explicit call to remove() on each component of the set before clearing it.

please try adding something like this before clearing the set to see if that solves the issue

this.services.forEach(service -> {licenseRepository.remove(service)});
2

Try to clear the list before setting the services:

license.getServices().clear();

And then

license.getServices.add("firstService");
licenseRepository.save(license);

UPDATE

To make this work, it is important to implement the equals and hashCode methods. I'm using apache commons lang library

@Override
public int hashCode() {
    return new HashCodeBuilder().append(idService).toHashCode();
}

@Override
public boolean equals(Object obj) {
    if (!(obj instanceof Service))
        return false;

    Service other = (Service) obj;

    return new EqualsBuilder()
            .append(this.idService, other.idService)
            .isEquals();
}
Carlos Laspina
  • 2,013
  • 4
  • 27
  • 44
  • isn't the same clear before adding individually lookup this http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#collections-value – Carlos Laspina Sep 22 '17 at 19:33