hibernate #Id #GeneratedValue with non auto-increment and i dont have sequence - java

i have situation on which i try to persist entity with id that depends on the max id value, for example the new entity id will be MAX(id)+1.
now i try to use JPA to persist this entity
#Entity
#Table(name = "product")
public class ProductDetails {
#Id
#GeneratedValue
private String id;
i used strategy = GenerationType.AUTO, strategy = GenerationType.IDENTITY,strategy = GenerationType.SEQUENCE,strategy = GenerationType.TABLE none of them work, so i think i can solve it through selecting the max id then +1 and use that value (i did not try it) what i am asking for, is there is any way to handle this situation through JPA or Hibernate.Note:the id columns is not auto-increment and the db doesn't have sequence.

Don't use String as Primary key. if you need id like "ABC123" then take 2 id columns. One as id(int) PK, second as display_id(String). You can auto-generate display_id in database level using trigger.

If you use String as a type of your Id you shouldn't use auto-increment cause String is something that can't be incremented since it's not a number type. Just leave #Idand add #GeneratedValue(generator = "uuid") - that should work.
Additionally you can add #GenericGenerator(name = "uuid", strategy = "uuid2")

Related

Java JPA Hibernate OneToOne vs HashMap - Which One Should I Use and Why?

I have a question about performance and common practice, if someone could explain this to me.
I have recently started using JPA and hibernate and have come across an Entity that has a foreign key and I need to get some data from it. So for example: CustomerAddress has a City and that city has a lot of detail and also a name.
SQL:
select
CA.Id, CI.Name
from
CustomerAddress as CA
inner join City as CI
on CA.CityID = CI.Id
So now in Java JPA Entity I can have a one-to-many annotation:
#Entity
#Table(name = "CustomerAddress")
public class CustomerAddressEntity {
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "city", referencedColumnName = "id", insertable = false, updatable = false)
private City city;
}
Where City is also an #Entity with #Id and simple object.
Which in my opinion does not turn out the best because it makes a lot of SQL requests.
And then I have the option having just two findAll() calls at the beginning, where I would collect all the City Entities in a HashMap<String, City> and when needing the name I would just call hashmap.get(key).getName().
EDIT (thanks for the heads up :)):
And when using this HashMap I can use a simpler Entity without the #JoinColumn
#Entity
#Table(name = "CustomerAddress")
public class CustomerAddressEntity {
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "CityID")
private Long cityId;
}
In the hashmap case I only get two SQL calls and I think it works much faster. Is there a way to get this behavior also using JPA and hibernate?
If my question and code needs some more refinement please let me know.. I can edit the question with more details and perhaps if necessary provide a working example. Thank you for your thoughts :)
And the same would go for OneToMany, where the hashmap would be: new HashMap<String, List<City>> for example - I mean the whole example should be created a bit differently - I guess it could even be a HashMap<String, HashMap<String,City>> - if one would need quick access to the City by Id or sth... but i digress :) I will edit the question and respond to comments as I will go.. and refine the question if necessary.. I would just like to hear some thoughts and where my thinking is wrong :) and what am I failing to see and missing :)
EDIT: For example a code that would create a lot of SQL requests:
public interface CustomerAddressRepository extends JpaRepository<CustomerAddressEntity, Long> {
#Override
List<CustomerAddressEntity> findAll();
}
This for example creates an SQL Query (I would use findAll() at the beginning to list all - or most of the Entities for the user) and you would get an SQL query for every Entity because it would want to find the Name of the City as well - because the ID of the City Entity does not really help to the user.
Also - I like to have all the Entities in my RAM so I can do a quick search for the user more responsive - So a search does not always do SQL Query + #(found results) Queries.
The HashMap has nothing to do with the fact that Hibernate issues a query when you want to get the name of the City object.Here's why it's happening.
In your CustomerAddressEntity you have a OneToOne with City , and since you have a #JoinColumn there ,it means that CustomerAddressEntity database Table will have the Primary Key of the City table as a foreign key , and since you specified in your class that it should be fetched LAZY,Hibernate will create a Proxy object wrapping the City object,ready to get queried from the database in case you call any getMethod ,like getName() ,(excluding the getId() method since the ID exists prealably in the proxy object,you can check the sql query logs and see that the query selects the foreign key with all the other fields of CustomerAddressEntity ),that's why when you trigger the getName() method Hibernate will fetch that entity from the database.

How to enable the Hibernate HiLo entity identifier optimizer strategy

I'm initializing Hibernate without any XML by something like
org.hibernate.SessionFactory sessionFactory =
new org.hibernate.cfg.Configuration().
.setProperty(...)
.setProperty(...)
...
.buildSessionFactory();
My classes use an ID like
#Id #Generated(GenerationTime.INSERT) #GeneratedValue private Integer id;
The generator used is SequenceStyleGenerator, which seems to be the replacement for the deprecated SequenceGenerator and SequenceHiLoGenerator and whatever. It uses
public static final int DEFAULT_INCREMENT_SIZE = 1;
and seems to allow configuration via
public static final String INCREMENT_PARAM = "increment_size";
but that's all I could find out. I guess I have to set some property "xxx.yyy.increment_size" or pass it in another way to Hibernate, but I can't see how.
I'm aware of #SequenceGenerator, but it seems to be completely ignored
I guess you are looking for how to set increment_size property for your SequenceSytleGenerator.
Sample snippet below setting increment_size using #GenericGenerator annotation with hilo optimizer and SEQUENCE strategy.
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "hilo_generator")
#GenericGenerator(
name = "hilo_generator",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
// Or leave it out to get "hibernate_sequence".
#Parameter(name = "sequence_name", value = "hilo_sequence"),
// Or leave it out as this is the default.
#Parameter(name = "initial_value", value = "1"),
#Parameter(name = "increment_size", value = "5"),
#Parameter(name = "optimizer", value = "hilo")
})
There's no way you can globally set the DEFAULT_INCREMENT_SIZE with a Hibernate configuration property. You need to use the #Id configuration properties instead.
#Generated vs #GeneratedValue
You don't need to use #Generated along with #GeneratedValue. The #Generated annotation is for the non-id entity attributes that are generated by the database during INSERT or UPDATE. For more details about the #Generated annotation.
On the other hand, the #GeneratedValue is only for the entity identifier attributes, and it's what you need to use when the entity identifier is generated automatically upon persisting an entity.
Sequence generator
The sequence generator requires an extra database roundtrip to call the sequence object when you persist the entity. For this reason, Hibernate offers sequence-based optimizers to reduce the number of roundtrips needed to fetch entity identifier values.
Now, if you want to use hilo, the identifier mapping will look as follows:
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "post_sequence"
)
#GenericGenerator(
name = "post_sequence",
strategy = "sequence",
parameters = {
#Parameter(name = "sequence_name", value = "post_sequence"),
#Parameter(name = "initial_value", value = "1"),
#Parameter(name = "increment_size", value = "3"),
#Parameter(name = "optimizer", value = "hilo")
}
)
private Long id;
Apart from having to use the Hibernate-specific #GenericGenerator, the problem with hilo is that the generated identifiers don't include the database sequence value, so an 3rd-party client using the database will not know how to generate the next identifier value unless they know the hilo algorithm and the allocationSize.
For this reason, it's better to use pooled or pooled-lo.
Pooled optimizer
The pooled optimizer is very easy to set up. All you need to do is set the allocationSize of the JPA #SequenceGenerator annotation, and Hibernate is going to switch to using the pooled optimizer:
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "post_sequence"
)
#SequenceGenerator(
name = "post_sequence",
sequenceName = "post_sequence",
allocationSize = 3
)
private Long id;
Pooled-lo optimizer
To use the pooled-lo optimizer, just add the following configuration property:
<property name="hibernate.id.optimizer.pooled.preferred" value="pooled-lo" />
Now, the entity identifier mapping is identical to the one I showed you before for the pooled optimizer.
To understand how the pooled-lo works, check out this diagram:
If you have been using the legacy hilo optimizer, you might want to switch to using pooled or pooled-lo, as hilo is not interoperable with other clients that might not be aware of the hilo identifier allocation strategy.

JPA 2 #SequenceGenerator #GeneratedValue producing unique constraint violation

Problem Overview
At seemingly random times we get an exception "postgresql duplicate key violates unique constraint." I do think I know what our problem"s" are but I don't want to make changes to the code without having a reproducible test case. But since we haven't been able to reproduce it in any environment other than randomly in production, I'm asking assistance from SO.
In this project we have multiple postgres databases, and a primary key sequence configured for each table in each database. These sequences are created like this:
create sequence PERSONS_SEQ;
create sequence VISITS_SEQ;
etc...
We use these sequences to generate the primary keys for the entities like this:
#Entity
#Table(name = "visits")
public class Visit {
#Id
#Column(name = "id")
#SequenceGenerator(name = "seq", sequenceName = "visits_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
private int id;
...
}
#Entity
#Table(name = "person")
public class Person {
#Id
#Column(name = "id")
#SequenceGenerator(name = "seq", sequenceName = "persons_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
private int id;
...
}
Analysis
I think I recognize 2 problems with this configuration:
1) Both #SequenceGenerators specify the same name attribute even though they are supposed to map to different database sequences.
2) The #SequenceGenerator allocationSize attribute defaults to 50 (we're using hibernate as the JPA provider) so I think the create sequence syntax should specify how much the sequence should increment by, specifically by 50 to match the allocationSize.
Based on this guess, I think the code should be modified to something like this:
create sequence PERSONS_SEQ increment by 50;
create sequence VISITS_SEQ increment by 50;
etc...
#Entity
#Table(name = "visits")
public class Visit {
#Id
#Column(name = "id")
#SequenceGenerator(name = "visits_seq", sequenceName = "visits_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "visits_seq")
private int id;
...
}
#Entity
#Table(name = "person")
public class Person {
#Id
#Column(name = "id")
#SequenceGenerator(name = "persons_seq", sequenceName = "persons_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "persons_seq")
private int id;
...
}
I would just test this rather than asking the question on SO, but again, we have not been able to reproduce this production issue in any other environments. And even in production the unique constraint violation only occurs at seemingly random times.
Questions:
1) Am I correct in my analysis of what the changes should be to fix this unique constraint violation?
2) What are the best practices for using sequence generators when using hibernate as a JPA provider?
Yes, your analysis is correct. You identified correctly the problem (we had a similar problem).
And... if you gonna put that in production, don't forget to:
either generate manually the sequence table for the new sequence generator WITH the correct initial value/initial ID (otherwise hibernate will begin from 1 and you will get again )
or set that value in Code (check initalValue in #SequenceGenerator).
I am not able to enumerate the best practices, but I suppose you could lower the limit of 50. Also I do not have experience with PostgreSQL, but in MySQL you have a simple table for the seq. generator and hibernate makes the entire stuff.
Had a same problem — for some reason hibernate wasn't picked the right number from the sequence. Tried all approaches with no luck and finally came to this solution:
#Entity
#Table(name = "events")
#SequenceGenerator(name = "events_id_seq", sequenceName = "events_id_seq", allocationSize = 1)
public class Event {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "events_id_seq")
private BigInteger id;
I've had to put #SequenceGenerator on top of class, not the method, also allocation size was set to 1 (if you'll left this value as default, it will start to produce negative ids).
spring-data-jpa 2.1.2, hibernate 5.3.7, pg 42.2.5
I hade a similar problem. In my case, I imported data directly via SQL. This led to a problem with the 'hibernate_sequence'. The hibernate_sequence was by id 123 but there were rows in my table where the id was greater than 123.
I gone through the same problem. and I tried this to fix the problem. may be this is not the best solution but i hope it will solve your problem for now.
#SequenceGenerator(schema = "DS_TEST",name = "SEQ_PR_TEXT",sequenceName = "SEQ_PR_TEXT",
allocationSize = 1)
public class TextEntity {
#Id
#GeneratedValue(generator = SequenceConstant.SEQ_PR_TEXT,
strategy = GenerationType.SEQUENCE)
#Column(name = "PR_TEXT_ID")
private Long id;
}

Hibernate, id, oracle, sequence

My database is Oracle, and my id column value is an Oracle sequence, this sequence is executed by a trigger, so, before each row is inserted this trigger use this sequence to get the id value. So I am confused in which id strategy generation should I define in my entity class.
#GenericGenerator(name = "generator", strategy = "increment")
#Id
#GeneratedValue(generator = "generator")
or
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "idGenerator")
#SequenceGenerator(name="idGenerator", sequenceName="ID_SEQ")
or
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Really confused, Could someone shed some light on this subject? Please explain clearly..
I had also a projet where an Oracle DB that provides the data to my #Entity classes. As you said, a sequence generates the id for the PK of the table via a trigger. This was the annotations that I used in one of these classes:
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "G1")
#SequenceGenerator(name = "G1", sequenceName = "LOG_SEQ")
#Column(name = "ID", unique = true, nullable = false, precision = 22, scale = 0)
public int getId() {
return this.id;
}
This is the second syntax that you have showed in your post.
There's no call to the trigger in the Java code because the trigger is managed by the DB. I remember that I had to have the sequence and the trigger at the same time in the DB if I didn't wanted to have problems. The trigger asked if the id of the row to insert is null or = 0. In this case the sequence LOG_SEQ is called.
So if you provide a value to the #Id of your entity it could be inserted in the DB (if that Id doesn't exist) and the sequence would not be called. Try to see the code of the trigger to see exactly what it happens.

How is my id being generated with JPA using Hibernate with the Oracle 10g dialect?

I have some code:
#Id
#SequenceGenerator(name = "SOMETHING_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SOMETHING_SEQ")
#Column(name = "SOMETHING", nullable = false)
private Long id;
How is hibernate providing my id?
I see in my database there a single sequence named 'hibernate_sequence' and no other hibernate 'special tables'.
Actually, here your SOMETHING_SEQ is the name of sequence you configured somewhere in your hibernate config. And hibernate_sequence is the sequence name in the database. In configuration it would be looking something like below,
<sequence-generator name="SOMETHING_SEQ"
sequence-name="hibernate_sequence"
allocation-size="<any_number_value>"/>
You can completely skip this configuration by using annotation instead. Then your #SequenceGenerator annotation would need to provide few more paramters. Below is the example.
#SequenceGenerator(name="SOMETHING_SEQ", sequenceName="hibernate_sequence", allocationSize=10)
For example multiple entity classes would do something like below,
#Entity
public class Entity1 {
#Id
#SequenceGenerator(name = "entity1Seq", sequenceName="ENTITY1_SEQ", allocationSize=1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "entity1Seq")
#Column(name = "ID", nullable = false)
private Long id;
...
...
}
#Entity
public class Entity2 {
#Id
#SequenceGenerator(name = "entity2Seq", sequenceName="ENTITY2_SEQ", allocationSize=10)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "entity2Seq")
#Column(name = "ID", nullable = false)
private Long id;
...
...
}
How is hibernate providing my id?
Well, you explicitly told the JPA engine to generate identifier automatically (with the #GeneratedValue annotation) using a strategy of type SEQUENCE indicating that a database sequence should be used to generate the identifier. In case you wonder, sequences are database specific objects (e.g. Oracle) that can be used to generate unique integers.
I see in my database there a single sequence named 'hibernate_sequence'
You didn't use the sequenceName annotation element in your #SequenceGenerator to specify the name of the database sequence object to use so Hibernate created a default sequence object during schema generation (which defaults to hibernate_sequence). To specify a sequence, do it like this:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_entity_seq_gen")
#SequenceGenerator(name = "my_entity_seq_gen", sequenceName="MY_ENTITY_SEQ")
private Long id;
In order to name the sequence you have to set the sequenceName in your #SequenceGenerator annotation:
#GeneratedValue(name="gen", strategy = GeneratorType.SEQUENCE)
#SequenceGenerator(name="gen", sequenceName="Sequence_Name", allocationSize = 1)
#Id
public Long getId()
{
// ...
}
Of note, if you are using a pre-existing generator, your allocationSize must match the allocation size of that generator.
In Oracle you don't have the auto_increment type as in MySQL. So, to generate an auto_increment column you need to use a sequence.
This is an example of how you can achieve this.
create table test (id number, testdata varchar2(255));
create sequence test_seq
start with 1
increment by 1
nomaxvalue;
create trigger test_trigger
before insert on test
for each row
begin
select test_seq.nextval into :new.id from dual;
end;
So you create a sequence and use a trigger before each row is inserted to add its id.
So hibernate must be doing something like this, or instead of using the trigger doing
insert into test values(test_seq.nextval, 'no trigger needed!');
Note: Example taken from here

Categories