Skip to content

Commit 897a193

Browse files
committed
Fix test with generated column
1 parent 3fa01d7 commit 897a193

File tree

6 files changed

+134
-105
lines changed

6 files changed

+134
-105
lines changed

hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java

+58-15
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.hibernate.event.spi.EventSource;
2626
import org.hibernate.generator.BeforeExecutionGenerator;
2727
import org.hibernate.generator.Generator;
28+
import org.hibernate.id.Assigned;
2829
import org.hibernate.id.IdentifierGenerationException;
2930
import org.hibernate.jpa.event.spi.CallbackRegistry;
3031
import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
@@ -122,27 +123,45 @@ protected CompletionStage<Void> reactiveSaveWithGeneratedId(
122123
C context,
123124
EventSource source,
124125
boolean requiresImmediateIdAccess) {
125-
callbackRegistry.preCreate( entity );
126-
127-
processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes );
128126

129127
final EntityPersister persister = source.getEntityPersister( entityName, entity );
130128
final Generator generator = persister.getGenerator();
131-
if ( !generator.generatedOnExecution() ) {
129+
final boolean generatedOnExecution = generator.generatedOnExecution( entity, source );
130+
final Object generatedId;
131+
if ( generatedOnExecution ) {
132+
// the id gets generated by the database
133+
// and is not yet available
134+
generatedId = null;
135+
}
136+
else if ( generator instanceof Assigned ) {
137+
// get it from the entity later, since we need
138+
// the @PrePersist callback to happen first
139+
generatedId = null;
140+
}
141+
else {
142+
// go ahead and generate id, and then set it to
143+
// the entity instance, so it will be available
144+
// to the entity in the @PrePersist callback
132145
if ( generator instanceof ReactiveIdentifierGenerator ) {
133146
return ( (ReactiveIdentifierGenerator<?>) generator )
134147
.generate( ( ReactiveConnectionSupplier ) source, entity )
135148
.thenApply( id -> castToIdentifierType( id, persister ) )
136-
.thenCompose( generatedId -> performSaveWithId( entity, context, source, persister, generator, generatedId ) );
149+
.thenCompose( gid -> performSaveWithId(
150+
entity,
151+
context,
152+
source,
153+
persister,
154+
generator,
155+
gid,
156+
requiresImmediateIdAccess,
157+
false
158+
) );
137159
}
138160

139-
final Object generatedId = ( (BeforeExecutionGenerator) generator ).generate( source, entity, null, INSERT );
140-
final Object id = castToIdentifierType( generatedId, persister );
141-
return performSaveWithId( entity, context, source, persister, generator, id );
142-
}
143-
else {
144-
return reactivePerformSave( entity, null, persister, true, context, source, requiresImmediateIdAccess );
161+
generatedId = ( (BeforeExecutionGenerator) generator ).generate( source, entity, null, INSERT );
145162
}
163+
final Object id = castToIdentifierType( generatedId, persister );
164+
return reactivePerformSave( entity, id, persister, generatedOnExecution, context, source, requiresImmediateIdAccess );
146165
}
147166

148167
private CompletionStage<Void> performSaveWithId(
@@ -151,7 +170,9 @@ private CompletionStage<Void> performSaveWithId(
151170
EventSource source,
152171
EntityPersister persister,
153172
Generator generator,
154-
Object generatedId) {
173+
Object generatedId,
174+
boolean requiresImmediateIdAccess,
175+
boolean generatedOnExecution) {
155176
if ( generatedId == null ) {
156177
throw new IdentifierGenerationException( "null id generated for: " + entity.getClass() );
157178
}
@@ -166,7 +187,11 @@ private CompletionStage<Void> performSaveWithId(
166187
generator.getClass().getName()
167188
);
168189
}
169-
return reactivePerformSave( entity, generatedId, persister, false, context, source, true );
190+
final boolean delayIdentityInserts =
191+
!source.isTransactionInProgress()
192+
&& !requiresImmediateIdAccess
193+
&& generatedOnExecution;
194+
return reactivePerformSave( entity, generatedId, persister, false, context, source, delayIdentityInserts );
170195
}
171196

172197
/**
@@ -196,6 +221,22 @@ protected CompletionStage<Void> reactivePerformSave(
196221
EventSource source,
197222
boolean requiresImmediateIdAccess) {
198223

224+
// call this after generation of an id,
225+
// but before we retrieve an assigned id
226+
callbackRegistry.preCreate( entity );
227+
228+
processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes );
229+
230+
if ( persister.getGenerator() instanceof Assigned ) {
231+
id = persister.getIdentifier( entity, source );
232+
if ( id == null ) {
233+
throw new IdentifierGenerationException(
234+
"Identifier of entity '" + persister.getEntityName()
235+
+ "' must be manually assigned before calling 'persist()'"
236+
);
237+
}
238+
}
239+
199240
if ( LOG.isTraceEnabled() ) {
200241
LOG.tracev( "Saving {0}", infoString( persister, id, source.getFactory() ) );
201242
}
@@ -268,8 +309,10 @@ protected CompletionStage<Void> reactivePerformSaveOrReplicate(
268309

269310
final Object id = key == null ? null : key.getIdentifier();
270311

271-
final boolean inTransaction = source.isTransactionInProgress();
272-
final boolean shouldDelayIdentityInserts = !inTransaction && !requiresImmediateIdAccess;
312+
Generator generator = persister.getGenerator();
313+
final boolean shouldDelayIdentityInserts = !source.isTransactionInProgress()
314+
&& !requiresImmediateIdAccess
315+
&& generator.generatedOnExecution( entity, source );
273316
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
274317

275318
// Put a placeholder in entries, so we don't recurse back and try to save() the

hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import org.hibernate.sql.model.MutationType;
4646
import org.hibernate.sql.results.internal.RowTransformerArrayImpl;
4747
import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl;
48-
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping;
4948
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer;
5049
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
5150
import org.hibernate.type.descriptor.WrapperOptions;
@@ -159,6 +158,7 @@ private static CompletionStage<Object[]> readGeneratedValues(
159158
SharedSessionContractImplementor session) {
160159
final ExecutionContext executionContext = new BaseExecutionContext( session );
161160

161+
162162
final ReactiveDirectResultSetAccess directResultSetAccess;
163163
try {
164164
directResultSetAccess = new ReactiveDirectResultSetAccess( session, (PreparedStatement) resultSet.getStatement(), resultSet );
@@ -167,13 +167,16 @@ private static CompletionStage<Object[]> readGeneratedValues(
167167
throw new HibernateException( "Could not retrieve statement from generated values result set", e );
168168
}
169169

170-
JdbcValuesMapping resolve = mappingProducer.resolve( directResultSetAccess, session.getLoadQueryInfluencers(), session.getSessionFactory() );
171170
final ReactiveValuesResultSet jdbcValues = new ReactiveValuesResultSet(
172171
directResultSetAccess,
173172
null,
174173
null,
175174
QueryOptions.NONE,
176-
resolve,
175+
mappingProducer.resolve(
176+
directResultSetAccess,
177+
session.getLoadQueryInfluencers(),
178+
session.getSessionFactory()
179+
),
177180
null,
178181
executionContext
179182
);

hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractSelectingDelegate.java

-67
This file was deleted.

hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java

+66-4
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,102 @@
77

88
import java.lang.invoke.MethodHandles;
99
import java.sql.PreparedStatement;
10+
import java.util.ArrayList;
11+
import java.util.List;
1012
import java.util.concurrent.CompletionStage;
1113

1214
import org.hibernate.dialect.Dialect;
15+
import org.hibernate.dialect.MySQLDialect;
1316
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
1417
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
18+
import org.hibernate.engine.spi.SessionFactoryImplementor;
1519
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1620
import org.hibernate.generator.EventType;
21+
import org.hibernate.generator.values.GeneratedValueBasicResultBuilder;
1722
import org.hibernate.generator.values.GeneratedValues;
23+
import org.hibernate.generator.values.internal.TableUpdateReturningBuilder;
1824
import org.hibernate.id.PostInsertIdentityPersister;
25+
import org.hibernate.id.insert.AbstractReturningDelegate;
1926
import org.hibernate.id.insert.InsertReturningDelegate;
27+
import org.hibernate.id.insert.TableInsertReturningBuilder;
28+
import org.hibernate.jdbc.Expectation;
29+
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
2030
import org.hibernate.persister.entity.EntityPersister;
2131
import org.hibernate.reactive.logging.impl.Log;
2232
import org.hibernate.reactive.logging.impl.LoggerFactory;
2333
import org.hibernate.reactive.session.ReactiveConnectionSupplier;
34+
import org.hibernate.sql.ast.tree.expression.ColumnReference;
35+
import org.hibernate.sql.model.ast.MutatingTableReference;
36+
import org.hibernate.sql.model.ast.builder.TableMutationBuilder;
2437

38+
import static java.sql.Statement.NO_GENERATED_KEYS;
39+
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getActualGeneratedModelPart;
2540
import static org.hibernate.reactive.generator.values.internal.ReactiveGeneratedValuesHelper.getGeneratedValues;
2641

2742
/**
2843
* @see InsertReturningDelegate
2944
*/
30-
public class ReactiveInsertReturningDelegate extends InsertReturningDelegate implements ReactiveAbstractReturningDelegate {
45+
public class ReactiveInsertReturningDelegate extends AbstractReturningDelegate implements ReactiveAbstractReturningDelegate {
3146

3247
private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() );
3348

3449
private final PostInsertIdentityPersister persister;
50+
private final MutatingTableReference tableReference;
51+
private final List<ColumnReference> generatedColumns;
3552

3653
public ReactiveInsertReturningDelegate(EntityPersister persister, EventType timing) {
37-
super( persister, timing );
38-
this.persister = (PostInsertIdentityPersister) persister;
54+
this( (PostInsertIdentityPersister) persister, timing, false );
55+
3956
}
4057

4158
public ReactiveInsertReturningDelegate(PostInsertIdentityPersister persister, Dialect dialect) {
42-
super( persister, dialect );
59+
// With JDBC it's possible to enabled GetGeneratedKeys for identity generation.
60+
// Vert.x doesn't have this option, so we always use the same strategy for all database.
61+
// But MySQL requires setting supportsArbitraryValues to false or it's not going to work.
62+
this( persister, EventType.INSERT, !(dialect instanceof MySQLDialect) );
63+
}
64+
65+
private ReactiveInsertReturningDelegate(PostInsertIdentityPersister persister, EventType timing, boolean supportsArbitraryValues) {
66+
super(
67+
persister,
68+
timing,
69+
supportsArbitraryValues,
70+
persister.getFactory().getJdbcServices().getDialect().supportsInsertReturningRowId()
71+
);
4372
this.persister = persister;
73+
this.tableReference = new MutatingTableReference( persister.getIdentifierTableMapping() );
74+
final List<GeneratedValueBasicResultBuilder> resultBuilders = jdbcValuesMappingProducer.getResultBuilders();
75+
this.generatedColumns = new ArrayList<>( resultBuilders.size() );
76+
for ( GeneratedValueBasicResultBuilder resultBuilder : resultBuilders ) {
77+
generatedColumns.add( new ColumnReference(
78+
tableReference,
79+
getActualGeneratedModelPart( resultBuilder.getModelPart() )
80+
) );
81+
}
82+
}
83+
@Override
84+
public TableMutationBuilder<?> createTableMutationBuilder(
85+
Expectation expectation,
86+
SessionFactoryImplementor sessionFactory) {
87+
if ( getTiming() == EventType.INSERT ) {
88+
return new TableInsertReturningBuilder( persister, tableReference, generatedColumns, sessionFactory );
89+
}
90+
else {
91+
return new TableUpdateReturningBuilder<>( persister, tableReference, generatedColumns, sessionFactory );
92+
}
93+
}
94+
95+
@Override
96+
public String prepareIdentifierGeneratingInsert(String insertSQL) {
97+
return dialect().getIdentityColumnSupport().appendIdentitySelectToInsert(
98+
( (BasicEntityIdentifierMapping) persister.getRootEntityDescriptor().getIdentifierMapping() ).getSelectionExpression(),
99+
insertSQL
100+
);
101+
}
102+
103+
@Override
104+
public PreparedStatement prepareStatement(String sql, SharedSessionContractImplementor session) {
105+
return session.getJdbcCoordinator().getMutationStatementPreparer().prepareStatement( sql, NO_GENERATED_KEYS );
44106
}
45107

46108
@Override

hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveValuesResultSetImpl.java

-9
This file was deleted.

hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertySingleTableTest.java

+4-7
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131

3232
import static java.util.concurrent.TimeUnit.MINUTES;
3333
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2;
34-
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MARIA;
35-
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MYSQL;
3634
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.ORACLE;
3735
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.SQLSERVER;
3836
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -54,12 +52,11 @@ protected Collection<Class<?>> annotatedEntities() {
5452
@Override
5553
protected Configuration constructConfiguration() {
5654
Configuration configuration = super.constructConfiguration();
57-
configuration.setProperty(AvailableSettings.HBM2DDL_CREATE_SOURCE, "script-then-metadata");
58-
configuration.setProperty(AvailableSettings.HBM2DDL_CREATE_SCRIPT_SOURCE, "/mysql-pipe.sql");
55+
configuration.setProperty( AvailableSettings.HBM2DDL_CREATE_SOURCE, "script-then-metadata" );
56+
configuration.setProperty( AvailableSettings.HBM2DDL_CREATE_SCRIPT_SOURCE, "/mysql-pipe.sql" );
5957
return configuration;
6058
}
6159

62-
@DisabledFor({MYSQL, MARIA})
6360
@Test
6461
public void testWithIdentity(VertxTestContext context) {
6562
final GeneratedWithIdentity davide = new GeneratedWithIdentity( "Davide", "D'Alto" );
@@ -69,7 +66,7 @@ public void testWithIdentity(VertxTestContext context) {
6966
context,
7067
getMutinySessionFactory()
7168
// Generated during insert
72-
.withSession( session -> session.persist( davide ).call( session::flush )
69+
.withTransaction( session -> session.persist( davide ).call( session::flush )
7370
.invoke( v -> {
7471
assertNotNull( davide.id );
7572
assertEquals( "Davide", davide.firstname );
@@ -83,7 +80,7 @@ public void testWithIdentity(VertxTestContext context) {
8380
} ) )
8481
// Generated during update
8582
.chain( () -> getMutinySessionFactory()
86-
.withSession( session -> session.find( GeneratedWithIdentity.class, davide.id )
83+
.withTransaction( session -> session.find( GeneratedWithIdentity.class, davide.id )
8784
.chain( result -> {
8885
CurrentUser.INSTANCE.logIn( "dd-update" );
8986
result.lastname = "O'Tall";

0 commit comments

Comments
 (0)