0

I'm new to JPA and Hibernate and came across strange behavior. Consider the code below.

License entity:

@Entity
@Data
public class License {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @Enumerated(EnumType.STRING)
    private LicenseType type;
    @Column(unique = true)
    private String activationKey;
    @OneToMany(mappedBy = "id", cascade = CascadeType.REMOVE)
    private List<Payment> payments = new ArrayList<>();
    private long productId;
    private String productName;
    private long term;
    private long creationTimestamp;
    private boolean active;
}

LicenceType enum:

public enum LicenseType {
    NAMED
}

Payment entity:

@Entity
@Data
public class Payment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REFRESH})
    private License license;
    private BigDecimal sum;
}

LicenceRepository:

@Repository
public interface LicenseRepository extends CrudRepository<License, Long> {

}

PaymentRepository:

@Repository
public interface PaymentRepository extends CrudRepository<Payment, Long> {

}

Bootstrap class:

@SpringBootApplication
public class LsPocApplication {

    public static void main(String[] args) {
        SpringApplication.run(LsPocApplication.class, args);
    }

    @Bean
    public CommandLineRunner demo(LicenseRepository licenseRepository, PaymentRepository paymentRepository) {
        return (args) -> {

            License license = new License();
            license.setActivationKey(UUID.randomUUID().toString());

            Payment payment = new Payment();
            payment.setSum(BigDecimal.valueOf(new Random().nextDouble()));
            payment.setLicense(license);

            paymentRepository.save(payment);

            // licenseRepository.delete(license); // This does nothing
            // licenseRepository.delete(license.getId()); // This deletes both licence and associated payment(s)
        };
    }
}

So the question is why the licenseRepository.delete(license.getId()) works as intended, but licenseRepository.delete(license) does nothing? I assumed, they are logically equivalent. Or I'm wrong?

Please advise.

Thanks in advance!

alxg2112
  • 318
  • 2
  • 11
  • Does the same happen if you change the License id from primitive long to Long class ? – arocketman Jan 26 '18 at 12:50
  • @arocketman Yes – alxg2112 Jan 26 '18 at 13:01
  • @alxg2112 Try to remove the Cascade from your relations mappings and check it. – cнŝdk Jan 26 '18 at 13:38
  • @chŝdk Everything works fine without the cascades. Why this doesn't work with them, that is the question... – alxg2112 Jan 26 '18 at 13:42
  • @alxg2112 It works when you delete Cascade, but why? Honestly I don"t know, I have been digging on the net for a while now and I haven't ever found an answer, check [this **thread**](https://stackoverflow.com/questions/22688402/delete-not-working-with-jparepository) for reference. – cнŝdk Jan 26 '18 at 14:02
  • @chŝdk Actually, If I remove cascades, I need to save licence and payment separately, same for the deleting. That's not what I want. The behavior I'm trying to achieve is when payment with license reference is added, license entry gets created in database table. And when I deleting licence, all corresponding payments are also getting deleted. Of course, I can achieve same behavior without cascades by rewriting the code a bit, but I desperately want to shed light on this mystery :) – alxg2112 Jan 26 '18 at 14:04
  • Normally you should use Cascading to achieve this, but I don't know why it doesn't work with JPARepository `delete`. – cнŝdk Jan 26 '18 at 14:07
  • It may be that the `equals(Object o)` method generated by Lombok is confusing things. For a JPA entity bean equals (and hashCode) should only use the `id` as that is literally the definition of equality for an `@Entity` bean – Mark Jan 26 '18 at 14:33
  • @Mark Added `@EqualsAndHashCode(of = "id") @ToString(of = "id")` to the both `@Entity` classes, to luck :( – alxg2112 Jan 26 '18 at 15:17

2 Answers2

1

Does your License implement Serializable? If so I think both calls will call the same method, the one intended for the id parameter. But since no License has another License as id nothing gets deleted.

The reason is that after type erasure all whats left of the nice generics are Serializable for the id and Object for the entity type. If the entity type also happens to implement Serializable that method is used since it is more specific. (I have to admit there is some handwaving going on in my brain, but the idea should be about right).

That is the reason why from Spring Data 2.0 (aka Kay) the method names of changed, so this confusion can no longer happen.

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
  • That's a really valuable remark, but `License` doesn't implement `Serializable` so I believe there is should not be any ambiguousness. – alxg2112 Jan 26 '18 at 15:35
  • I've debugged my code and turned out you are absolutely right! Despite `License` doesn't implement `Serializable` for some reason, both `licenseRepository.delete(license)` and `licenseRepository.delete(license.getId())` calls invoke the same method `public Object invoke(Object proxy, Method method, Object[] args) throws Throwable` in `JdkDynamicAopProxy.java` class! – alxg2112 Jan 26 '18 at 15:47
0

Looks like Payment entity saved to database using merge. So License did not added to session and and therefore licenseRepository.delete(license) doesen't work.

Try to change this lines in Payment class:

@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REFRESH})
private License license;

to

@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
private License license;
Anton Tupy
  • 951
  • 5
  • 16