Dynamic values for JPA id generation table using #TableGenerator - java

I have a Spring based application which uses JPA 2.1 for the persistence layer.
I use #TableGenerator and #GeneratedValue annotations to handle my primary key id generation.
These are the annotations:
#TableGenerator(name = "user_gen", table = "id_gen", pkColumnName = "gen_name", valueColumnName = "gen_val", allocationSize = 1)
…
#GeneratedValue(strategy = GenerationType.TABLE, generator = "user_gen")
I use this entity to insert records in my user table.
It was all working good.
Now I have a new problem. An external application needs to insert records in my user tables. This causes primary key violation at my end.
Is there any option in JPA which will make my id_gen table's gen_val value updated based on the max id of my user table?
[I could not find such a solution in my research]
Other thoughts to fix the issue are also welcome.

You might make it work with custom id generator where you would check for maximum id before each insert, but I wouldn't recommend it. In my opinion, this would be best handled on database level.
How is that application generating id's? They have to get it from somewhere, so why not why not handle id in that step. Best option would be if it can use the same id_gen mechanism for this. Several ideas:
After inserting the data, they could just call your stored procedure which would take care of synchronizing ids
They could call your stored procedure prior to insert, which would return them the id for the new row
They could call your stored procedure which would insert the data instead of them inserting it directly, and you would handle the id

Related

Problems with #GeneratedValue in the H2 database

Hello StackOverflow Community, I have a problem with the annotation #GenerateValue. I want that JPA generates the values for my ID column. But I have another column where people can write some sort of tasks (todo list).
My code seems like this:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
and I have a SQL data file that write some data at the beginning in my h2 database:
INSERT INTO task VALUES(1, 'go to the gym');
INSERT INTO task VALUES(2, 'eat with tom');
INSERT INTO task VALUES(3, 'meetup');
INSERT INTO task VALUES(4, 'doing some homeworks');
INSERT INTO task VALUES(5, 'doing some exercise');
INSERT INTO task VALUES(6, 'studying with Mat');
my problem is, when I delete the integer values on my SQL data file, my compiler says always that I have to declare an id for the tasks, but I thought the #GenerateValue automatically generate the id's for me?
You have to use #GeneratedValue(strategy = GenerationType.IDENTITY) or #GeneratedValue(strategy = GenerationType.SEQUENCE) and make sure that you have index or sequence on database level. Also when using auto-generated ids, don't specify them explicitly when inserting.
Correct:
INSERT INTO task(description) VALUES('go to the gym');
but I thought the "#GenerateValue" automatically generate the id's for me?
It's a not the correct assumption, Hibernate just uses ids provided by db. To get it to work you need to create index/sequence for your primary key column.
You're mixing up sql and hibernate. #GeneratedValue is declared in your java code, so that hibernate knows, that when you're persisting an entity without an id, it should be generated by given strategy.
On the other hand you tried to make insertions using sql without passing primary key, so you're explicitly said, that it should be NULL, which is clearly against the constraint.

Sequence "HIBERNATE_SEQUENCE" not found for h2 test with GenerationType.AUTO

I am trying to migrate one of our services to Spring Boot 2.0.3.
While most of the tests are fine, one of them fails with error:
Caused by: org.h2.jdbc.JdbcSQLException: Sequence "HIBERNATE_SEQUENCE" not found; SQL statement:
call next value for hibernate_sequence [90036-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.command.Parser.readSequence(Parser.java:5970)
at org.h2.command.Parser.readTerm(Parser.java:3131)
at org.h2.command.Parser.readFactor(Parser.java:2587)
This is really confusing because all teh entities rely on the same generation id mechanism:
#GeneratedValue(strategy = GenerationType.AUTO)
It's a repository test and the repository itself is very straight-forward:
#Repository
public interface OrderDetailsRepository extends JpaRepository<OrderDetails, Long> {
OrderDetails findFirstByOrderIdOrderByIdDesc(String orderId);
}
What can possible go wrong here?
PS: And, yes, there is both orderId and Id field present in the entity.
When you choose #GeneratedValue(strategy = GenerationType.AUTO) Hibernate selects a generation strategy based on the database-specific dialect.
The problem in your case is hibernate can't find the HIBERNATE_SEQUENCE and thus can't create a new object for the sequence. Try adding a sequence like this and it should solve the problem, but could lead to inconsistencies with the data...
CREATE TABLE CUSTOMER(
  id int primary key,
);
CREATE SEQUENCE HIBERNATE_SEQUENCE START WITH 1 INCREMENT BY 1;
I would suggest using the GenerationType.SEQUENCEand try to recreate your id pattern with your custom db sequence. You can read more about the GenerationType's
here
I encountered the same issue when written sample code for spring boot with h2. please find the details below of my findings.
In your entity class sequence is not given and check your table as well i.e. have you given AUTO_INCREMENT for primary key?
Please follow as below.
1. Check your ddl once and set auto_increment for primary key (see below for id)
CREATE TABLE EMPLOYEES (
id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(250),
last_name VARCHAR(250),
email VARCHAR(250) DEFAULT NULL
);
Check your entity class and update primary key as below
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Please make a note that GenerationType is given IDENTITY you can give AUTO as well. Also if you are using h2 in-memory DB and table inserted few records while boot-start (if dml file available in resource) then hibernate insertion may give unique constraint because sequence 1,2,3..(depends on how many records inserted while startup) may have already used and as I said above hibernate will generate the sequence from 1 and will increment by 1 for every new insertion. So I would suggest don't insert records while boot startup better to insert programmatically.
For your learning you can use as given above but if it may use in production then better to implement your own logic to generate the sequence.
I had similar problem. If I understand things correctly It went down like this.
Before Spring upgrade I used AUTO - but it actually opted by default to IDENTITY strategy. I had auto incrementing PKs defined like this:
id BIGINT AUTO_INCREMENT PRIMARY KEY
Everything was fine.
With spring upgrade I had to specify H2 dialect:
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
I've read that if you use Hibernate as your persistence provider, it selects a generation strategy based on the database specific dialect. For H2 it probably opted for global sequence (that's what AUTO should mean according to JPA spec) - and it didn't find the sequence.
Solution is of course create the sequence (as suggested above) or manually override to originally auto selected IDENTITY.
CREATE SEQUENCE HIBERNATE_SEQUENCE START WITH 1 INCREMENT BY 1;
#GeneratedValue(strategy = GenerationType.IDENTITY)
I believe that root cause is that meaning of AUTO is/was not consistent/well defined/understood in time. Probably original 'auto' switch to IDENTITY was basically a bug.

Generate identifiers based on Hibernate #SequenceGenerator annotations

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?

JQPL Update Query to update entity without using the primary key

This may be a simple question, but I'm trying to find out if there is a way that I can create a JPQL update query that would allow me to update a single Persisted Entity using a unique column identifier that is not the primary key.
Say I have and entity like the following:
#Entity
public class Customer {
#ID
private Long id;
#Column
private String uniqueExternalID;
#Column
private String firstname;
....
}
Updating this entity with a Customer that has the id value set is easy, however, id like to update this customer entity using the uniqueExternalId without having to pre-query for the local entity and merge the changes in or manually construct a jpql query with all the fields in it manually.
Something like
UPDATE Customer c SET c = :customer WHERE c.uniqueExternalId = :externalId
Is something like this possible in JQPL?
You cannot do it in the exact way you describe - by passing an entity reference, but you can use bulk queries to achieve the same effect.
UPDATE Customer c SET c.name = :name WHERE c.uniqueExternalId = :externalId
Please note that you will have to explicitly define each updated attribute.
It is important to note that bulk queries bypass the persistence context. Entity instances that are managed within the persistence context will not reflect the changes to the records that are changed by the bulk update. Further, if you use optimistic locking, consider incrementing the #Version field of your entities with the bulk update:
UPDATE Customer c SET c.name = :name, c.version = c.version + 1 WHERE c.uniqueExternalId = :externalId
EDIT: The JPA 2.0 spec advises in § 4.10:
In general, bulk update and delete operations should only be performed
within a transaction in a new persistence context or before fetching
or accessing entities whose state might be affected by such
operations.

Generate ID based on number of rows in table in Spring

I am developing a spring 3 MVC application. I am using hibernate as the ORM. While defining the model, i have an ID field. I want to auto generate it in such a way that its value is the current number of rows in the table + 1. How can it be done?
AUTOINCREMENT column or a sequence will do the trick. In Hibernate simply annotate id with #GeneratedValue:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
Hibernate will automatically set the id to next available value.
See also
Hibernate Auto Increment ID

Categories