Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configure Hibernate to use Oracle's SYS_GUID() for Primary Key

I am looking for a way to get hibernate to use oracle's SYS_GUID() function when inserting new rows. Currently my DB tables have SYS_GUID() as the default so if hibernate simply generated SQL that omited the value it should work.

I have everything working, but it is currently generating the UUID/GUID in code using the system-uuid generator:

@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
@Column(name = "PRODUCT_ID", unique = true, nullable = false)
public String getId() {
    return this.productId;
}

This is fine, but I would prefer that the guids were generated by the database so they will be sequential and potentially have better performance. Plus I would just like to know how to configure this.

I am using annotations for configuration, but xml configuration examples are awesome as well.

Here is a sample table definition (in case it matters):

CREATE TABLE SCHEMA_NAME.PRODUCT
(
    PRODUCT_ID RAW(16) DEFAULT SYS_GUID() NOT NULL,
    PRODUCT_CODE VARCHAR2(10 CHAR) NOT NULL,
    PRODUCT_NAME VARCHAR2(30 CHAR) NOT NULL,
    PRODUCT_DESC VARCHAR2(512 CHAR)
)

UPDATE:

Mat's sollution of using "guid" worked, here is the sql generated:

Hibernate: 
    select rawtohex(sys_guid()) 
    from dual
Hibernate: 
    insert into PRODUCT
    (PRODUCT_CODE, PRODUCT_DESC, LOB_ID, PRODUCT_NAME, PROVIDER_ID, PRODUCT_ID) 
    values (?, ?, ?, ?, ?, ?)

It seems that using the columns default value in an insert is not possible, so the choice is between an application generated guid and a database round trip.

like image 516
Ryan Cook Avatar asked May 11 '09 05:05

Ryan Cook


2 Answers

You might be able to use the "guid" generator. See this post from the Hibernate forum. It looks like they added support for Oracle using SYS_GUID() a while back, but the documentation still says they only support SQL Server and MySQL.

I haven't worked with JPA annotations yet, but here is an example using XML configuration:

<id name="PRODUCT_ID">
  <generator class="guid" />
</id>

EDIT: In regards to your second question, I think you are asking why Hibernate can't do something like this:

INSERT INTO PRODUCT (PRODUCT_ID, /* etc */)
SELECT SYSGUID(), /* etc */

The reason is that Hibernate must know what the object's ID is. For example, consider the following scenario:

  1. You create a new Product object and save it. Oracle assigns the ID.
  2. You detach the Product from the Hibernate session.
  3. You later re-attach it and make some changes.
  4. You now want to persist those changes.

Without knowing the ID, Hibernate can't do this. It needs the ID in order to issue the UPDATE statement. So the implementation of org.hibernate.id.GUIDGenerator has to generate the ID beforehand, and then later on re-use it in the INSERT statement.

This is the same reason why Hibernate cannot do any batching if you use a database-generated ID (including auto-increment on databases that support it). Using one of the hilo generators, or some other Hibernate-generated ID mechanism, is the only way to get good performance when inserting lots of objects at once.

like image 132
Matt Solnit Avatar answered Sep 20 '22 14:09

Matt Solnit


I have same task that topic starter. With thanks to @Matt Solnit suggestion I use such annotations:

@Id
@NotNull
@Column(name = "UUID")
@GenericGenerator(name = "db-uuid", strategy = "guid")
@GeneratedValue(generator = "db-uuid")
private String uuid;
public String getUuid() { return uuid; }
public void setUuid(String uuid) { this.uuid = uuid; }

strategy = "guid" and String type are essential parts of solution.

Before persisting new entities Hibernate issue SQL query:

select rawtohex(sys_guid()) from dual

My setup: Oracle 11, Hibernate 4.3.4.Final, Spring 3.2.x. And field is raw(16) in table for efficient storage and lesser index size then if you use char(32).

When I try to use java.util.UUID as ID field type I get error from Hibernate on persisting new entity (it try to set String type to java.util.UUID field).

Also I use javax.xml.bind.DatatypeConverter for non-Hibernate queries (Spring JDBC helpers), for passing convert to byte[]:

String query = "insert into TBL (UUID, COMPANY) values (:UUID, :COMPANY)";
MapSqlParameterSource parameters = new MapSqlParameterSource()
    .addValue("COMPANY", repo.getCompany())
    .addValue("UUID", DatatypeConverter.parseHexBinary(signal.getUuid()));
namedJdbcTemplate.update(query, parameters);

for extracting:

ResultSet rs;
sig.id = DatatypeConverter.printHexBinary(rs.getBytes("UUID"));

All web controllers get codes like:

025131763FB19522E050010A106D11E9

without {, -, } chars (usual representation of UUID is {a-b-c-d-x-y} if you remember). This representation already URL encoding clean and safe. You don't need to implement PropertyEditor or Convertor for String type:

@RequestMapping(value = {"/signal/edit/{id}.htm"}, method = RequestMethod.POST)
public String handleEditRequest(
        @PathVariable("id") String id,

Compare with failed attempt to use jaa.util.UUID, where I need to write:

@Component
public static class UUIDPropertyEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(final String str) {
        if (str == null || str.isEmpty()) {
            setValue(null);
            return;
        }
        setValue(UUID.fromString(str));
    }
}
private @Autowired UUIDPropertyEditor juuidPE;

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(UUIDPropertyEditor.class, juuidPE);
}

in order to use:

@PathVariable("id") UUID id,
like image 25
gavenkoa Avatar answered Sep 19 '22 14:09

gavenkoa