HHH-15958 much better support for @RowId annotation
- the rowid pseudo-column and type are now determined automatically from Dialect - works (after all these years) in Postgres (and also on h2) - introduce RowIdJdbcType (not strictly necessary, but a nicety)
This commit is contained in:
parent
6da38d0b05
commit
689cca1963
|
@ -15,6 +15,9 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
/**
|
||||
* Specifies that an Oracle-style {@code rowid} should be used in SQL
|
||||
* {@code update} statements for an entity, instead of the primary key.
|
||||
* <p>
|
||||
* If the {@linkplain org.hibernate.dialect.Dialect SQL dialect} does
|
||||
* not support some sort of {@code rowid}, this annotation is ignored.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -25,6 +28,10 @@ public @interface RowId {
|
|||
* Specifies the {@code rowid} identifier.
|
||||
* <p>
|
||||
* For example, on Oracle, this should be just {@code "rowid"}.
|
||||
*
|
||||
* @deprecated the {@code rowid} identifier is now inferred
|
||||
* automatically from the {@link org.hibernate.dialect.Dialect}
|
||||
*/
|
||||
String value();
|
||||
@Deprecated(since = "6.2")
|
||||
String value() default "";
|
||||
}
|
||||
|
|
|
@ -215,6 +215,7 @@ import static org.hibernate.type.SqlTypes.NCLOB;
|
|||
import static org.hibernate.type.SqlTypes.NUMERIC;
|
||||
import static org.hibernate.type.SqlTypes.NVARCHAR;
|
||||
import static org.hibernate.type.SqlTypes.REAL;
|
||||
import static org.hibernate.type.SqlTypes.ROWID;
|
||||
import static org.hibernate.type.SqlTypes.SMALLINT;
|
||||
import static org.hibernate.type.SqlTypes.TIME;
|
||||
import static org.hibernate.type.SqlTypes.TIMESTAMP;
|
||||
|
@ -4622,4 +4623,23 @@ public abstract class Dialect implements ConversionContext {
|
|||
ServiceRegistryImplementor registry) {
|
||||
return new HibernateSchemaManagementTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of a {@code rowid}-like pseudo-column which
|
||||
* acts as a high-performance row locator, or null if
|
||||
* this dialect has no such pseudo-column.
|
||||
*/
|
||||
public String rowId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The JDBC type code of the {@code rowid}-like pseudo-column
|
||||
* which acts as a high-performance row locator.
|
||||
*
|
||||
* @return {@link Types#ROWID} by default
|
||||
*/
|
||||
public int rowIdSqlType() {
|
||||
return ROWID;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ import jakarta.persistence.TemporalType;
|
|||
|
||||
import static org.hibernate.query.sqm.TemporalUnit.SECOND;
|
||||
import static org.hibernate.type.SqlTypes.ARRAY;
|
||||
import static org.hibernate.type.SqlTypes.BIGINT;
|
||||
import static org.hibernate.type.SqlTypes.BINARY;
|
||||
import static org.hibernate.type.SqlTypes.CHAR;
|
||||
import static org.hibernate.type.SqlTypes.DECIMAL;
|
||||
|
@ -856,4 +857,14 @@ public class H2Dialect extends Dialect {
|
|||
public UniqueDelegate getUniqueDelegate() {
|
||||
return uniqueDelegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rowId() {
|
||||
return "_rowid_";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int rowIdSqlType() {
|
||||
return BIGINT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1406,4 +1406,9 @@ public class OracleDialect extends Dialect {
|
|||
public String getCreateUserDefinedTypeKindString() {
|
||||
return "object";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rowId() {
|
||||
return "rowid";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1349,7 +1349,17 @@ public class PostgreSQLDialect extends Dialect {
|
|||
// disabled foreign key constraints still prevent 'truncate table'
|
||||
// (these would help if we used 'delete' instead of 'truncate')
|
||||
|
||||
// @Override
|
||||
@Override
|
||||
public String rowId() {
|
||||
return "ctid";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int rowIdSqlType() {
|
||||
return OTHER;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public String getDisableConstraintsStatement() {
|
||||
// return "set constraints all deferred";
|
||||
// }
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
*/
|
||||
package org.hibernate.metamodel.mapping.internal;
|
||||
|
||||
import java.sql.Types;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.mapping.IndexedConsumer;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
|
@ -41,9 +41,9 @@ public class EntityRowIdMappingImpl implements EntityRowIdMapping {
|
|||
this.rowIdName = rowIdName;
|
||||
this.tableExpression = tableExpression;
|
||||
this.declaringType = declaringType;
|
||||
this.rowIdType = declaringType.getEntityPersister().getFactory().getTypeConfiguration()
|
||||
.getBasicTypeRegistry()
|
||||
.resolve( Object.class, Types.ROWID );
|
||||
final SessionFactoryImplementor factory = declaringType.getEntityPersister().getFactory();
|
||||
this.rowIdType = factory.getTypeConfiguration().getBasicTypeRegistry()
|
||||
.resolve( Object.class, factory.getJdbcServices().getDialect().rowIdSqlType() );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -544,7 +544,13 @@ public abstract class AbstractEntityPersister
|
|||
rootTableKeyColumnReaderTemplates = new String[identifierColumnSpan];
|
||||
identifierAliases = new String[identifierColumnSpan];
|
||||
|
||||
rowIdName = persistentClass.getRootTable().getRowId();
|
||||
final String rowId = persistentClass.getRootTable().getRowId();
|
||||
if ( rowId == null ) {
|
||||
rowIdName = null;
|
||||
}
|
||||
else {
|
||||
rowIdName = rowId.isEmpty() ? dialect.rowId() : rowId;
|
||||
}
|
||||
|
||||
if ( persistentClass.getLoaderName() != null ) {
|
||||
// We must resolve the named query on-demand through the boot model because it isn't initialized yet
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.type.descriptor.jdbc;
|
||||
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.descriptor.ValueBinder;
|
||||
import org.hibernate.type.descriptor.ValueExtractor;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.RowId;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Descriptor for {@link Types#ROWID ROWID} handling.
|
||||
*
|
||||
* @author Gavin King
|
||||
*/
|
||||
public class RowIdJdbcType implements JdbcType {
|
||||
public static final RowIdJdbcType INSTANCE = new RowIdJdbcType();
|
||||
|
||||
@Override
|
||||
public int getJdbcTypeCode() {
|
||||
return SqlTypes.ROWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RowIdJdbcType";
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
|
||||
return new BasicBinder<>( javaType, this ) {
|
||||
@Override
|
||||
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
|
||||
throws SQLException {
|
||||
st.setRowId( index, getJavaType().unwrap( value, RowId.class, options ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
|
||||
throws SQLException {
|
||||
st.setRowId( name, getJavaType().unwrap( value, RowId.class, options ) );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) {
|
||||
return new BasicExtractor<>( javaType, this ) {
|
||||
@Override
|
||||
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
|
||||
return getJavaType().wrap( rs.getRowId( paramIndex ), options );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
|
||||
return getJavaType().wrap( statement.getRowId( index ), options );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
|
||||
return getJavaType().wrap( statement.getObject( name ), options );
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@ import java.sql.Types;
|
|||
import org.hibernate.type.descriptor.ValueBinder;
|
||||
import org.hibernate.type.descriptor.ValueExtractor;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.BasicJavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterNumericData;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.hibernate.type.descriptor.jdbc.LongVarbinaryJdbcType;
|
|||
import org.hibernate.type.descriptor.jdbc.LongVarcharJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.NumericJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.RealJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.RowIdJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.TimeJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.TimestampJdbcType;
|
||||
|
@ -84,5 +85,7 @@ public class JdbcTypeBaseline {
|
|||
target.addDescriptor( Types.LONGNVARCHAR, LongVarcharJdbcType.INSTANCE );
|
||||
target.addDescriptor( Types.NCLOB, ClobJdbcType.DEFAULT );
|
||||
target.addDescriptor( new LongVarcharJdbcType(SqlTypes.LONG32NVARCHAR) );
|
||||
|
||||
target.addDescriptor( RowIdJdbcType.INSTANCE );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
* <ul>
|
||||
* <li>{@link java.sql.Types#DATALINK DATALINK}</li>
|
||||
* <li>{@link java.sql.Types#DISTINCT DISTINCT}</li>
|
||||
* <li>{@link java.sql.Types#ROWID ROWID}</li>
|
||||
* <li>{@link java.sql.Types#REF REF}</li>
|
||||
* <li>{@link java.sql.Types#REF_CURSOR REF_CURSOR}</li>
|
||||
* </ul>
|
||||
|
|
|
@ -13,11 +13,9 @@ import jakarta.persistence.Id;
|
|||
import jakarta.persistence.Table;
|
||||
|
||||
import org.hibernate.annotations.RowId;
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialect;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -33,14 +31,13 @@ import static org.junit.Assert.assertThat;
|
|||
*/
|
||||
@DomainModel( annotatedClasses = RowIdTest.Product.class )
|
||||
@SessionFactory(statementInspectorClass = SQLStatementInspector.class)
|
||||
@RequiresDialect( value = OracleDialect.class)
|
||||
public class RowIdTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
Product product = new Product();
|
||||
product.setId( 1L );
|
||||
product.setId( "1L" );
|
||||
product.setName( "Mobile phone" );
|
||||
product.setNumber( "123-456-7890" );
|
||||
session.persist( product );
|
||||
|
@ -51,15 +48,19 @@ public class RowIdTest {
|
|||
void testRowId(SessionFactoryScope scope) {
|
||||
final String updatedName = "Smart phone";
|
||||
scope.inTransaction( session -> {
|
||||
String rowId = scope.getSessionFactory().getJdbcServices().getDialect().rowId();
|
||||
|
||||
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
Product product = session.find( Product.class, 1L );
|
||||
Product product = session.find( Product.class, "1L" );
|
||||
|
||||
List<String> sqls = statementInspector.getSqlQueries();
|
||||
|
||||
assertThat( sqls, hasSize( 1 ) );
|
||||
assertThat( sqls.get(0).matches( "(?i).*\\bselect\\b.+\\.ROWID.*\\bfrom\\s+product\\b.*" ), is( true ) );
|
||||
assertThat( rowId == null
|
||||
|| sqls.get(0).matches( "(?i).*\\bselect\\b.+\\." + rowId + ".*\\bfrom\\s+product\\b.*" ),
|
||||
is( true ) );
|
||||
|
||||
assertThat( product.getName(), not( is( updatedName ) ) );
|
||||
|
||||
|
@ -71,7 +72,9 @@ public class RowIdTest {
|
|||
sqls = statementInspector.getSqlQueries();
|
||||
|
||||
assertThat( sqls, hasSize( 1 ) );
|
||||
assertThat( sqls.get( 0 ).matches( "(?i).*\\bupdate\\s+product\\b.+?\\bwhere\\s+ROWID\\s*=.*" ), is( true ) );
|
||||
assertThat( rowId == null
|
||||
|| sqls.get( 0 ).matches( "(?i).*\\bupdate\\s+product\\b.+?\\bwhere\\s+" + rowId + "\\s*=.*" ),
|
||||
is( true ) );
|
||||
} );
|
||||
|
||||
scope.inTransaction( session -> {
|
||||
|
@ -82,11 +85,11 @@ public class RowIdTest {
|
|||
|
||||
@Entity(name = "Product")
|
||||
@Table(name = "product")
|
||||
@RowId("ROWID")
|
||||
@RowId
|
||||
public static class Product {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
private String id;
|
||||
|
||||
@Column(name = "`name`")
|
||||
private String name;
|
||||
|
@ -94,11 +97,11 @@ public class RowIdTest {
|
|||
@Column(name = "`number`")
|
||||
private String number;
|
||||
|
||||
public Long getId() {
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@ import org.hibernate.dialect.NationalizationSupport;
|
|||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
import org.hibernate.dialect.TiDBDialect;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureCheck;
|
||||
|
||||
/**
|
||||
* Container class for different implementation of the {@link DialectCheck} interface.
|
||||
*
|
||||
|
@ -312,4 +310,11 @@ abstract public class DialectChecks {
|
|||
return dialect.supportsRecursiveCTE();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SupportsRowId implements DialectCheck {
|
||||
@Override
|
||||
public boolean isMatch(Dialect dialect) {
|
||||
return dialect.rowId() != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue