2

I am trying to save an entity with pure JPA and the Postgres UUID field is not being returned on the save. The UUID field is auto-generated by default using public.uuid_generate_v1mc(). I am using postgres as the database and for some reason, the default value that is created when doing a save in jpa does not get returned. I have tried adding a @Generated field and setting it to auto and that doesn't do anything.

Am i doing something wrong?

Database Create Statement

CREATE TABLE usermgmt.test
(
  id SERIAL PRIMARY KEY,
  active boolean NOT NULL DEFAULT false,
  created timestamp without time zone NOT NULL DEFAULT now(),
  updated timestamp without time zone NOT NULL DEFAULT now(),
  description character varying,
  name character varying NOT NULL,
  external_id bigint UNIQUE ,
  uuid uuid DEFAULT public.uuid_generate_v1mc(),
  alias character varying NOT NULL UNIQUE,
  code character varying NOT NULL UNIQUE
);

Entity

@SuppressWarnings("serial")
@Entity(name = "managementTest")
@Cacheable
@Table(schema = "usermgmt", name = "test")
public class Test extends BaseEntity implements Serializable {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String description;

    @Column(nullable = false, insertable = false, updatable = false)
    private UUID uuid;

    @Column(nullable = false, unique = true)
    private String alias;

    @Column(nullable = false, unique = true)
    private String code;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return
     */
    public String getDescription() {
        return description;
    }

    /**
     * @param description
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * @return the uuid
     */
    public UUID getUuid() {
        return uuid;
    }

    public void setUuid(UUID uuid) {
        this.uuid = uuid;
    }

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}

Base Entity

@MappedSuperclass
public abstract class BaseEntity {
    private LocalDateTime created;
    private LocalDateTime updated;
    @NotNull
    private Boolean active;

    public BaseEntity() {
        this.active = Boolean.TRUE;
    }

    @PrePersist
    @PreUpdate
    void setDates() {
        if (this.created == null) {
            this.created = LocalDateTime.now();
        }

        this.updated = LocalDateTime.now();
    }

    public Boolean getActive() {
        return this.active;
    }

    public void setActive(Boolean active) {
        this.active = active;
    }

    public LocalDateTime getCreated() {
        return this.created;
    }

    public LocalDateTime getUpdated() {
        return this.updated;
    }

    public void setUpdated(LocalDateTime updated) {
        this.updated = updated;
    }

    public void touch() {
        this.updated = LocalDateTime.now();
    }
}

Crud

@Repository("managementTestCrudRepository")
public interface TestCrudRepository extends JpaRepository<Test, Long> {
    Test findByCode(String code);
    Test findByUuid(UUID uuid);
    Test findByAlias(String alias);
    List<Test> findByActive(Boolean active);
}

This is the method I used to save the entity

@PutMapping
public Test putByJson(@RequestBody String json) {
    return testCrudRepository.save(new Gson().fromJson(json, Test.class));
}

Object that is returned in postman

{
    "created": {
        "month": "OCTOBER",
        "year": 2018,
        "dayOfYear": 275,
        "hour": 11,
        "minute": 9,
        "nano": 325000000,
        "second": 52,
        "dayOfMonth": 2,
        "dayOfWeek": "TUESDAY",
        "monthValue": 10,
        "chronology": {
            "id": "ISO",
            "calendarType": "iso8601"
        }
    },
    "updated": {
        "month": "OCTOBER",
        "year": 2018,
        "dayOfYear": 275,
        "hour": 11,
        "minute": 9,
        "nano": 329000000,
        "second": 52,
        "dayOfMonth": 2,
        "dayOfWeek": "TUESDAY",
        "monthValue": 10,
        "chronology": {
            "id": "ISO",
            "calendarType": "iso8601"
        }
    },
    "active": true,
    "id": 2,
    "externalId": null,
    "name": "test1",
    "description": "test1",
    "uuid": null,
    "alias": "test1",
    "code": "test1"
}
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
Justin
  • 866
  • 1
  • 13
  • 29
  • 2
    Which `@Generated` did you use? There are multiple out there and it might be easy to confuse them. Also did Hibernate perform the additional select one would expect from `@Generated`? – Jens Schauder Oct 09 '18 at 17:59

2 Answers2

4

Solution

You need to add the @Generated annotation over the fields that will be generated by the database.

@Generated(GenerationTime.ALWAYS)
private UUID uuid;

Explanation

You need to find a way to tell your JPA provider about (1) the fields that are generated on the database side and (2) when to fetch them.

For such properties, there is an annotation called @Generated which provides two options:

  • to fetch a generated property on insert (GenerationTime.INSERT);
  • to fetch a generated property on both insert and update (GenerationTime.ALWAYS).

To retrieve needed @Generated values, Hibernate (Spring uses it as the default JPA provider) will execute an additional SELECT statement.

I don't know what you going to use the PUT endpoint for (create/update/override a resource1), but I would go with @Generated(ALWAYS) since, with @Generated(INSERT), you will be getting null while updating/merging.

Bonus

I find this video very informative and on point.

video


1 a serious discussion here

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • Thanks for the details on this. I am going to update this to ALWAYS since you posted this! So thanks for taking the time to answer :). – Justin Oct 11 '18 at 13:56
1

So what I have discovered is that you must use the annotation @Generated(GenerationTime.INSERT)

I was actually using @GeneratedValue and i tried all of the different values for that and none of them worked. Finally after using @Generated(GenerationTime.INSERT) it worked fine.

Justin
  • 866
  • 1
  • 13
  • 29