Skip to main content

Hibernate: How to Save Entity With User Specified Id

Spoiled by Hibernate, we usually let Hibernate to generate the id for the entity object, like the following:

@MappedSuperclass 
public abstract class GuidEntity 
{ 
 @Id @GeneratedValue(generator = "uuid2") 
 @GenericGenerator(name = "uuid2", strategy = "uuid2")
 @Column(unique = true, nullable = false) 
 private String id; 
 
 //getter, setter, ... 
}

When you calls .save or .persist via Hibernate session or EntityManager, an id is generated automatically for the entity object using the specified generator by Hibernated. This is a neat feature but also means you don’t have control on what the value of the id is.

What if you want Hibernate to use your specified id to persist the entity object? It requires a little twist.

1. Why do you even want to do this?

Simple, imagine your application has tons of data and it support bulk data import/export via xls, csv, whatever format you like. Your customer plays with your system for a while with some data on a free trail. Now they want seriously become your customer and want to integrate their business data from their other applications with yours. They have a requirement to bulk import data with their ids into your application in order to maintain the data integrity across all applications of the customer.

Another scenario is that your application does pull/push data from/to another web service (maybe another product of your company), and they share the same domain schema for both applications. It is a requirement to have the same id to represent the same data information across the two applications. Then you want to sync the data from one application to another using the same id.

2. How?

Write a customized id generator class.

import java.io.Serializable; 
import org.hibernate.id.UUIDGenerator; 
import org.hibernate.HibernateException; 
import org.hibernate.engine.spi.SessionImplementor; 

public class UserSpecifiedIdOrGenerate extends UUIDGenerator 
{ 
 @Override 
 public Serializable generate(final SessionImplementor session, final Object obj) throws HibernateException 
 { 
  if (obj == null) { 
   throw new HibernateException(new NullPointerException()); 
  } 

  final Serializable id = session.getEntityPersister(null, obj).getClassMetadata().getIdentifier(obj, session); 

  return id == null? super.generate(session, obj) : id; 
 } 
}

Then, update your entity id annotation.

@MappedSuperclass 
public abstract class GuidEntity 
{ 
 @Id @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "IdOrGenerated") 
 @GenericGenerator(name = "IdOrGenerated", strategy = "com.yourdomain.UserSpecifiedIdOrGenerate") 
 @Column(unique = true, nullable = false) 
 provate String id; 

 //getter, setter, ... 
}

Now you can use this entity class as usual. For example, you can create an entity object, set its id to whatever you want, and call getCurrentSession().save(obj). The customized id generator class will take care of the id generation.

Important thing is that you should not use the convenient method getCurrentSession().saveOrUpdate(obj). You may get StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect).

This is because the id is set by the user, the non-empty id confuses Hibernate to use update(obj) to database instead of save, but the object with the user specified id does not exist in the database yet.

Note:

  1. strategy in @GenericGenerator should use the full qualified name with package:com.yourdomain.UserSpecifiedIdOrGenerate. Otherwise, you may get org.hibernate.MappingException: Could no
    t instantiate id generator.
  2. If you don’t use uuid2 as id, you can extends other Hibernate id generators, for example, SequenceGenerator.
  3. Why not use merge method? you definitely can create an entity object, set your id, and call merge(obj) to get the object attached to the current session. However, when you later on save this object, Hibernate will still generate id for you, not using the one you set. In other words, you almost have to use your customized id generator.

This article was originally published on https://coddicting.wordpress.com/.

Reference:

  1. http://stackoverflow.com/questions/3194721/bypass-generatedvalue-in-hibernate-merge-data-not-in-db/8535006#8535006
  2. https://forum.hibernate.org/viewtopic.php?p=2428082

Comments

Popular posts from this blog

Book Note - Learning Java Functional Programming (Richard M Reese)

The problem to solve is how to blend the various programming styles (functional programming in this case) available in Java 8 to meet an application's need. Disclaimer : All the paragraphs/codes are direct copy from the book . This note captures the key points in the book I found particularly useful to myself. Thank the author for creating such a good book on the functional programming in Java. Functional Programming Concept First-class and high-order functions are associated with functional programming. A first-class function is a computer science term. It refers to functions that can be used anywhere a first-class entity can be used. A first-class entity includes elements such as numbers and strings. They can be used as an argument to a function, returned from a function, or assigned to a variable. High-order functions depend upon the existence of first-class functions. They are functions that either: Take a function as an argument Return a function Java 8 ha...

Production Ready Spring WebFlux WebClient For Spring Web MVC

Starting from Spring 5, AsyncRestTemplate is deprecated in favour of WebClient from spring-webflux. One good thing is that you don’t have to use reactive async WebFlux in order to use WebClient. Instead, you can still use WebClient in a synchronous blocking way in Spring Web MVC so your existing projects can continue to work. When converting one of my existing applications from AsyncRestTemplate to WebClient, I found there wasn’t sufficient documentation or examples to do a quick replacement. After a bit of searching and learning by trial and error, here are the code snippets I came out eventually. Here is the code gist Reference : https://docs.spring.io/spring/docs/5.0.0.RELEASE/spring-framework-reference/web-reactive.html#webflux-client https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/ Create WebClient Requirements Create one WebClient bean for each service that requires specific configurations. Config timeout, such as c...