GeneratedValue counter for Id resets every time that the server and client are executed - jpa

I'm working on a JavaEE application with EJB and JPA.
My Entities, are defined, for instance, like this:
#Entity
public class Utente implements Serializable {
#Id
#GeneratedValue
private int cod_utente;
private String nome_utente;
private String morada_utente;
#Temporal(TemporalType.DATE)
private GregorianCalendar dnasc_utente;
private int tel_utente;
private List<GregorianCalendar> agenda;
#OneToMany
#JoinColumn(nullable=true)
private List<Prescricao> lista_presc;
When I create entities Utente, the keys are generated sequentially starting from one. If I shut down the clients and server and execute them again, the "counter" of the key generator is reestablished. This results in an error because the application will try to create another Utente with primary key "1".
Can please someone help me solve this problem?

The code:
#Id
#GeneratedValue
private int cod_utente;
doesn't set a specific stategy to generate the values for the ID.
It is the same as this code:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int cod_utente;
GenerationType.AUTO means that the persistence provider (in Glassfish the default persistence provider is EclipseLink) should choose an appropriate strategy for the database you are using. It looks like the persistence provider is choosing a strategy which is restarting the values after a server restart in your case.
There are different generation strategies, you can find some detailed information in the EclipseLink Wiki.
I guess your best bet is to use a database sequence (GenerationType.SEQUENCE) to generate the ID values.
Example:
Create a database sequence named GEN_SEQUENCE (if you let the persistence provider generate your tables I guess you can also let it create the sequence somehow but this example will show how to do it manually), you should look for information on how to do that in the database you are using (probably something like CREATE SEQUENCE gen_sequence;). Change your code to this:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_seq_gen")
#SequenceGenerator(name = "my_seq_gen", sequenceName = "GEN_SEQUENCE")
private int cod_utente;
You can also use the same sequence for different classes.
Update:
For the #SequenceGenerator you can set an allocationSize, this value is the amount of sequence values which get reserved. The default value is 50. When you have a sequence which starts at 0, the first time a value is requested from the sequence, the sequence allocates (and reserves) the values 0-49 (or 1-50). These values can be used by the persistence provider until all values have been used, then the next 50 values (50-99 or 51-100) will get allocated and reserved. The sequence remembers the current position, so that it doesn't give out the same range twice if it is used by multiple classes.
For the value of the allocationSize you can keep the default, but this may produce gaps in the IDs. If a sequence range (e.g. 0-49) gets allocated (reserved) and only one or some of the values are used (e.g. 0, 1 and 2) the other values of this range (3-49) will get "lost" on server restart. The next time a range of values is allocated it will be 50-99, so the next ID in your table will be 50.
Now you have the following IDs in your table: 0,1,2,50. Normally this shouldn't be a problem, but you can also set the allocationSize to a lower value or to 1 to avoid creating such gaps.
See also:
what is the use of annotations #Id and #GeneratedValue(strategy = GenerationType.IDENTITY)? Why the generationtype is identity?
what is sequence (Database) ? When we need it?
The differences between GeneratedValue strategies

Related

Non-primary key business-id containing unique number from database

I'm using JPA, Hibernate and Postgres. I'd like the code to be as solution neutral as possible, where JPA is a given.
My simplified entity looks like this:
#Entity
#Table(name = "example")
public class ExampleEntity {
#Id
#GeneratedValue
private UUID id;
private String businessId; //format is YYYY000001 where YYYY = current year. Current assumption: number is incrementing and reset every year, number is always filled up with leading 0 to make the key 10 digits
}
I always have a generated UUID as the primary key. The business-id shall only be set if a certain state has been reached and is therefore unrelated to when the entity has been created. I would like the database to take care of the incrementing number.
Preferably I'd like to solve this through JPA, but also see a "dirtier" solution where I fetch the sequence-id and generate the business-key in my logic.

Spring Boot Hibernate Problem with generating entity ID using sequence or identity

I am developed Rest API with sprint boot, jpa, hibernate and PostgreSQL. My goal is to be able to generate auto-incremented id for using with code and using database tool such as D Beaver without writing any extra queries for getting next ID value and etc.
I have created entity User. I tried generating id in two ways:
GenerationType.IDENTITY
When using GenerationType.IDENTITY it successfully creates table with name user, sequence with name user_id_seq and adds a default value for user.id column nextval('user_id_seq'::regclass). Everything in database is as expected and it works great with database tool, but problem occurs when I am trying to insert new row from my API. When trying to insert new row hibernate executes query
select currval('user_id_seq')
to get id value and I am getting error
org.postgresql.util.PSQLException: ERROR: invalid name syntax
because of those quotes around user. It should execute
select currval('user_id_seq')
I believe that the problem here is because I use table name user which is a reserved keyword, but I want to keep it this way because this naming matches other tables pattern.
GenerationType.SEQUENCE
If I use annotations:
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_generator")
#SequenceGenerator(name="user_generator", sequenceName = "user_id_seq", allocationSize=1)
it creates table 'user', sequence 'user_id_seq' but doesn't add user.id column default value, so I can't insert new rows using database tool without specifying id value. But using this generation type my API works fine.
It is also worth mentioning that I am using spring.jpa.hibernate.ddl-auto=create-drop and manually dropping and recreating schema each time so there wouldn't be any unnecessary sequences/tables left.
#Entity
#Table(name = "`user`")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other properties...
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
// other getters and setters...
}
So... It is possible to somehow connect those two ways and create one working solution? I need to have default value for column and that hibernate would also know how to generate that id.
P.S. I don't want to change table/entity naming or execute SQL to correct tables when running application. I believe that there should be a better approach.
After a lot of hours of debugging I ended up extending PostgreSQL82Dialect and overriding getIdentitySelectString function to remove quotes from table name.

How to properly use Locking or Transactions to prevent duplicates using Spring Data

What is the best way to check if a record exists and if it doesn't, create it (avoiding duplicates)?
Keep in mind that this is a distributed application running across many application servers.
I'm trying to avoid these:
Race Conditions
TOCTOU
A simple example:
Person.java
#Entity
public class Person {
#Id
#GeneratedValue
private long id;
private String firstName;
private String lastName;
//Getters and Setters Omitted
}
PersonRepository.java
public interface PersonRepository extends CrudRepository<Person, Long>{
public Person findByFirstName(String firstName);
}
Some Method
public void someMethod() {
Person john = new Person();
john.setFirstName("John");
john.setLastName("Doe");
if(personRepo.findByFirstName(john.getFirstName()) == null){
personRepo.save(john);
}else{
//Don't Save Person
}
}
Clearly as the code currently stands, there is a chance that the Person could be inserted in the database in between the time I checked if it already exists and when I insert it myself. Thus a duplicate would be created.
How should I avoid this?
Based on my initial research, perhaps a combination of
#Transactional
#Lock
But the exact configuration is what I'm unsure of. Any guidance would be greatly appreciated. To reiterate, this application will be distributed across multiple servers so this must still work in a highly-available, distributed environment.
For Inserts: if you want to prevent same recordsto be persisted, than you may want to take some precoutions on DB side. In your example, if firstname should be unique, then define a unique index on that column, or a agroup of colunsd that should be unique, and let the DB handle the check, you just insert & get exception if you're inserting a record that's already inserted.
For updates: use #Version (javax.persistence.Version) annotation like this:
#Version
private long version;
Define a version column in tables, Hibernate or any other ORM will automatically populate the value & also verison to where clause when entity updated. So if someone try to update the old entity, it prevent this. Be careful, this doesn't throw exception, just return update count as 0, so you may want to check this.

JPA #EmbeddedId: How to update part of a composite primary key?

I have a many-to-many relationship where the link table has an additional property. Hence the link table is represented by an entity class too and called Composition. The primary key of Composition is an #Embeddable linking to the according entities, eg. 2 #ManyToOne references.
It can happen that a user makes an error when selecting either of the 2 references and hence the composite primary key must be updated. However due to how JPA (hibernate) works this will of course always create a new row (insert) instead of an update and the old Composition will still exist. The end result being that a new row was added instead of one being updated.
Option 1:
The old Composition could just be deleted before the new one is inserted but that would require that the according method handling this requires both the old and new version. plus since the updated version is actually a new entity optimistic locking will not work and hence last update will always win.
Option 2:
Native query. The query also increments version column and includes version in WHERE clause. Throw OptimisticLockException if update count is 0 (concurrent modification or deletion)
What is the better choice? What is the "common approach" to this issue?
Why not just change the primary key of Composition to be a UID which is auto-generated? Then the users could change the two references to the entities being joined without having to delete/re-create the Composition entity. Optimistic locking would then be maintained.
EDIT: For example:
#Entity
#Table(name = "COMPOSITION")
public class Composition {
#Id
#Column(name = "ID")
private Long id; // Auto-generate using preferred method
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn( .... as appropriate .... )
private FirstEntity firstEntity;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn( .... as appropriate .... )
private SecondEntity secondEntity;
....

JPA Composite Primary Key generating

I have simple entity class (irrelevant methods omitted):
#Entity
#Table(name="CONNECTIONS")
public class Connection implements Serializable {
#Id private Long id_track;
#Id private Long id_carrier;
#Id private Date date_out;
#Id private Time time_out;
private Date date_in;
private Time time_in;
private Double price;
...
}
I expect that JPA (in my case Eclipse implementation) creates TABLE CONNETIONS with composite primary key that consists of id_track, id_carrier, date_out and time_out columns but it adds addidional column id (of type integer) What do I do wrong?
I can not reproduce this. Are you sure JPA is creating your table?
Also ensure you have recompiled and redeployed your code.
Perhaps enable logging, and include what JPA provider and version you are using.
Are you using Glassfish?