javadatabasehibernatejpatype-safety

Hibernate: EmbeddedId with auto increment


Suppose that I have a simple Hibernate entity with auto-incremented id.

@Entity
@Table(name = "product")
public class Product {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    private String name;
}

Is it possible to declare id as a type-safe variable? I could apply @EmbeddedId like this.

@Entity
@Table(name = "product")
public class Product {
    @EmbeddedId
    private ProductId id;

    private String name;
    
    @Embeddable
    public static class ProductId implements Serializable {
        @GeneratedValue(strategy = IDENTITY)
        private Long id;

        public Long getId() {
            return id;
        }
    }
}

It works with client-generated IDs, but not with database-generated ones.

Has anyone solved similar problem? What are the possible approaches?


Solution

  • @EmbeddedId Works when you are using an identity provided by either, the user or the application but not by the persistence mechanism.

    In other words you need to assign the id prior persisting the entity. There are several ways to do so:

    1. Let the user provide the id. For instance their email which is universally unique. Not your case but if your product is a car you can get the VIN number as id
    2. Let the application provide the id. For instance generating a UUID when instantiating a new ProductId:
    @Embeddable
    public class ProductId {
      private String uuid;
      //Entity requirement
      protected ProductId() { }
    
      private ProductId(final String uuid) {
        this.uuid = uuid;
      }
      public static ProductId of(final String uuid) {
        return new ProductId(uuid);
      }
      public static ProductId createProductId() {
        // You can also prefix the UUID so a human can identify 
        // where this id comes from in case you have multiple ids
        // based on UUID, like SupplierId
        String uuid = "product-" + UUID.randomUUID().toString();
        return new ProductId(uuid)
      }
    }
    

    so you create a new product id or get a ProductId from a raw (String) id

    @Entity
    public class Product {
      private ProductId id;
      private String name;
    
      //Entity requirement
      protected Product() { }
      public Product(final String name) {
        this.id = createProductId();//static import allowing you to use a friendly language
        this.name = name;
    }
    
    1. Get the next sequence value from the persistence mechanism. For instance, executing the query to get the next value and assigning it directly to your ProductId: select product_seq.nextval as product_id from dual

    Another alternative is for you to use that numeric sequence as a surrogate identity and your product id. Thus, the ORM can identify entities by the surrogate id and your domain can identify entities by the entity id, product id in your case. You'll end up with something like

    @Entity
    public class Product {
      @Id
      @GeneratedValue
      private Long id;
      private ProductId productId;
      ...
    }
    

    The most important part for you is to discover what makes your product unique. Then you can create a ProductId that matches that concept or simply use a UUID generated by the application. It depends on your domain, perhaps the name? barcode? sku?

    Just bear in mind that a UUID might repeat, it is very very very hard and unlikely but not impossible. So if you go with UUID make sure your table column is unique, so you don't repeat UUIDs

    Glad you are using value objects as identifiers instead of primitive values, kudos!