Skip to content

Commit 6d21f7e

Browse files
CHAPEL Guillaumebeikov
CHAPEL Guillaume
authored andcommitted
HHH-15118 Fix duplicate ids with PooledOptimizer when sequence value is initialValue
1 parent 3b9fbdc commit 6d21f7e

File tree

6 files changed

+148
-90
lines changed

6 files changed

+148
-90
lines changed

documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import java.util.Arrays;
1010
import java.util.Date;
1111
import java.util.HashSet;
12-
import java.util.List;
1312
import java.util.Map;
1413
import java.util.Set;
1514
import javax.persistence.Column;

hibernate-core/src/main/java/org/hibernate/id/enhanced/PooledOptimizer.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,21 @@ public synchronized Serializable generate(AccessCallback callback) {
7070
final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() );
7171

7272
if ( generationState.hiValue == null ) {
73-
generationState.value = callback.getNextValue();
73+
generationState.hiValue = callback.getNextValue();
7474
// unfortunately not really safe to normalize this
7575
// to 1 as an initial value like we do for the others
7676
// because we would not be able to control this if
7777
// we are using a sequence...
78-
if ( generationState.value.lt( 1 ) ) {
79-
log.pooledOptimizerReportedInitialValue( generationState.value );
78+
if ( generationState.hiValue.lt( 1 ) ) {
79+
log.pooledOptimizerReportedInitialValue( generationState.hiValue );
8080
}
8181
// the call to obtain next-value just gave us the initialValue
8282
if ( ( initialValue == -1
83-
&& generationState.value.lt( incrementSize ) )
84-
|| generationState.value.eq( initialValue ) ) {
85-
generationState.hiValue = callback.getNextValue();
83+
&& generationState.hiValue.lt( incrementSize ) )
84+
|| generationState.hiValue.eq( initialValue ) ) {
85+
generationState.value = generationState.hiValue.copy();
8686
}
8787
else {
88-
generationState.hiValue = generationState.value;
8988
generationState.value = generationState.hiValue.copy().subtract( incrementSize - 1 );
9089
}
9190
}

hibernate-core/src/test/java/org/hibernate/id/enhanced/OptimizerUnitTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,15 @@ public void testRecoveredPooledOptimizerUsage() {
226226

227227
Long next = ( Long ) optimizer.generate( sequence );
228228
assertEquals( 1, next.intValue() );
229+
assertEquals( 1, sequence.getTimesCalled() );
230+
assertEquals( 1, sequence.getCurrentValue() );
231+
232+
next = ( Long ) optimizer.generate( sequence );
233+
assertEquals( 2, next.intValue() );
229234
assertEquals( 2, sequence.getTimesCalled() );
230235
assertEquals( 4, sequence.getCurrentValue() );
231236

232-
// app ends, and starts back up (we should "lose" only 2 and 3 as id values)
237+
// app ends, and starts back up (we should "lose" only 3 and 4 as id values)
233238
final Optimizer optimizer2 = buildPooledOptimizer( 1, 3 );
234239
next = ( Long ) optimizer2.generate( sequence );
235240
assertEquals( 5, next.intValue() );

hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/forcedtable/PooledForcedTableSequenceTest.java

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.hibernate.test.idgen.enhanced.forcedtable;
88

99
import org.junit.Test;
10+
import org.junit.jupiter.api.Assertions;
1011

1112
import org.hibernate.Session;
1213
import org.hibernate.id.IdentifierGeneratorHelper.BasicHolder;
@@ -15,6 +16,7 @@
1516
import org.hibernate.id.enhanced.TableStructure;
1617
import org.hibernate.persister.entity.EntityPersister;
1718
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
19+
import org.hibernate.testing.transaction.TransactionUtil;
1820

1921
import static org.junit.Assert.assertEquals;
2022
import static org.junit.Assert.assertTrue;
@@ -23,6 +25,9 @@
2325
* @author Steve Ebersole
2426
*/
2527
public class PooledForcedTableSequenceTest extends BaseCoreFunctionalTestCase {
28+
29+
private static final long INITIAL_VALUE = 1;
30+
2631
public String[] getMappings() {
2732
return new String[] { "idgen/enhanced/forcedtable/Pooled.hbm.xml" };
2833
}
@@ -46,37 +51,47 @@ public void testNormalBoundary() {
4651
PooledOptimizer optimizer = (PooledOptimizer) generator.getOptimizer();
4752

4853
int increment = optimizer.getIncrementSize();
49-
Entity[] entities = new Entity[ increment + 2 ];
50-
Session s = openSession();
51-
s.beginTransaction();
52-
for ( int i = 0; i <= increment; i++ ) {
53-
entities[i] = new Entity( "" + ( i + 1 ) );
54-
s.save( entities[i] );
55-
long expectedId = i + 1;
56-
assertEquals( expectedId, entities[i].getId().longValue() );
57-
// NOTE : initialization calls table twice
58-
assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() );
59-
assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
60-
assertEquals( i + 1, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
61-
assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
62-
}
63-
// now force a "clock over"
64-
entities[increment + 1] = new Entity( "" + increment );
65-
s.save( entities[increment + 1] );
66-
long expectedId = optimizer.getIncrementSize() + 2;
67-
assertEquals( expectedId, entities[ increment + 1 ].getId().longValue() );
68-
// initialization (2) + clock over
69-
assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() );
70-
assertEquals( ( increment * 2 ) + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
71-
assertEquals( increment + 2, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
72-
s.getTransaction().commit();
7354

74-
s.beginTransaction();
75-
for ( int i = 0; i < entities.length; i++ ) {
76-
assertEquals( i + 1, entities[i].getId().intValue() );
77-
s.delete( entities[i] );
78-
}
79-
s.getTransaction().commit();
80-
s.close();
55+
TransactionUtil.doInHibernate(
56+
this::sessionFactory,
57+
s -> {
58+
// The value that we get from the callback is the high value (PooledOptimizer by default)
59+
// When first increment is initialValue, we can only generate one id from it -> id 1
60+
Entity entity = new Entity( "" + INITIAL_VALUE );
61+
s.save( entity );
62+
63+
long expectedId = INITIAL_VALUE;
64+
assertEquals( expectedId, entity.getId().longValue() );
65+
assertEquals( 1, generator.getDatabaseStructure().getTimesAccessed() );
66+
assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
67+
assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
68+
assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
69+
70+
// now start a full range of values, callback give us hiValue 11
71+
// id : 2,3,4...,11
72+
for ( int i = 1; i <= increment; i++ ) {
73+
entity = new Entity( "" + ( i + INITIAL_VALUE ) );
74+
s.save( entity );
75+
76+
expectedId = i + INITIAL_VALUE;
77+
assertEquals( expectedId, entity.getId().longValue() );
78+
assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() );
79+
assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
80+
assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
81+
}
82+
83+
// now force a "clock over"
84+
expectedId++;
85+
entity = new Entity( "" + expectedId );
86+
s.save( entity );
87+
88+
assertEquals( expectedId, entity.getId().longValue() );
89+
assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() );
90+
assertEquals( increment * 2L + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
91+
assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
92+
93+
s.createQuery( "delete Entity" ).executeUpdate();
94+
}
95+
);
8196
}
8297
}

hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/sequence/PooledSequenceTest.java

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.hibernate.id.enhanced.SequenceStyleGenerator;
1414
import org.hibernate.persister.entity.EntityPersister;
1515
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
16+
import org.hibernate.testing.transaction.TransactionUtil;
1617

1718
import static org.hibernate.id.IdentifierGeneratorHelper.BasicHolder;
1819
import static org.hibernate.testing.junit4.ExtraAssertions.assertClassAssignability;
@@ -22,6 +23,9 @@
2223
* @author Steve Ebersole
2324
*/
2425
public class PooledSequenceTest extends BaseCoreFunctionalTestCase {
26+
27+
private static final long INITIAL_VALUE = 1;
28+
2529
@Override
2630
public String[] getMappings() {
2731
return new String[] { "idgen/enhanced/sequence/Pooled.hbm.xml" };
@@ -36,31 +40,47 @@ public void testNormalBoundary() {
3640
PooledOptimizer optimizer = (PooledOptimizer) generator.getOptimizer();
3741

3842
int increment = optimizer.getIncrementSize();
39-
Entity[] entities = new Entity[ increment + 2 ];
40-
Session s = openSession();
41-
s.beginTransaction();
42-
for ( int i = 0; i <= increment; i++ ) {
43-
entities[i] = new Entity( "" + ( i + 1 ) );
44-
s.save( entities[i] );
45-
assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() ); // initialization calls seq twice
46-
assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization calls seq twice
47-
assertEquals( i + 1, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
48-
assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
49-
}
50-
// now force a "clock over"
51-
entities[ increment + 1 ] = new Entity( "" + increment );
52-
s.save( entities[ increment + 1 ] );
53-
assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() ); // initialization (2) + clock over
54-
assertEquals( ( increment * 2 ) + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization (2) + clock over
55-
assertEquals( increment + 2, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
56-
s.getTransaction().commit();
5743

58-
s.beginTransaction();
59-
for ( int i = 0; i < entities.length; i++ ) {
60-
assertEquals( i + 1, entities[i].getId().intValue() );
61-
s.delete( entities[i] );
62-
}
63-
s.getTransaction().commit();
64-
s.close();
44+
TransactionUtil.doInHibernate(
45+
this::sessionFactory,
46+
s -> {
47+
// The value that we get from the callback is the high value (PooledOptimizer by default)
48+
// When first increment is initialValue, we can only generate one id from it -> id 1
49+
Entity entity = new Entity( "" + INITIAL_VALUE );
50+
s.save( entity );
51+
52+
long expectedId = INITIAL_VALUE;
53+
assertEquals( expectedId, entity.getId().longValue() );
54+
assertEquals( 1, generator.getDatabaseStructure().getTimesAccessed() );
55+
assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
56+
assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
57+
assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
58+
59+
// now start a full range of values, callback give us hiValue 11
60+
// id : 2,3,4...,11
61+
for ( int i = 1; i <= increment; i++ ) {
62+
entity = new Entity( "" + ( i + INITIAL_VALUE ) );
63+
s.save( entity );
64+
65+
expectedId = i + INITIAL_VALUE;
66+
assertEquals( expectedId, entity.getId().longValue() );
67+
assertEquals( 2, generator.getDatabaseStructure().getTimesAccessed() );
68+
assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
69+
assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
70+
}
71+
72+
// now force a "clock over"
73+
expectedId++;
74+
entity = new Entity( "" + expectedId );
75+
s.save( entity );
76+
77+
assertEquals( expectedId, entity.getId().longValue() );
78+
assertEquals( 3, generator.getDatabaseStructure().getTimesAccessed() );
79+
assertEquals( increment * 2L + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
80+
assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
81+
82+
s.createQuery( "delete Entity" ).executeUpdate();
83+
}
84+
);
6585
}
6686
}

hibernate-core/src/test/java/org/hibernate/test/idgen/enhanced/table/PooledTableTest.java

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.hibernate.id.enhanced.TableGenerator;
1414
import org.hibernate.persister.entity.EntityPersister;
1515
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
16+
import org.hibernate.testing.transaction.TransactionUtil;
1617

1718
import static org.hibernate.id.IdentifierGeneratorHelper.BasicHolder;
1819
import static org.hibernate.testing.junit4.ExtraAssertions.assertClassAssignability;
@@ -22,6 +23,9 @@
2223
* @author Steve Ebersole
2324
*/
2425
public class PooledTableTest extends BaseCoreFunctionalTestCase {
26+
27+
private static final long INITIAL_VALUE = 1;
28+
2529
@Override
2630
public String[] getMappings() {
2731
return new String[] { "idgen/enhanced/table/Pooled.hbm.xml" };
@@ -36,31 +40,47 @@ public void testNormalBoundary() {
3640
PooledOptimizer optimizer = (PooledOptimizer) generator.getOptimizer();
3741

3842
int increment = optimizer.getIncrementSize();
39-
Entity[] entities = new Entity[ increment + 2 ];
40-
Session s = openSession();
41-
s.beginTransaction();
42-
for ( int i = 0; i <= increment; i++ ) {
43-
entities[i] = new Entity( "" + ( i + 1 ) );
44-
s.save( entities[i] );
45-
assertEquals( 2, generator.getTableAccessCount() ); // initialization calls seq twice
46-
assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization calls seq twice
47-
assertEquals( i + 1, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
48-
assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
49-
}
50-
// now force a "clock over"
51-
entities[ increment + 1 ] = new Entity( "" + increment );
52-
s.save( entities[ increment + 1 ] );
53-
assertEquals( 3, generator.getTableAccessCount() ); // initialization (2) + clock over
54-
assertEquals( ( increment * 2 ) + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() ); // initialization (2) + clock over
55-
assertEquals( increment + 2, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
56-
s.getTransaction().commit();
5743

58-
s.beginTransaction();
59-
for ( int i = 0; i < entities.length; i++ ) {
60-
assertEquals( i + 1, entities[i].getId().intValue() );
61-
s.delete( entities[i] );
62-
}
63-
s.getTransaction().commit();
64-
s.close();
44+
TransactionUtil.doInHibernate(
45+
this::sessionFactory,
46+
s -> {
47+
// The value that we get from the callback is the high value (PooledOptimizer by default)
48+
// When first increment is initialValue, we can only generate one id from it -> id 1
49+
Entity entity = new Entity( "" + INITIAL_VALUE );
50+
s.save( entity );
51+
52+
long expectedId = INITIAL_VALUE;
53+
assertEquals( expectedId, entity.getId().longValue() );
54+
assertEquals( 1, generator.getTableAccessCount() );
55+
assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
56+
assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
57+
assertEquals( INITIAL_VALUE, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
58+
59+
// now start a full range of values, callback give us hiValue 11
60+
// id : 2,3,4...,11
61+
for ( int i = 1; i <= increment; i++ ) {
62+
entity = new Entity( "" + ( i + INITIAL_VALUE ) );
63+
s.save( entity );
64+
65+
expectedId = i + INITIAL_VALUE;
66+
assertEquals( expectedId, entity.getId().longValue() );
67+
assertEquals( 2, generator.getTableAccessCount() );
68+
assertEquals( increment + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
69+
assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
70+
}
71+
72+
// now force a "clock over"
73+
expectedId++;
74+
entity = new Entity( "" + expectedId );
75+
s.save( entity );
76+
77+
assertEquals( expectedId, entity.getId().longValue() );
78+
assertEquals( 3, generator.getTableAccessCount() );
79+
assertEquals( increment * 2L + 1, ( (BasicHolder) optimizer.getLastSourceValue() ).getActualLongValue() );
80+
assertEquals( expectedId, ( (BasicHolder) optimizer.getLastValue() ).getActualLongValue() );
81+
82+
s.createQuery( "delete Entity" ).executeUpdate();
83+
}
84+
);
6585
}
6686
}

0 commit comments

Comments
 (0)