Hiberate generates #GeneratedValue that causes UniqueConstraint violation - java

I have code that uses Hibernate to write to an Oracle table. It uses a SequenceGenerator to generate unique id's. Say I have id's 1 through 40 in the database. What happens is that if any users are deleted from the table, it leaves a gap (say, id=24) in the id's in the table. Then, when a new user is created, the new user's id is set by Hibernate to 24.
Now there is a problem because the immediate next new user gets an id=25, which causes a UniqueConstraint exception.
Is there something I'm doing wrong? How do I make Hibernate stop generating sequence values that already exist in the table?
#Entity
#Table(name="User")
public class User {
#Id
#SequenceGenerator(name="UserGen", sequenceName="UserSeq")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="UserGen")
private Integer id;
#Column(length=64, unique=true)
private String username;
...
Here is the sequence info in Oracle:
CREATED 31-OCT-16
LAST_DDL_TIME 31-OCT-16
SEQUENCE_OWNER USERSERVICE
SEQUENCE_NAME USERSEQ
MIN_VALUE 1
MAX_VALUE 9999999999999999999999999999
INCREMENT_BY 1
CYCLE_FLAG N
ORDER_FLAG N
CACHE_SIZE 20
LAST_NUMBER 81
PARTITION_COUNT
SESSION_FLAG N
KEEP_VALUE N

You have to define your SequenceGenerator to have the same allocationSize as the INCREMENT_BY value of your sequence.
#SequenceGenerator(name="UserGen", sequenceName="UserSeq", allocationSize = 1)
I've faced this problem before (in PostgreSQL) and eventually I just changed it to #GeneratedValue( strategy = GenerationType.IDENTITY ) and removed the SequenceGenerators entirely, since they were already used as default value on insert when not specified. It's a slight performance boost when your sequence increment size is 1, because with the SequenceGenerator Hibernate calls the sequence manually, using one extra query that it can spare.

Related

Jpa generatedValue skipping values

I have the next problem
POJO
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idUser;
#Email
#Column(length = 64, unique = true)
private String email;
When the email constrains fail, the next generated entity doesn't follow the previous id.
So, it goes like
1
2, (email constraint fails), 4. Jpa skipped number 3....
This is normal for many databases; to support concurrent transactions, underlying sequence numbers may be incremented by transactions that are later rolled back, but the database doesn't attempt to "fill in" the missing values, since it would be prohibitive to keep track of them.

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?

Hibernate and Oracle: Primary Key constraint violation

In my web application I have multiple scheduled services which work on same entities ( like article, customer...etc). If I run a single service at time I've no problem, but when I run two services I get an error because the primary key unique constraint is violated.
As primary key I use a generated Long value:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
public Long getId() {
return id;
}
Every service read data from a txt file, create its entities and every 20 entities execute a flush on hibernate session followed by a clear. Only at the end of the execution of the service the session is committed.
How can I solve?
Oracle supports only sequences for generated keys. Add a sequence to your database:
CREATE SEQUENCE ARTICLE_SEQ;
Change your annotations to:
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="articleSequence")
#SequenceGenerator(name="articleSequence", sequenceName="ARTICLE_SEQ",allocationSize=1) 
It's best to use a separate sequence for each table/type.
Create sequence in DB and use it like #GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_NAME").

JPA/Ebean - force #Id to strictly increment by +1 with PostgreSQL

I have the following Model:
public class Parameter extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public long id;
}
The records created by save() look like this:
play=> select id from parameter;
id
----
1
2
3
4
5
21
(6 rows)
Is there a way that I can tell JPA to always increment the id by exactly one, instead of randomly jumping to 21?
What you are observing (jump to 21) is likely to be an implmentation detail of Ebean prior to 4.0.5. Ebean is using a 'fetch ahead' mechanism on the sequence. After 4.0.5 Ebean switched to use Postgres SERIAL by default (so by default you won't see this with Ebean/Postgres after 4.0.5). Refer: https://github.com/ebean-orm/avaje-ebeanorm/issues/97
That all said - 'incrementing EXACTLY by 1' is something you generally want to avoid as it becomes a concurrency issue (avoid making the autoincrement ID transactional as that introduces contention). When you need 'increment exactly by one' semantics (like cheque numbers etc) then you can look to create in batch in advance.
You should be able to achieve this by using #TableGenerator and allocationSize=1. An example code something like the following. You need to create a separate table to store the PK, additional work, but more portable than #GeneratedValue.
#TableGenerator(name="MAIN_PK_GEN", table="pk_gen", pkColumnName="GEN_NAME", valueColumnName="GEN_VALUE", pkColumnValue="MAIN_PK_GEN", allocationSize=1)
#Id #GeneratedValue(strategy=GenerationType.TABLE, generator="MAIN_PK_GEN" )
#Basic(optional = false)
#Column(name = "PK")
private Integer pk;
(But using allocationSize=1 may not be efficient)
See this tutorial for step by step explanation.
If you are using Oracle don't forget to add SequenceGenerator with an allocationSize of 1 :
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "mysequence")
#SequenceGenerator(name = "mysequence", sequenceName = "mysequence", allocationSize = 1)
private Long id;

GeneratedValue in Postgres

I have my entity class mapped like below:
#Entity
#Audited
#Table(name="messages_locale")
public class Locale {
#Id
#GeneratedValue
#Getter #Setter //Project Lombok's annotations, equal to generated getter and setter method
private int id;
(...)
I create clean new database ,and properties:
< prop key="hibernate.hbm2ddl.auto" >create < /prop>
WHY THE HELL (Sorry, almost two days wasted on this bug) after created database, i got a sequence in my postgres db?:
CREATE SEQUENCE hibernate_sequence
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 2
CACHE 1;
ALTER TABLE hibernate_sequence
OWNER TO postgres;
I dont want to have a sequence, I want to have just auto increment auto generated values..
I think the accepted answer from Petar is not correct, or not correct any longer. The auto-increment in Postgres is handled through SERIAL pseudo type, that’s correct. However, the mapping that Petar gives will result in the following DDL generated by Hibernate 5.1:
CREATE SEQUENCE users_id_seq START 1 INCREMENT 50;
CREATE TABLE … (
id INT8 NOT NULL,
…
);
This is not using SERIAL, but a Hibernate managed sequence. It is not owned by the table and no default value has been set. Of course, DDL generation is a feature that many people do not use in production (but many take the generated code as a template).
If you hand-write your DDL and actually used SERIAL, then using GenerationType.SEQUENCE may even conflict with the database behaviour. The correct way to map Hibernate with Postgres’ preferred ID strategy is using GenerationType.IDENTITY. Incidentally, the code is also much shorter and more readable:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
In PostgreSQL auto-increment is handled using the SERIAL pseudo type. You use this type when you execute CREATE TABLE.
Now to the point - this SERIAL pseudo type creates a sequence.
Autoincrement in PostgreSQL is handled using the created sequence. The default value of the id column becomes - nextval('your_sequence_name').
In Hibernate for an User entity:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_seq_gen")
#SequenceGenerator(name = "users_seq_gen", sequenceName = "users_id_seq")
public Long getId() {
return id;
}
Read here:
http://www.postgresql.org/docs/8.4/static/datatype-numeric.html#DATATYPE-SERIAL
http://www.neilconway.org/docs/sequences/

Categories