HHH-13763 : Update all load-by-key handling to use SQL AST

SingleUniqueKeyEntityLoader
This commit is contained in:
Steve Ebersole 2019-12-03 13:12:57 -06:00
parent 0a41ac8466
commit e112d9631e
6 changed files with 205 additions and 239 deletions

View File

@ -6,17 +6,38 @@
*/
package org.hibernate.loader.ast.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.hibernate.LockOptions;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.SingleUniqueKeyEntityLoader;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
import org.hibernate.sql.exec.spi.Callback;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameterBinding;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcSelect;
/**
* @author Steve Ebersole
*/
public class SingleUniqueKeyEntityLoaderStandard implements SingleUniqueKeyEntityLoader {
public class SingleUniqueKeyEntityLoaderStandard<T> implements SingleUniqueKeyEntityLoader<T> {
private final EntityMappingType entityDescriptor;
private final SingularAttributeMapping uniqueKeyAttribute;
@ -33,10 +54,172 @@ public class SingleUniqueKeyEntityLoaderStandard implements SingleUniqueKeyEntit
}
@Override
public Object load(
public T load(
Object ukValue,
LockOptions lockOptions,
SharedSessionContractImplementor session) {
throw new NotYetImplementedFor6Exception( getClass() );
final SessionFactoryImplementor sessionFactory = session.getFactory();
// todo (6.0) : cache the SQL AST and JdbcParameters
final List<JdbcParameter> jdbcParameters = new ArrayList<>();
final SelectStatement sqlAst = LoaderSelectBuilder.createSelect(
entityDescriptor,
Collections.emptyList(),
uniqueKeyAttribute,
null,
1,
LoadQueryInfluencers.NONE,
LockOptions.READ,
jdbcParameters::add,
sessionFactory
);
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst );
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
final Iterator<JdbcParameter> jdbcParamItr = jdbcParameters.iterator();
uniqueKeyAttribute.visitJdbcValues(
ukValue,
Clause.WHERE,
(jdbcValue, jdbcMapping) -> {
assert jdbcParamItr.hasNext();
final JdbcParameter jdbcParameter = jdbcParamItr.next();
jdbcParameterBindings.addBinding(
jdbcParameter,
new JdbcParameterBinding() {
@Override
public JdbcMapping getBindType() {
return jdbcMapping;
}
@Override
public Object getBindValue() {
return jdbcValue;
}
}
);
},
session
);
final List<Object> list = sessionFactory.getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect,
jdbcParameterBindings,
new ExecutionContext() {
@Override
public SharedSessionContractImplementor getSession() {
return session;
}
@Override
public QueryOptions getQueryOptions() {
return QueryOptions.NONE;
}
@Override
public QueryParameterBindings getQueryParameterBindings() {
return QueryParameterBindings.NO_PARAM_BINDINGS;
}
@Override
public Callback getCallback() {
return afterLoadAction -> {
};
}
},
row -> row[0]
);
assert list.size() == 1;
//noinspection unchecked
return (T) list.get( 0 );
}
@Override
public Object resolveId(Object ukValue, SharedSessionContractImplementor session) {
final SessionFactoryImplementor sessionFactory = session.getFactory();
// todo (6.0) : cache the SQL AST and JdbcParameters
final List<JdbcParameter> jdbcParameters = new ArrayList<>();
final SelectStatement sqlAst = LoaderSelectBuilder.createSelect(
entityDescriptor,
Collections.singletonList( entityDescriptor.getIdentifierMapping() ),
uniqueKeyAttribute,
null,
1,
LoadQueryInfluencers.NONE,
LockOptions.READ,
jdbcParameters::add,
sessionFactory
);
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst );
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
final Iterator<JdbcParameter> jdbcParamItr = jdbcParameters.iterator();
uniqueKeyAttribute.visitJdbcValues(
ukValue,
Clause.WHERE,
(jdbcValue, jdbcMapping) -> {
assert jdbcParamItr.hasNext();
final JdbcParameter jdbcParameter = jdbcParamItr.next();
jdbcParameterBindings.addBinding(
jdbcParameter,
new JdbcParameterBinding() {
@Override
public JdbcMapping getBindType() {
return jdbcMapping;
}
@Override
public Object getBindValue() {
return jdbcValue;
}
}
);
},
session
);
final List<Object> list = sessionFactory.getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect,
jdbcParameterBindings,
new ExecutionContext() {
@Override
public SharedSessionContractImplementor getSession() {
return session;
}
@Override
public QueryOptions getQueryOptions() {
return QueryOptions.NONE;
}
@Override
public QueryParameterBindings getQueryParameterBindings() {
return QueryParameterBindings.NO_PARAM_BINDINGS;
}
@Override
public Callback getCallback() {
return afterLoadAction -> {
};
}
},
row -> row[0]
);
assert list.size() == 1;
return list.get( 0 );
}
}

View File

@ -20,4 +20,9 @@ public interface SingleUniqueKeyEntityLoader<T> extends SingleEntityLoader {
*/
@Override
T load(Object ukValue, LockOptions lockOptions, SharedSessionContractImplementor session);
/**
* Resolve the matching id
*/
Object resolveId(Object key, SharedSessionContractImplementor session);
}

View File

@ -1255,19 +1255,6 @@ public abstract class AbstractEntityPersister
);
}
private void resolveTableReferenceJoins(
TableReference rootTableReference,
SqlAliasBase sqlAliasBase,
org.hibernate.sql.ast.JoinType joinType,
Consumer<TableReferenceJoin> collector,
SqlExpressionResolver sqlExpressionResolver) {
for ( int i = 1; i < getSubclassTableSpan(); i++ ) {
collector.accept(
createTableReferenceJoin( i, rootTableReference, joinType, sqlAliasBase, sqlExpressionResolver )
);
}
}
@Override
public void applyTableReferences(
SqlAliasBase sqlAliasBase,
@ -1817,8 +1804,7 @@ public abstract class AbstractEntityPersister
}
@Override
public Object getIdByUniqueKey(Object key, String uniquePropertyName, SharedSessionContractImplementor session)
throws HibernateException {
public Object getIdByUniqueKey(Object key, String uniquePropertyName, SharedSessionContractImplementor session) {
if ( LOG.isTraceEnabled() ) {
LOG.tracef(
"resolving unique key [%s] to identifier for entity [%s]",
@ -1827,103 +1813,7 @@ public abstract class AbstractEntityPersister
);
}
int propertyIndex = getSubclassPropertyIndex( uniquePropertyName );
if ( propertyIndex < 0 ) {
throw new HibernateException(
"Could not determine Type for property [" + uniquePropertyName + "] on entity [" + getEntityName() + "]"
);
}
Type propertyType = getSubclassPropertyType( propertyIndex );
try {
PreparedStatement ps = session
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( generateIdByUniqueKeySelectString( uniquePropertyName ) );
try {
propertyType.nullSafeSet( ps, key, 1, session );
ResultSet rs = session.getJdbcCoordinator().getResultSetReturn().extract( ps );
try {
//if there is no resulting row, return null
if ( !rs.next() ) {
return null;
}
return (Serializable) getIdentifierType().nullSafeGet( rs, getIdentifierAliases(), session, null );
}
finally {
session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( rs, ps );
}
}
finally {
session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( ps );
session.getJdbcCoordinator().afterStatementExecution();
}
}
catch (SQLException e) {
throw session.getJdbcServices().getSqlExceptionHelper().convert(
e,
String.format(
"could not resolve unique property [%s] to identifier for entity [%s]",
uniquePropertyName,
getEntityName()
),
getSQLSnapshotSelectString()
);
}
}
protected String generateIdByUniqueKeySelectString(String uniquePropertyName) {
Select select = new Select( getFactory().getDialect() );
if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) {
select.setComment( "resolve id by unique property [" + getEntityName() + "." + uniquePropertyName + "]" );
}
final String rooAlias = getRootAlias();
select.setFromClause( fromTableFragment( rooAlias ) + fromJoinFragment( rooAlias, true, false ) );
SelectFragment selectFragment = new SelectFragment();
selectFragment.addColumns( rooAlias, getIdentifierColumnNames(), getIdentifierAliases() );
select.setSelectClause( selectFragment );
StringBuilder whereClauseBuffer = new StringBuilder();
final int uniquePropertyIndex = getSubclassPropertyIndex( uniquePropertyName );
final String uniquePropertyTableAlias = generateTableAlias(
rooAlias,
getSubclassPropertyTableNumber( uniquePropertyIndex )
);
String sep = "";
for ( String columnTemplate : getSubclassPropertyColumnReaderTemplateClosure()[uniquePropertyIndex] ) {
if ( columnTemplate == null ) {
continue;
}
final String columnReference = StringHelper.replace(
columnTemplate,
Template.TEMPLATE,
uniquePropertyTableAlias
);
whereClauseBuffer.append( sep ).append( columnReference ).append( "=?" );
sep = " and ";
}
for ( String formulaTemplate : getSubclassPropertyFormulaTemplateClosure()[uniquePropertyIndex] ) {
if ( formulaTemplate == null ) {
continue;
}
final String formulaReference = StringHelper.replace(
formulaTemplate,
Template.TEMPLATE,
uniquePropertyTableAlias
);
whereClauseBuffer.append( sep ).append( formulaReference ).append( "=?" );
sep = " and ";
}
whereClauseBuffer.append( whereJoinFragment( rooAlias, true, false ) );
select.setWhereClause( whereClauseBuffer.toString() );
return select.setOuterJoins( "", "" ).toStatementString();
return getUniqueKeyLoader( uniquePropertyName ).resolveId( key, session );
}
@ -2644,13 +2534,18 @@ public abstract class AbstractEntityPersister
}
private Map<SingularAttributeMapping, SingleUniqueKeyEntityLoader<?>> uniqueKeyLoadersNew;
public Object loadByUniqueKey(
String propertyName,
Object uniqueKey,
SharedSessionContractImplementor session) throws HibernateException {
final SingularAttributeMapping attribute = (SingularAttributeMapping) findSubPart( propertyName );
return getUniqueKeyLoader( propertyName ).load( uniqueKey, LockOptions.READ, session );
}
private Map<SingularAttributeMapping, SingleUniqueKeyEntityLoader<?>> uniqueKeyLoadersNew;
protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName) {
final SingularAttributeMapping attribute = (SingularAttributeMapping) findSubPart( attributeName );
final SingleUniqueKeyEntityLoader<?> existing;
if ( uniqueKeyLoadersNew == null ) {
uniqueKeyLoadersNew = new IdentityHashMap<>();
@ -2666,6 +2561,7 @@ public abstract class AbstractEntityPersister
final SingleUniqueKeyEntityLoader loader = new SingleUniqueKeyEntityLoaderStandard( this, attribute );
uniqueKeyLoadersNew.put( attribute, loader );
return loader;
}

View File

@ -16,7 +16,10 @@ import org.hibernate.dialect.Dialect;
* Implementation of InsertSelect.
*
* @author Steve Ebersole
*
* @deprecated (since 6.0) Use {@link org.hibernate.sql.ast.tree.insert.InsertSelectStatement} instead
*/
@Deprecated
public class InsertSelect {
private String tableName;
private String comment;

View File

@ -1,18 +0,0 @@
/*
* 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.sql;
/**
* TODO : javadoc
*
* @author Steve Ebersole
*/
public interface SelectExpression {
public String getExpression();
public String getAlias();
}

View File

@ -1,103 +0,0 @@
/*
* 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.sql;
import java.util.ArrayList;
import java.util.HashSet;
import org.hibernate.dialect.Dialect;
import org.jboss.logging.Logger;
/**
* Models a SELECT values lists. Eventually, rather than Strings, pass in the Column/Formula representations (something
* like {@link org.hibernate.sql.ordering.antlr.ColumnReference}/{@link org.hibernate.sql.ordering.antlr.FormulaReference}
*
* @author Steve Ebersole
*/
public class SelectValues {
private static final Logger log = Logger.getLogger( SelectValues.class );
private static class SelectValue {
private final String qualifier;
private final String value;
private final String alias;
private SelectValue(String qualifier, String value, String alias) {
this.qualifier = qualifier;
this.value = value;
this.alias = alias;
}
}
private final Dialect dialect;
private final ArrayList<SelectValue> selectValueList = new ArrayList<SelectValue>();
public SelectValues(Dialect dialect) {
this.dialect = dialect;
}
public SelectValues addColumns(String qualifier, String[] columnNames, String[] columnAliases) {
for ( int i = 0; i < columnNames.length; i++ ) {
if ( columnNames[i] != null ) {
addColumn( qualifier, columnNames[i], columnAliases[i] );
}
}
return this;
}
public SelectValues addColumn(String qualifier, String columnName, String columnAlias) {
selectValueList.add( new SelectValue( qualifier, columnName, columnAlias ) );
return this;
}
public SelectValues addParameter(int jdbcTypeCode, int length) {
final String selectExpression = dialect.requiresCastingOfParametersInSelectClause()
? dialect.cast( "?", jdbcTypeCode, length )
: "?";
selectValueList.add( new SelectValue( null, selectExpression, null ) );
return this;
}
public SelectValues addParameter(int jdbcTypeCode, int precision, int scale) {
final String selectExpression = dialect.requiresCastingOfParametersInSelectClause()
? dialect.cast( "?", jdbcTypeCode, precision, scale )
: "?";
selectValueList.add( new SelectValue( null, selectExpression, null ) );
return this;
}
public String render() {
final StringBuilder buf = new StringBuilder( selectValueList.size() * 10 );
final HashSet<String> uniqueAliases = new HashSet<String>();
boolean firstExpression = true;
for ( SelectValue selectValue : selectValueList ) {
if ( selectValue.alias != null ) {
if ( ! uniqueAliases.add( selectValue.alias ) ) {
log.debug( "Skipping select-value with non-unique alias" );
continue;
}
}
if ( firstExpression ) {
firstExpression = false;
}
else {
buf.append( ", " );
}
if ( selectValue.qualifier != null ) {
buf.append( selectValue.qualifier ).append( '.' );
}
buf.append( selectValue.value );
if ( selectValue.alias != null ) {
buf.append( " as " ).append( selectValue.alias );
}
}
return buf.toString();
}
}