t777
t777

Reputation: 3329

Unique constraint violation on hibernate update to OneToMany collection

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!

Upvotes: 1

Views: 3612

Answers (2)

Carlos Laspina
Carlos Laspina

Reputation: 2221

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();
}

Upvotes: 2

Jorge Alvarenga
Jorge Alvarenga

Reputation: 41

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)});

Upvotes: 4

Related Questions