Skip to content

Commit be3621d

Browse files
committed
hand over responsibilities of SelectGenerator to @generated
at the end of all this work on SelectGenerator, a cruel twist of fate!
1 parent 2509953 commit be3621d

File tree

13 files changed

+389
-61
lines changed

13 files changed

+389
-61
lines changed

hibernate-core/src/main/java/org/hibernate/annotations/Generated.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
*/
77
package org.hibernate.annotations;
88

9-
import java.lang.annotation.ElementType;
109
import java.lang.annotation.Retention;
11-
import java.lang.annotation.RetentionPolicy;
1210
import java.lang.annotation.Target;
1311

1412
import org.hibernate.generator.EventType;
1513
import org.hibernate.generator.internal.GeneratedGeneration;
1614

15+
import static java.lang.annotation.ElementType.FIELD;
16+
import static java.lang.annotation.ElementType.METHOD;
17+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
1718
import static org.hibernate.generator.EventType.INSERT;
1819
import static org.hibernate.generator.EventType.UPDATE;
1920

@@ -56,8 +57,9 @@
5657
* @see GeneratedColumn
5758
*/
5859
@ValueGenerationType( generatedBy = GeneratedGeneration.class )
59-
@Target({ElementType.FIELD, ElementType.METHOD})
60-
@Retention(RetentionPolicy.RUNTIME)
60+
@IdGeneratorType( GeneratedGeneration.class )
61+
@Target( {FIELD, METHOD} )
62+
@Retention( RUNTIME )
6163
public @interface Generated {
6264
/**
6365
* Specifies the events that cause the value to be generated by the

hibernate-core/src/main/java/org/hibernate/annotations/GeneratedColumn.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
*
2828
* @see DialectOverride.GeneratedColumn
2929
*/
30+
@ValueGenerationType( generatedBy = GeneratedAlwaysGeneration.class )
3031
@Target( {FIELD, METHOD} )
3132
@Retention( RUNTIME )
32-
@ValueGenerationType(generatedBy = GeneratedAlwaysGeneration.class)
3333
public @interface GeneratedColumn {
3434
/**
3535
* The expression to include in the generated DDL.

hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3448,7 +3448,7 @@ public boolean supportsInsertReturning() {
34483448
return false;
34493449
}
34503450

3451-
public boolean supportedInsertReturningGeneratedKeys() {
3451+
public boolean supportsInsertReturningGeneratedKeys() {
34523452
return false;
34533453
}
34543454
/**

hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ public String currentTimestampWithTimeZone() {
324324
}
325325

326326
@Override
327-
public boolean supportedInsertReturningGeneratedKeys() {
327+
public boolean supportsInsertReturningGeneratedKeys() {
328328
return true;
329329
}
330330

hibernate-core/src/main/java/org/hibernate/generator/InDatabaseGenerator.java

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@
66
*/
77
package org.hibernate.generator;
88

9+
import org.hibernate.Incubating;
910
import org.hibernate.dialect.Dialect;
1011
import org.hibernate.id.PostInsertIdentityPersister;
11-
import org.hibernate.id.insert.BasicSelectingDelegate;
12+
import org.hibernate.id.insert.GetGeneratedKeysDelegate;
1213
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
14+
import org.hibernate.id.insert.InsertReturningDelegate;
15+
import org.hibernate.id.insert.UniqueKeySelectingDelegate;
16+
import org.hibernate.persister.entity.EntityPersister;
17+
18+
import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyName;
1319

1420
/**
1521
* A value generated by the database might be generated implicitly, by a trigger, or using
@@ -71,15 +77,60 @@ public interface InDatabaseGenerator extends Generator {
7177
String[] getReferencedColumnValues(Dialect dialect);
7278

7379
/**
74-
* The {@link InsertGeneratedIdentifierDelegate} used to retrieve the generates value if this
80+
* The {@link InsertGeneratedIdentifierDelegate} used to retrieve the generated value if this
7581
* object is an identifier generator.
7682
* <p>
7783
* This is ignored by {@link org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor},
78-
* which handles multiple generators at once. This method arguably breaks the separation of
79-
* concerns between the generator and the coordinating code.
84+
* which handles multiple generators at once. So if this object is not an identifier generator,
85+
* this method is never called.
86+
* <p>
87+
* Note that this method arguably breaks the separation of concerns between the generator and
88+
* coordinating code, by specifying how the generated value should be <em>retrieved</em>.
89+
* <p>
90+
* The problem solved here is: we want to obtain an insert-generated primary key. But, sadly,
91+
* without already knowing the primary key, there's no completely-generic way to locate the
92+
* just-inserted row to obtain it.
93+
* <p>
94+
* We need one of the following things:
95+
* <ul>
96+
* <li>a database which supports some form of {@link Dialect#supportsInsertReturning()
97+
* insert ... returning} syntax, or can do the same thing using the JDBC
98+
* {@link Dialect#supportsInsertReturningGeneratedKeys() getGeneratedKeys()} API, or
99+
* <li>a second unique key of the entity, that is, a property annotated
100+
* {@link org.hibernate.annotations.NaturalId @NaturalId}.
101+
* </ul>
102+
* Alternatively, if the generated id is an identity/"autoincrement" column, we can take
103+
* advantage of special platform-specific functionality to retrieve it. Taking advantage
104+
* of the specialness of identity columns is the job of one particular implementation:
105+
* {@link org.hibernate.id.IdentityGenerator}. And the need for customized behavior for
106+
* identity columns is the reason why this layer-breaking method exists.
80107
*/
108+
@Incubating
81109
default InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(PostInsertIdentityPersister persister) {
82-
return new BasicSelectingDelegate( persister, persister.getFactory().getJdbcServices().getDialect() );
110+
Dialect dialect = persister.getFactory().getJdbcServices().getDialect();
111+
if ( dialect.supportsInsertReturningGeneratedKeys() ) {
112+
return new GetGeneratedKeysDelegate( persister, dialect, false );
113+
}
114+
else if ( dialect.supportsInsertReturning() ) {
115+
return new InsertReturningDelegate( persister, dialect );
116+
}
117+
else {
118+
// let's just hope the entity has a @NaturalId!
119+
return new UniqueKeySelectingDelegate( persister, dialect, getUniqueKeyPropertyName( persister ) );
120+
}
121+
}
122+
123+
/**
124+
* The name of a property of the entity which may be used to locate the just-{@code insert}ed
125+
* row containing the generated value. Of course, the columns mapped by this property should
126+
* form a unique key of the entity.
127+
* <p>
128+
* The default implementation uses the {@link org.hibernate.annotations.NaturalId @NaturalId}
129+
* property, if there is one.
130+
*/
131+
@Incubating
132+
default String getUniqueKeyPropertyName(EntityPersister persister) {
133+
return getNaturalIdPropertyName( persister );
83134
}
84135

85136
default boolean generatedByDatabase() {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.generator.internal;
8+
9+
import org.hibernate.id.IdentifierGenerationException;
10+
import org.hibernate.persister.entity.EntityPersister;
11+
12+
public class NaturalIdHelper {
13+
public static String getNaturalIdPropertyName(EntityPersister persister) {
14+
int[] naturalIdPropertyIndices = persister.getNaturalIdentifierProperties();
15+
if ( naturalIdPropertyIndices == null ) {
16+
throw new IdentifierGenerationException(
17+
"no natural-id property defined; " +
18+
"need to specify [key] in generator parameters"
19+
);
20+
}
21+
if ( naturalIdPropertyIndices.length > 1 ) {
22+
throw new IdentifierGenerationException(
23+
"generator does not currently support composite natural-id properties;" +
24+
" need to specify [key] in generator parameters"
25+
);
26+
}
27+
if ( persister.getEntityMetamodel().isNaturalIdentifierInsertGenerated() ) {
28+
throw new IdentifierGenerationException(
29+
"natural-id also defined as insert-generated; " +
30+
"need to specify [key] in generator parameters"
31+
);
32+
}
33+
return persister.getPropertyNames()[naturalIdPropertyIndices[0]];
34+
}
35+
}

hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java

Lines changed: 22 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@
1111
import org.hibernate.dialect.Dialect;
1212
import org.hibernate.generator.InDatabaseGenerator;
1313
import org.hibernate.id.factory.spi.StandardGenerator;
14-
import org.hibernate.id.insert.GetGeneratedKeysDelegate;
15-
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
16-
import org.hibernate.id.insert.InsertReturningDelegate;
17-
import org.hibernate.id.insert.UniqueKeySelectingDelegate;
1814
import org.hibernate.persister.entity.EntityPersister;
1915
import org.hibernate.service.ServiceRegistry;
2016
import org.hibernate.type.Type;
2117

18+
import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyName;
19+
2220
/**
2321
* A generator that {@code select}s the just-{@code insert}ed row to determine the
2422
* column value assigned by the database. The correct row is located using a unique
@@ -45,6 +43,21 @@
4543
* ...
4644
* }
4745
* }</pre>
46+
* However, after a very long working life, this generator is now handing over its
47+
* work to {@link org.hibernate.generator.internal.GeneratedGeneration}, and the
48+
* above code may be written as:
49+
* <pre>{@code
50+
* @Entity @Table(name="TableWithPKAssignedByTrigger")
51+
* public class TriggeredEntity {
52+
* @Id @Generated(event = INSERT)
53+
* private Long id;
54+
*
55+
* @NaturalId
56+
* private String name;
57+
*
58+
* ...
59+
* }
60+
* }</pre>
4861
* For tables with identity/autoincrement columns, use {@link IdentityGenerator}.
4962
* <p>
5063
* The actual work involved in retrieving the primary key value is the job of
@@ -68,49 +81,11 @@ public void configure(Type type, Properties parameters, ServiceRegistry serviceR
6881
uniqueKeyPropertyName = parameters.getProperty( "key" );
6982
}
7083

71-
/**
72-
* The name of a property of the entity which may be used to locate the just-{@code insert}ed
73-
* row containing the generated value. Of course, the columns mapped by this property should
74-
* form a unique key of the entity.
75-
*/
76-
protected String getUniqueKeyPropertyName(EntityPersister persister) {
77-
if ( uniqueKeyPropertyName != null ) {
78-
return uniqueKeyPropertyName;
79-
}
80-
int[] naturalIdPropertyIndices = persister.getNaturalIdentifierProperties();
81-
if ( naturalIdPropertyIndices == null ) {
82-
throw new IdentifierGenerationException(
83-
"no natural-id property defined; need to specify [key] in " +
84-
"generator parameters"
85-
);
86-
}
87-
if ( naturalIdPropertyIndices.length > 1 ) {
88-
throw new IdentifierGenerationException(
89-
"select generator does not currently support composite " +
90-
"natural-id properties; need to specify [key] in generator parameters"
91-
);
92-
}
93-
if ( persister.getEntityMetamodel().isNaturalIdentifierInsertGenerated() ) {
94-
throw new IdentifierGenerationException(
95-
"natural-id also defined as insert-generated; need to specify [key] " +
96-
"in generator parameters"
97-
);
98-
}
99-
return persister.getPropertyNames()[naturalIdPropertyIndices[0]];
100-
}
101-
10284
@Override
103-
public InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(PostInsertIdentityPersister persister) {
104-
Dialect dialect = persister.getFactory().getJdbcServices().getDialect();
105-
if ( dialect.supportedInsertReturningGeneratedKeys() ) {
106-
return new GetGeneratedKeysDelegate( persister, dialect, false );
107-
}
108-
else if ( dialect.supportsInsertReturning() ) {
109-
return new InsertReturningDelegate( persister, dialect );
110-
}
111-
else {
112-
return new UniqueKeySelectingDelegate( persister, dialect, getUniqueKeyPropertyName( persister ) );
113-
}
85+
public String getUniqueKeyPropertyName(EntityPersister persister) {
86+
return uniqueKeyPropertyName != null
87+
? uniqueKeyPropertyName
88+
: getNaturalIdPropertyName( persister );
11489
}
11590

11691
@Override
@@ -120,6 +95,6 @@ public boolean referenceColumnsInSql(Dialect dialect) {
12095

12196
@Override
12297
public String[] getReferencedColumnValues(Dialect dialect) {
123-
return new String[0];
98+
return null;
12499
}
125100
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.orm.test.generatedkeys.generated;
8+
9+
import org.hibernate.dialect.DB2Dialect;
10+
import org.hibernate.dialect.H2Dialect;
11+
import org.hibernate.dialect.MySQLDialect;
12+
import org.hibernate.dialect.OracleDialect;
13+
import org.hibernate.dialect.PostgreSQLDialect;
14+
import org.hibernate.dialect.SQLServerDialect;
15+
import org.hibernate.testing.orm.junit.DomainModel;
16+
import org.hibernate.testing.orm.junit.JiraKey;
17+
import org.hibernate.testing.orm.junit.RequiresDialect;
18+
import org.hibernate.testing.orm.junit.SessionFactory;
19+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
20+
import org.hibernate.tool.hbm2ddl.SchemaExport;
21+
import org.hibernate.tool.schema.TargetType;
22+
import org.junit.jupiter.api.Test;
23+
24+
import java.io.File;
25+
import java.io.IOException;
26+
import java.nio.file.Files;
27+
import java.util.EnumSet;
28+
29+
import static org.junit.jupiter.api.Assertions.assertEquals;
30+
import static org.junit.jupiter.api.Assertions.assertFalse;
31+
32+
/**
33+
* @author Steve Ebersole
34+
* @author Marco Belladelli
35+
*/
36+
@DomainModel(
37+
annotatedClasses = MyEntity.class,
38+
xmlMappings = "org/hibernate/orm/test/generatedkeys/selectannotated/MyEntity.hbm.xml"
39+
)
40+
@SessionFactory
41+
@RequiresDialect(OracleDialect.class)
42+
@RequiresDialect(PostgreSQLDialect.class)
43+
@RequiresDialect(MySQLDialect.class)
44+
@RequiresDialect(H2Dialect.class)
45+
@RequiresDialect(DB2Dialect.class)
46+
@RequiresDialect(SQLServerDialect.class)
47+
public class GeneratedTest {
48+
49+
@Test
50+
public void test(SessionFactoryScope scope) {
51+
scope.inTransaction(
52+
session -> {
53+
MyEntity e1 = new MyEntity( "entity-1" );
54+
session.persist( e1 );
55+
// this insert should happen immediately!
56+
assertEquals( Long.valueOf( 1L ), e1.getId(), "id not generated through forced insertion" );
57+
58+
MyEntity e2 = new MyEntity( "entity-2" );
59+
session.persist( e2 );
60+
assertEquals( Long.valueOf( 2L ), e2.getId(), "id not generated through forced insertion" );
61+
62+
session.remove( e1 );
63+
session.remove( e2 );
64+
}
65+
);
66+
}
67+
68+
@Test
69+
@JiraKey("HHH-15900")
70+
public void testGeneratedKeyNotIdentityColumn(SessionFactoryScope scope) throws IOException {
71+
File output = File.createTempFile( "schema_export", ".sql" );
72+
output.deleteOnExit();
73+
74+
final SchemaExport schemaExport = new SchemaExport();
75+
schemaExport.setOutputFile( output.getAbsolutePath() );
76+
schemaExport.execute(
77+
EnumSet.of( TargetType.SCRIPT ),
78+
SchemaExport.Action.CREATE,
79+
scope.getMetadataImplementor()
80+
);
81+
82+
String fileContent = new String( Files.readAllBytes( output.toPath() ) );
83+
assertFalse( fileContent.toLowerCase().contains( "identity" ), "Column was generated as identity" );
84+
}
85+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.hibernate.orm.test.generatedkeys.generated;
2+
3+
import org.h2.tools.TriggerAdapter;
4+
5+
import java.sql.Connection;
6+
import java.sql.ResultSet;
7+
import java.sql.SQLException;
8+
9+
public class H2Trigger extends TriggerAdapter {
10+
@Override
11+
public void fire(Connection conn, ResultSet oldRow, ResultSet newRow) throws SQLException {
12+
ResultSet resultSet = conn.createStatement().executeQuery("select coalesce(max(id), 0) from my_entity");
13+
resultSet.next();
14+
newRow.updateInt( "id", resultSet.getInt(1) + 1 );
15+
}
16+
}

0 commit comments

Comments
 (0)