Francesco Rogo
Francesco Rogo

Reputation: 343

How can i generate an entity id only if it is null with JpaRepository?

I have this class with UUID as primary key:

@Entity
@Table(name = "JOURNAL")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class JournalEntity extends AbstractEntity<UUID> {

    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", 
      strategy = "it.frogo.journal.dao.model.UUIDGenerator")
    @Column(name = "id", updatable = false, nullable = false)
    UUID id;
    String name;

    @Override
    public UUID getId() {
        return id;
    }

}

I want to be able to persist a new entity with his own id if it's not null or generate a random one otherwise.

I've found this question:

How to generate ID only if it's null on persisting

so i created a custom IdentifierGenerator like this:

public class UUIDGenerator implements IdentifierGenerator {

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object arg) throws HibernateException {

        log.info("UUID generation");

        try {

            final Method m = arg.getClass().getMethod("getId");

            if (!m.getReturnType().equals(UUID.class)) {
                throw new NoSuchMethodException();
            }
   
            final UUID invoke = (UUID)m.invoke(arg);
            
            return invoke == null ? UUID.randomUUID() : invoke;

        } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            throw new HibernateException("invalid entity");
        }

    }
    
}

What happens is that if i try to save an instance through the following repository

@Repository
public interface JournalRepository extends JpaRepository<JournalEntity, UUID>, JpaSpecificationExecutor<JournalEntity> {
    
}

is that the custom generator is called only if id is null on the entity.

When i set a pre-generated uuid on the entity the custom generator is not called and an exception is thrown:

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: it.frogo.journal.dao.model.JournalEntity; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: it.frogo.journal.dao.model.JournalEntity

Using directly the entityManager i should probably be able to merge it without issues, but imho that defeats the purpose of having a repository in the first place.

So, am i missing something? There is really no other way to make this work with JpaRepository only?

Thank you.

Upvotes: 1

Views: 5932

Answers (3)

Rohit Barnwal
Rohit Barnwal

Reputation: 21

I faced the same situation and I used this approach and it worked for me :

    @Id
    @Column(name = "id")
    UUID id;

use @Prepersist annotation to get random UUID if null :

    @PrePersist
    protected void onCreate() {
        if (Objects.isNull(this.id)) {
            this.id = UUID.randomUUID();
        }
    }

Upvotes: 2

Christian Beikov
Christian Beikov

Reputation: 16400

Spring Data JPA has custom means to decide if it does an update or persist: https://stackoverflow.com/a/44494970/412446

Upvotes: 0

Semyon Kirekov
Semyon Kirekov

Reputation: 1442

Try this one.

@Entity
@Table(name = "JOURNAL")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class JournalEntity extends AbstractEntity<UUID> {

    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", 
      strategy = "it.frogo.journal.dao.model.UUIDGenerator")
    @Column(name = "id", updatable = false, nullable = false)
    UUID id;
    String name;

    @Override
    public UUID getId() {
        return id;
    }
    
    @PrePersist
    @PreUpdate
    void setIdIfMissing() {
        if (id == null) {
            id = UUID.randomUUID();
        }
    }
}

If you need to apply the behaviour to several entities, then you can declare a separate entity listener.

@EntityListener
class UUIDEntityListener {
    @PrePersist
    @PreUpdate
    void setIdIfMissing(Object entity) {
        if (entity instanceof UUIDEntity) {
            UUIDEntity e = (UUIDEntity) e;
            if (e.getId() == null) {
                e.setId(UUID.randomUUID())
            }
        }
    }
}

Upvotes: 0

Related Questions