SQL Server with Hibernate UUID2 generation strategy: Solving the issue
After switching the Hibernate generation strategy from GUID to UUID2, we started experiencing issues with SQL Server unique identifier columns. In this article we’ll dive into the problem, the cause and how it can be resolved.
On a project I’m currently working on, we’re using a SQL Server database, where some of the ID fields are of the UNIQUEIDENTIFIER type. For example:
CREATE TABLE test_entity (
id UNIQUEIDENTIFIER NOT NULL PRIMARY KEY DEFAULT NEWID()
);
For exposing these records in our application, we specified them as JPA @Entity classes. Here we also configured that the guid strategy should be used when generating new IDs.
@Entity
@Table(name = "test_entity")
public class TestEntity {
@Id
@GenericGenerator(name = "generator", strategy = "guid")
@GeneratedValue(generator = "generator")
private String id;
public String getId() {
return id;
}
}
Only, when starting the application, we noticed warnings in the log file, like the following:
org.hibernate.id.GUIDGenerator: HHH000065: DEPRECATED : use [org.hibernate.id.UUIDGenerator] instead with custom [org.hibernate.id.UUIDGenerationStrategy] implementation
As can be found in the DefaultIdentifierGeneratorFactory class constructor, the org.hibernate.id.UUIDGenerator is represented by the uuid2 generation strategy. So, we decided to change the @GenericGenerator annotation values:
@GenericGenerator(name = "generator", strategy = "uuid2")
Symptoms after the change
After this change, we started noticing issues with the generated IDs. When we would save a new entity into the database, the (generated) ID did not match the ID of that same entity when fetching it from the database. We proved this by a simple unit test, similar to the one below:
@Test
void verifyIdReturnedByCreateEqualToIdInDatabase() {
// Given
TestEntity newEntity = new TestEntity();
// When
repository.save(newEntity);
// Then
TestEntity savedEntity = repository.findAll().get(0);
assertEquals(savedEntity.getId(), newEntity.getId());
}
When executing this test, this would fail with a reason like the following:
org.opentest4j.AssertionFailedError:
Expected :9567253B-8F81-4EC4-8274-8577861F1896
Actual :9567253b-8f81-4ec4-8274-8577861f1896
As you can see, the expected value (so the value that was stored in the database) is uppercase, but the actual value (the generated ID that was stored in the entity) is lowercase.
This was also addressed to Hibernate via issue HHH-12943: SQL Server UNIQUEIDENTIFIER type uses an uppercase UUID String representation. However, Hibernate will not implement a fix, because Hibernate is actually following the UUID specifications (stating UUIDs should be lowercase), where SQL Server deviates from this standard.
Possible solutions
There are a couple of possibilities to resolve this issue:
- Keep on using the guid generation strategy instead of uuid2; The GUIDGenerator documentation even states that this was specifically designed for the SQL Server NEWID() function.
- Update field type from String to either byte[] or UUID; As also stated in the Hibernate issue, these field types don’t experience the same issue:
@Entity @Table(name = "test_entity") public class TestEntity { @Id @GenericGenerator(name = "generator", strategy = "uuid2") @GeneratedValue(generator = "generator") private UUID id; public UUID getId() { return id; } }
- Keep the field type String, but always convert it to uppercase in the getId() method, so it matches the SQL Server implementation. Of course, you have to keep it null-safe:
@Entity
@Table(name = "test_entity ")
public class TestEntity {
@Id
@GenericGenerator(name = "generator", strategy = "uuid2")
@GeneratedValue(generator = "generator")
private String id;
public String getId() {
return id == null ? null : id.toUpperCase();
}
}
Conclusion
All 3 suggestions above provide a solution (or work around) for the problem. However, all solutions have their own positive and negative sides:
- In my opinion, keep using the guid generation strategy instead is not a solution. It has been deprecated for a reason!
- Changing the field type from String to either byte[] or UUID is, from a Java perspective, probably the cleanest way to resolve the issue. However, if you don’t only use the entity class to describe the database entity, but also as a Data Transfer Object (DTO) class (to marshal the data to another format like JSON, for example), this can cause other issues. Because then the DTO model would change, which could obviously impact the exposed interfaces.
- Converting the value to uppercase in the getId() method does the job. However, it isn’t the cleanest solution in the code, because it adds some complexity. But if you would also use your entity object as a DTO, unlike option 2, this does not result in a change of the DTO model.
To wrap up, both solution 2 and 3 work perfectly fine and are even proposed as solutions/workarounds by Hibernate. My personal preference would be solution 2, because your code remains cleaner. However, if there is a good reason why the field type should not change (so for example when the entity class is also used as DTO class), I’d go for solution 3.
Geen reacties
Geef jouw mening
Reactie plaatsenReactie toevoegen