I'm upgrading Hibernate4 to Hibernate5. Spring-4.3.7 And facing the problem that hibernate assigns duplicate IDs to the objects while EntityManager.persist(object) that results in exception :-
org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was
already associated with the session : [com.domain.multilanguage.LiteralText#498]; nested exception
is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.domain.multilanguage.LiteralText#498][machine=]
I debug inside the SharedEntityManagerCreator and found that, hibernate only assigns 0 - 49 objects total 50 ids and after that it start duplicating the ids.
I'm using the sequence generator GenerationType.SEQUENCE.
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idGenerator")
#SequenceGenerator(name = "idGenerator", sequenceName = "HIBERNATE_SEQUENCE", allocationSize = 50)
#Column(name = "ID")
public long getId() {
return mId;
}
This is working fine with Hibernate4. What could be the problem here, please.
Related
I have an existing database table For e.g. T_STUDENTS on top of which I have to create a JPA entity. All three columns in the table are NON NULL and the table has a self-reference as mentor_id
id | name | mentor_id
-----|---------|----------
1 | John | 1
-----|---------|----------
2 | Marc | 1
-----|---------|----------
3 | Abby | 2
-----|---------|----------
4 | Jimy | 3
-----|---------|----------
5 | Boni | 4
-----|---------|----------
Each student has a mentor who is also a student. There is a strict OneToOne relationship between the student and the mentor. For id 1, there can't be any mentor, therefore it has the mentor id as it's own id. The ids are generated using a database sequence.
The problem is that while generating the first record with id 1, hibernate is not assigning the same id as mentor id even though I have created necessary relationships. Since columns can't be null and hibernate is not assigning mentor_id, SQLConstraint nonnull exception is thrown.
Following is how I have created the relationship.
#Entity
#Table(name = 'T_STUDENTS')
public class Student implements Serializable {
#Id
#SequenceGenerator(name = 'S_STUDENTS_SEQUENCE', allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = 'S_STUDENTS_SEQUENCE')
#Column(name = "id")
private Long studentId;
#Column(name = "name", length = 20)
private String studentName;
#OneToOne(optional = false, cascade = CascadeType.NONE)
#JoinColumn(name = "mentor_id")
private Student mentor;
// getters and setters
}
I have set CascadeType.NONE because else hibernate tries to retrieve 2 id's from sequence and tries to create 2 records which are not desirable.
The problem is how can I insert the very first record. Following is how the insert is being done.
Student student = Student.builder()
.setName('John')
.build();
student = student.toBuilder().setMentor(student).build();
return studentRepository.save(student);
If I change the relationship annotation to #ManyToOne since technically mentor_id is 1 is mapped to 2 students, I get the following exception
.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation
Edit 1: If relationship type changed to #ManyToOne and cascade is removed following error is observed.
org.hibernate.action.internal.UnresolvedEntityInsertActions.logCannotResolveNonNullableTransientDependencies - HHH000437: Attempting to save one or more entities that have a non-nullable association with an unsaved transient entity. The unsaved transient entity must be saved in an operation prior to saving these dependent entities.
Edit 2: Changed the cascade type to cascade = CascadeType.PERSIST and hibernate tries to persist the mentor as a separate record. I verified from logs that it tries to retrieve 2 different sequence ids and creates 2 insert queries, with both mentor_id as null.
NOTE: Finally I found the root cause. I was using Lombok builder in the JPA entity and it does not support the self-reference relationship yet.
I switched to public setters and it worked fine. See the link below for more details
https://github.com/rzwitserloot/lombok/issues/2440#event-3270871969
You can ignore the below solution.
I'm not very proud of the solution, but here is how I achieved it.
1.Removed auto sequence generation from the id.
#Id
#SequenceGenerator(name = 'S_STUDENTS_SEQUENCE', allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = 'S_STUDENTS_SEQUENCE')
#Column(name = "id")
private Long studentId
to
#Id
#Column(name = "id")
private Long studentId;
2.Changed the mapping to the simple foreign key field.
#OneToOne(optional = false, cascade = CascadeType.NONE)
#JoinColumn(name = "mentor_id")
private Student mentorId;
to
#Column(name = "mentor_id")
private Long mentorId;
3.Created a method to retrieve the sequence manually and then assigned the value to both 'id' and 'mentorId'
#Override
public Student saveExtended(Student student) {
Object sequence =
em.createNativeQuery(
"SELECT NEXT VALUE FOR S_STUDENTS_SEQUENCE AS VALUE FROM SYSIBM.SYSDUMMY1")
.getSingleResult();
BigInteger sequenceLong = (BigInteger) sequence;
student = student.toBuilder().id(sequenceLong.longValue()).mentorId(sequenceLong.longValue()).build();
em.persist(student);
em.flush();
return student;
}
After upgrading from 5.4.7 to 5.4.10 it looks like hibernate cannot handle two sequences with the same name in different db schemas anymore.
I have this entity
#Entity
#Table(name = "VM_LAUF_RICHTUNG", schema = "INFOP_FAHRPLAN")
public class VmLaufRichtung {
public static final String VM_LAUF_RICHTUNG_TABLE = "INFOP_FAHRPLAN.VM_LAUF_RICHTUNG";
#Id
#Digits(integer = 15, fraction = 0)
#SequenceGenerator(name = "InfopFahrplan.seqVmLaufRichtung", schema = "INFOP_FAHRPLAN", sequenceName = "SEQ_VM_LAUF_RICHTUNG")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "InfopFahrplan.seqVmLaufRichtung")
#Column(name = ID)
private Long id;
}
In an other schema, there's a sequence with a the same name SEQ_VM_LAUF_RICHTUNG.
When my spring boot application starts I do get
Caused by: org.hibernate.MappingException: The increment size of the [SEQ_VM_LAUF_RICHTUNG] sequence is set to [50] in the entity mapping while the associated database sequence increment size is [1].
This happens because it's picking up the wrong sequence, which has an other imcrement size.
I tried to fix the problem by setting
spring.jpa.hibernate.use-new-id-generator-mappings=true
but that did not change anything.
With hibernate 5.4.7 everyhting is working fine.
Did I miss something or is this a hibernate bug after all?
It looks that this is indeed a hibernate bug: https://hibernate.atlassian.net/browse/HHH-13322
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;
}
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.
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