I've been searching for a way to generate IDs without actually persisting entities in Hibernate. The reason for this, is that I want to manually insert new database rows. The ideal situation would be to use the auto increment that is present in MySQL, but that is not possible due to the fact that I'm using InheritanceType.TABLE_PER_CLASS. (It is not possible to switch strategies for either inheritance mapping or sequence generation, since the project is already quite mature.)
The conclusion from the research I've done is (please correct me if I'm wrong):
- Hibernate reserves a block of IDs by increasing next_val by 1. The range of IDs Hibernate can assign to entities without having to query and update the sequence table again, is (using the old value of next_val) next_val * allocationSize ... (next_val + 1) * allocationSize
My observations from playing with the conclusion:
- Hibernate generates IDs that have a value far smaller than next_val * allocationSize (which is in conflict with the conclusion) - in fact, it seems that Hibernate uses IDs up to next_val * allocationSize (but not quite: the value of next_val is 350781 whereas the highest ID in the database is 350744, with an allocationSize of 20)
- Sometimes, the value of next_val is smaller than the highest ID in the database
This leaves me with the only option of delegating the ID generation process to Hibernate, since it is unclear to me how IDs are generated. Besides the fact that it is unclear to me, I would like to have a solution that works for different ID generation strategies.
So the approach I want to take is to generate a bunch of ids from java code, which are then 'reserved' for me (the backing table containing the next_val gets updated), so I can use them in my INSERT queries.
This would look something like this:
Given the following definition:
@Entity
@Table(name="datamodel")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class DataModel {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="model_sequence")
@SequenceGenerator(
name="model_sequence",
sequenceName="model_sequence",
allocationSize=20
)
@Column(name="id", nullable=false, unique=true, length=11)
private int id;
}
Generate IDs in a way like this:
// This will be the ID generator, a
// long-lived object that generates ids
IDGenerator gen = new IDGenerator();
gen.initialize(DataModel.class, ... /* provide a serviceRegistry, sessionFactory, or anything else that might be needed to initialize this object */);
// This will generate the next ID based
// on the parameters passed to @SequenceGenerator
// and the values that are present in the DB.
int nextId = gen.generateNextId(/* provide a session, transaction, or anything else that might be needed for accessing the DB */);
The following posts have helped me get a bit further:
- Hibernate, @SequenceGenerator and allocationSize
- How to set up @Id field properly when database table is already populated JPA
- https://vladmihalcea.com/from-jpa-to-hibernates-legacy-and-enhanced-identifier-generators/
And my current attempt is stuck at this point:
URL configUrl = this.getClass().getClassLoader().getResource("hibernate.main.cfg.xml");
Configuration configuration = new Configuration().configure(configUrl);
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
SessionFactorysessionFactory = configuration.buildSessionFactory(serviceRegistry);
Properties generatorProperties = new Properties();
// Properties would normally be read from annotations
// that are present on the entity class
generatorProperties.put("name", "model_sequence");
generatorProperties.put("sequence_name", "model_sequence");
generatorProperties.put("allocation_size", 20);
sequenceGenerator = new SequenceStyleGenerator();
sequenceGenerator.configure(LongType.INSTANCE, generatorProperties, serviceRegistry);
// Without the line below, the queries that Hibernate uses to read
// and alter the sequence table are not initialized (they are null)
// and Hibernate throws an exception
sequenceGenerator.registerExportables( database?? /* This requires a Database object, but how to provide it? */);
// In order to generate a new ID, do the following:
Session session = sessionFactory.getCurrentSession();
Serializable id = sequenceGenerator.generate((SessionImplementor)session, entity?? /* This requires an entity, but that is exactly what I'm trying to omit. */);
My considerations on this approach:
- As I understand, Hibernate reserves a block of IDs (rather than just 1 ID). This implies that the block of IDs is maintained in-memory in the SequenceStyleGenerator instance. This would cause two blocks to be present simultaneously: one for the running web application, and one for my ID generation application.
- It might be problematic when the sequence table is not locked, in which case both instances may simultaneously read the same value from the table and end up using the same block of IDs.
Do any of you have an idea on how to do this?