Spring JpaRepositroy.save() does not appear to throw exception on duplicate saves

后端 未结 4 1826
遇见更好的自我
遇见更好的自我 2020-12-09 02:36

I\'m currently playing around on Spring boot 1.4.2 in which I\'ve pulled in Spring-boot-starter-web and Spring-boot-starter-jpa.

My main issue is that when I save a

4条回答
  •  独厮守ぢ
    2020-12-09 03:31

    My solution is a lot cleaner. Spring Data already provides a nice way for us to define how an entity is considered to be new. This can easily be done by implementing Persistable on our entities, as documented in the reference.

    In my case, as is the OP's, the IDs come from an external source and cannot be auto generated. So the default logic used by Spring Data to consider an entity as new if the ID is null wouldn't have worked.

    @Entity
    public class MyEntity implements Persistable {
    
        @Id
        private UUID id;
    
        @Transient
        private boolean update;
    
        @Override
        public UUID getId() {
            return this.id;
        }
    
        public void setId(UUID id) {
            this.id = id;
        }
    
        public boolean isUpdate() {
            return this.update;
        }
    
        public void setUpdate(boolean update) {
            this.update = update;
        }
    
        @Override
        public boolean isNew() {
            return !this.update;
        }
    
        @PrePersist
        @PostLoad
        void markUpdated() {
            this.update = true;
        }
    }
    

    Here, I have provided a mechanism for the entity to express whether it considers itself new or not by means of another transient boolean property called update. As the default value of update will be false, all entities of this type are considered new and will result in a DataIntegrityViolationException being thrown when you attempt to call repository.save(entity) with the same ID.

    If you do wish to perform a merge, you can always set the update property to true before attempting a save. Of course, if your use case never requires you to update entities, you can always return true from the isNew method and get rid of the update field.

    The advantages of this approach over checking whether an entity with the same ID already exists in the database before saving are many:

    1. Avoids an extra round trip to the database
    2. We cannot guarantee that by the time one thread has determined that this entity doesn't exist and is about to persist, another thread doesn't attempt to do the same and result in inconsistent data.
    3. Better performance as a result of 1 and having to avoid expensive locking mechanisms.
    4. Atomic
    5. Simple

    EDIT: Don't forget to implement a method using JPA callbacks that sets the correct state of the update boolean field just before persisting and just after loading from the database. If you forget to do this, calling deleteAll on the JPA repository will have no effect as I painfully found out. This is because the Spring Data implementation of deleteAll now checks if the entity is new before performing the delete. If your isNew method returns true, the entity will never be considered for deletion.

提交回复
热议问题