2

I have a very similar question to this one, however my question is even more basic than that one and so I don't feel like its a dupe...(we'll see what SO thinks). And if it is a dupe of another question, please point out to me (perhaps in a comment) how the accepted answer completely answers/addresses my issue (I don't think it does!).


I have a Java/JPA/Hibernate @Entity class that needs to have a UUID field:

@Canonical
@MappedSuperclass
public abstract class BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private UUID refId;

    // Getters/setters/ctors/etc.
}

@Entity(name = "accounts")
@AttributeOverrides({
    @AttributeOverride(name = "id", column=@Column(name="account_id")),
    @AttributeOverride(name = "refId", column=@Column(name="account_ref_id"))
})
public class Account extends BaseEntity {
    // blah whatever
}

That is, I need to be working with UUIDs at the app layer. However the database here is MySQL, and the overwhelming recommendations for representing UUIDs in MySQL seems to be to represent them as a VARCHAR(36), so that's what I have.

At runtime I'm getting the following exception:

Caused by: org.hibernate.HibernateException: Wrong column type in my_db.accounts for column account_ref_id. Found: varchar, expected: binary(255)
    at org.hibernate.mapping.Table.validateColumns(Table.java:373)
    at org.hibernate.cfg.Configuration.validateSchema(Configuration.java:1338)
  <rest of stack trace omitted for brevity>

So clearly, the database is presenting a VARCHAR(36), but Hibernate seems to be defaulting to expect a binary(255).

I honestly don't care what the UUID gets stored as in the DB. It could be VARCHAR, it could be TEXT, it could be BLOB...it could be a toaster oven for all I care! But the essential requirement here is that the data model (the entity class) use UUIDs.

What's the fix here? If I have to change the column type in the DB, what do I change the type to? If I have to change/modify the UUID field in the entity, what else do I need to annotate it with?

Community
  • 1
  • 1
smeeb
  • 27,777
  • 57
  • 250
  • 447

2 Answers2

2

For me the fix required changes to both DB schema as well as the entity (Java) code.

First I changed my CREATE TABLE statement to use BINARY(255) instead of VARCHAR(36):

CREATE TABLE IF NOT EXISTS accounts (
    # lots of fields...

    account_ref_id BINARY(255) NOT NULL,

    # ...lots of other fields
);

Next I added @Type(type="uuid-binary") to my field declaration in the entity:

@Canonical
@MappedSuperclass
public abstract class BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Type(type="uuid-binary")
    private UUID refId;

    // Getters/setters/ctors/etc.
}

@Entity(name = "accounts")
@AttributeOverrides({
    @AttributeOverride(name = "id", column=@Column(name="account_id")),
    @AttributeOverride(name = "refId", column=@Column(name="account_ref_id"))
})
public class Account extends BaseEntity {
    // blah whatever
}

This works beautifully for me.

smeeb
  • 27,777
  • 57
  • 250
  • 447
0

Try it like this:

    public class BaseEntity{

    @Column(nullable = false)
    private String uuid;

    public BaseEntity(){
         setUuid(UUID.randomUUID().toString());
    }   

    public String getUuid() {
        return uuid;
    }

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

  @PrePersist
    public void prePersist(){

        if(null == getUuid())
             setUuid(UUID.randomUUID().toString());
 }

    ....

Column definition:

uuid varchar(255) DEFAULT NULL

When generating UUID use toString method:

entity.setUuid(UUID.randomUUID().toString())
dsharew
  • 10,377
  • 6
  • 49
  • 75
  • Thanks @dsharew (+1) - but a few things: (1) my UUID field (`refId`) CANNOT be null, so I have `account_ref_id VARCHAR(36) NOT NULL` in the CREATE TABLE statement. (2) I **really** don't want `refId` to be a string. I'll need to be CRUDding instances of `Account` all over my code and don't want to be constantly coverting back and forth between UUID and String...any thoughts there? – smeeb Apr 13 '17 at 14:15
  • We had the same agrument in our team. just to answer your questions 1)"my UUID field (refId) CANNOT be null"? you can apply not null constraint to string column as well I dont see this issue here. 2) you will not be converting back between string and UUID forever. uuid is supposed to created only once when the entity is created; that is the only time you need to convert UUID to string. After that your code should be designed to work with string uuid. – dsharew Apr 13 '17 at 14:20
  • Thanks for the feedback, it is definitely appreciated. If there's truly no other way of going about this then I'll try it out and accept it if it works for me, but I'd like to hear other suggestions/recommendations (if any exist!). – smeeb Apr 13 '17 at 14:21
  • put `refId = UUID.randomUUID().toString()` in the constructor. Remove the setter. – Alan Hay Apr 13 '17 at 15:03
  • @AlanHay is this to ensure that refId is not null? – dsharew Apr 13 '17 at 15:04
  • is that is the case I think it is good to do that on ```prePersist``` event handler. – dsharew Apr 13 '17 at 15:06
  • Well why should client code have to make that additional call every time a new Entity is created? Also makes it immutable. – Alan Hay Apr 13 '17 at 15:06
  • yeah yeah I agree with you just want indicate if prePresist is prefereable. – dsharew Apr 13 '17 at 15:07
  • 1
    A UUID should be set as soon as an object is constructed. What if it is being used in equals() and hashcode() and we add 2 instances to a Set before they are made persistent. – Alan Hay Apr 13 '17 at 15:10