diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java index 4d483c2289..67421a5bd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/FilterSourceImpl.java @@ -43,7 +43,7 @@ public class FilterSourceImpl for ( Serializable content : filterElement.getContent() ) { if ( String.class.isInstance( content ) ) { final String str = content.toString(); - if ( !StringHelper.isEmptyOrWhiteSpace( str ) ) { + if ( !StringHelper.isBlank( str ) ) { conditionContent = str.trim(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java index 001722e60a..fbe9ad5740 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java @@ -18,7 +18,6 @@ import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.engine.spi.Status; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.tuple.entity.EntityTuplizer; @@ -37,9 +36,9 @@ public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInter private final boolean inLineDirtyChecking; private Set writtenFieldNames; - private boolean initialized; + private Status status; - private boolean initializeBeforeWrite; + private final boolean initializeBeforeWrite; public EnhancementAsProxyLazinessInterceptor( String entityName, @@ -63,6 +62,7 @@ public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInter // if self-dirty tracking is enabled but DynamicUpdate is not enabled then we need to initialise the entity // because the pre-computed update statement contains even not dirty properties and so we need all the values initializeBeforeWrite = !inLineDirtyChecking || !entityPersister.getEntityMetamodel().isDynamicUpdate(); + status = Status.UNINITIALIZED; } public EntityKey getEntityKey() { @@ -72,7 +72,7 @@ public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInter @Override protected Object handleRead(Object target, String attributeName, Object value) { // it is illegal for this interceptor to still be attached to the entity after initialization - if ( initialized ) { + if ( isInitialized() ) { throw new IllegalStateException( "EnhancementAsProxyLazinessInterceptor interception on an initialized instance" ); } @@ -125,7 +125,7 @@ public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInter isTempSession ); - initialized = true; + setInitialized(); if ( writtenValues != null ) { // here is the replaying of the explicitly set values we prepared above @@ -189,7 +189,7 @@ public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInter // Add an entry for this entity in the PC of the temp Session session.getPersistenceContextInternal().addEntity( target, - Status.READ_ONLY, + org.hibernate.engine.spi.Status.READ_ONLY, // loaded state ArrayHelper.filledArray( LazyPropertyInitializer.UNFETCHED_PROPERTY, @@ -215,7 +215,7 @@ public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInter @Override protected Object handleWrite(Object target, String attributeName, Object oldValue, Object newValue) { - if ( initialized ) { + if ( isInitialized() ) { throw new IllegalStateException( "EnhancementAsProxyLazinessInterceptor interception on an initialized instance" ); } @@ -251,7 +251,7 @@ public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInter forceInitialize( target, attributeName ); } finally { - initialized = true; + setInitialized(); } if ( inLineDirtyChecking ) { @@ -279,14 +279,14 @@ public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInter @Override public void attributeInitialized(String name) { - if ( initialized ) { + if ( status == Status.INITIALIZED ) { throw new UnsupportedOperationException( "Expected call to EnhancementAsProxyLazinessInterceptor#attributeInitialized" ); } } @Override public boolean isAttributeLoaded(String fieldName) { - if ( initialized ) { + if ( isInitialized() ) { throw new UnsupportedOperationException( "Call to EnhancementAsProxyLazinessInterceptor#isAttributeLoaded on an interceptor which is marked as initialized" ); } // Only fields from the identifier are loaded (until it's initialized) @@ -295,7 +295,7 @@ public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInter @Override public boolean hasAnyUninitializedAttributes() { - if ( initialized ) { + if ( isInitialized() ) { throw new UnsupportedOperationException( "Call to EnhancementAsProxyLazinessInterceptor#hasAnyUninitializedAttributes on an interceptor which is marked as initialized" ); } return true; @@ -306,8 +306,26 @@ public class EnhancementAsProxyLazinessInterceptor extends AbstractLazyLoadInter return entityKey.getIdentifier(); } + public boolean isInitializing() { + return status == Status.INITIALIZING; + } + + public void setInitializing() { + status = Status.INITIALIZING; + } + //Mostly useful for testing public boolean isInitialized() { - return initialized; + return status == Status.INITIALIZED; + } + + private void setInitialized() { + status = Status.INITIALIZED; + } + + private enum Status { + UNINITIALIZED, + INITIALIZING, + INITIALIZED } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/IdSubselectUpdateExecutor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/IdSubselectUpdateExecutor.java new file mode 100644 index 0000000000..38dade2b2f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/IdSubselectUpdateExecutor.java @@ -0,0 +1,120 @@ +/* + * 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 . + */ +package org.hibernate.hql.internal.ast.exec; + +import antlr.RecognitionException; +import org.hibernate.AssertionFailure; +import org.hibernate.HibernateException; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.hql.internal.ast.HqlSqlWalker; +import org.hibernate.hql.internal.ast.QuerySyntaxException; +import org.hibernate.hql.internal.ast.SqlGenerator; +import org.hibernate.hql.internal.ast.tree.AssignmentSpecification; +import org.hibernate.hql.internal.ast.tree.UpdateStatement; +import org.hibernate.param.ParameterSpecification; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.sql.Update; + +import java.util.List; +import java.util.stream.IntStream; + +import static org.hibernate.hql.spi.id.AbstractTableBasedBulkIdHandler.generateIdSelect; + +/** + * Executes HQL bulk updates against a single table, using a subselect + * against multiple tables to collect ids, which is needed when the + * where condition of the query touches columns from multiple tables. + * + * @author Gavin King + */ +public class IdSubselectUpdateExecutor extends BasicExecutor { + + private final Queryable persister; + private final String sql; + private final List parameterSpecifications; + + public Queryable getPersister() { + return persister; + } + @Override + public String getSql() { + return sql; + } + + @Override + public List getParameterSpecifications() { + return parameterSpecifications; + } + + public IdSubselectUpdateExecutor(HqlSqlWalker walker) { + persister = walker.getFinalFromClause().getFromElement().getQueryable(); + + Dialect dialect = walker.getDialect(); + UpdateStatement updateStatement = (UpdateStatement) walker.getAST(); + List assignments = walker.getAssignmentSpecifications(); + + String whereClause; + if ( updateStatement.getWhereClause().getNumberOfChildren() == 0 ) { + whereClause = ""; + } + else { + try { + SqlGenerator gen = new SqlGenerator( walker.getSessionFactoryHelper().getFactory() ); + gen.whereClause( updateStatement.getWhereClause() ); + gen.getParseErrorHandler().throwQueryException(); + whereClause = gen.getSQL().substring( 7 ); // strip the " where " + } + catch ( RecognitionException e ) { + throw new HibernateException( "Unable to generate id select for DML operation", e ); + } + } + String tableAlias = updateStatement.getFromClause().getFromElement().getTableAlias(); + String idSelect = generateIdSelect( tableAlias, whereClause, dialect, persister ); + + String[] tableNames = persister.getConstraintOrderedTableNameClosure(); + String[][] columnNames = persister.getContraintOrderedTableKeyColumnClosure(); + + int[] affectedTables = + IntStream.range( 0, tableNames.length ).filter( + table -> assignments.stream().anyMatch( + assign -> assign.affectsTable( tableNames[table] ) + ) + ).toArray(); + if ( affectedTables.length > 1 ) { + throw new AssertionFailure("more than one affected table"); + } + int affectedTable = affectedTables[0]; + + String tableName = tableNames[affectedTable]; + String idColumnNames = String.join( ", ", columnNames[affectedTable] ); + Update update = new Update( dialect ).setTableName( tableName ); + if ( dialect instanceof MySQLDialect) { + //MySQL needs an extra subselect to hack the query optimizer + String selectedIdColumns = String.join( ", ", persister.getIdentifierColumnNames() ); + update.setWhere( "(" + idColumnNames + ") in (select " + selectedIdColumns + " from (" + idSelect + ") as ht_ids)" ); + } + else { + update.setWhere( "(" + idColumnNames + ") in (" + idSelect + ")" ); + } + for ( AssignmentSpecification assignment: assignments ) { + update.appendAssignmentFragment( assignment.getSqlAssignmentFragment() ); + } + sql = update.toStatementString(); + + // now collect the parameters from the whole query + // parameters included in the list + try { + SqlGenerator gen = new SqlGenerator( walker.getSessionFactoryHelper().getFactory() ); + gen.statement( walker.getAST() ); + parameterSpecifications = gen.getCollectedParameters(); + } + catch ( RecognitionException e ) { + throw QuerySyntaxException.convert( e ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/InsertExecutor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/InsertExecutor.java new file mode 100644 index 0000000000..bdff7a5275 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/InsertExecutor.java @@ -0,0 +1,59 @@ +/* + * 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 . + */ +package org.hibernate.hql.internal.ast.exec; + +import antlr.RecognitionException; +import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; +import org.hibernate.hql.internal.ast.HqlSqlWalker; +import org.hibernate.hql.internal.ast.QuerySyntaxException; +import org.hibernate.hql.internal.ast.SqlGenerator; +import org.hibernate.hql.internal.ast.tree.InsertStatement; +import org.hibernate.hql.internal.ast.tree.Statement; +import org.hibernate.param.ParameterSpecification; +import org.hibernate.persister.entity.Queryable; + +import java.util.List; + +/** + * Executes HQL insert statements. + * + * @author Gavin King + */ +public class InsertExecutor extends BasicExecutor { + private final Queryable persister; + private final String sql; + private final List parameterSpecifications; + + @Override + public Queryable getPersister() { + return persister; + } + + @Override + public String getSql() { + return sql; + } + + @Override + public List getParameterSpecifications() { + return parameterSpecifications; + } + + public InsertExecutor(HqlSqlWalker walker) { + persister = ( (InsertStatement) walker.getAST() ).getIntoClause().getQueryable(); + try { + SqlGenerator gen = new SqlGenerator( walker.getSessionFactoryHelper().getFactory() ); + gen.statement( walker.getAST() ); + sql = gen.getSQL(); + gen.getParseErrorHandler().throwQueryException(); + parameterSpecifications = gen.getCollectedParameters(); + } + catch ( RecognitionException e ) { + throw QuerySyntaxException.convert( e ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/SimpleUpdateExecutor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/SimpleUpdateExecutor.java new file mode 100644 index 0000000000..4c7407c1bd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/SimpleUpdateExecutor.java @@ -0,0 +1,69 @@ +/* + * 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 . + */ +package org.hibernate.hql.internal.ast.exec; + +import antlr.RecognitionException; +import org.hibernate.AssertionFailure; +import org.hibernate.hql.internal.ast.HqlSqlWalker; +import org.hibernate.hql.internal.ast.QuerySyntaxException; +import org.hibernate.hql.internal.ast.SqlGenerator; +import org.hibernate.param.ParameterSpecification; +import org.hibernate.persister.entity.Queryable; + +import java.util.List; + +/** + * Executes HQL bulk updates against a single table, where the + * query only touches columns from the table it's updating, and + * so we don't need to use a subselect. + * + * @author Gavin King + */ +public class SimpleUpdateExecutor extends BasicExecutor { + + private final Queryable persister; + private final String sql; + private final List parameterSpecifications; + + @Override + public Queryable getPersister() { + return persister; + } + + @Override + public String getSql() { + return sql; + } + + @Override + public List getParameterSpecifications() { + return parameterSpecifications; + } + + public SimpleUpdateExecutor(HqlSqlWalker walker) { + persister = walker.getFinalFromClause().getFromElement().getQueryable(); + + if ( persister.isMultiTable() && walker.getQuerySpaces().size() > 1 ) { + throw new AssertionFailure("not a simple update"); + } + + try { + SqlGenerator gen = new SqlGenerator( walker.getSessionFactoryHelper().getFactory() ); + gen.statement( walker.getAST() ); + gen.getParseErrorHandler().throwQueryException(); + // workaround for a problem where HqlSqlWalker actually generates + // broken SQL with undefined aliases in the where clause, because + // that is what MultiTableUpdateExecutor is expecting to get + String alias = walker.getFinalFromClause().getFromElement().getTableAlias(); + sql = gen.getSQL().replace( alias + ".", "" ); + parameterSpecifications = gen.getCollectedParameters(); + } + catch ( RecognitionException e ) { + throw QuerySyntaxException.convert( e ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java index c5579424b7..8c6df556b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java @@ -539,8 +539,30 @@ public final class StringHelper { return string == null || string.isEmpty(); } - public static boolean isEmptyOrWhiteSpace(String string) { - return isEmpty( string ) || isEmpty( string.trim() ); + public static boolean isBlank(String string) { + //TODO use Java 11's more efficient String#isBlank - currently we still require Java 8 compatibility + if ( string == null || string.isEmpty() ) { + return true; + } + else { + //Else: we need to check all characters, preferably without using String#trim() so to + //not allocate temporary strings + for ( int i = 0; i < string.length(); i++ ) { + if ( ! Character.isWhitespace( string.charAt( i ) ) ) { + return false; + } + } + return true; + } + } + + /** + * @deprecated use {@link #isBlank(String)} + * @return + */ + @Deprecated + public static boolean isEmptyOrWhitespace(String string) { + return isBlank(string); } public static String qualify(String prefix, String name) { @@ -669,7 +691,7 @@ public final class StringHelper { } public static String moveAndToBeginning(String filter) { - if ( filter.trim().length() > 0 ) { + if ( !isBlank( filter ) ) { filter += " and "; if ( filter.startsWith( " and " ) ) { filter = filter.substring( 4 ); diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/ast/SqlScriptLogging.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/ast/SqlScriptLogging.java new file mode 100644 index 0000000000..d080151024 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/ast/SqlScriptLogging.java @@ -0,0 +1,28 @@ +/* + * 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 . + */ +package org.hibernate.tool.schema.ast; + +import org.jboss.logging.Logger; +import org.jboss.logging.annotations.ValidIdRange; + +/** + * @author Steve Ebersole + */ +@ValidIdRange( ) +public class SqlScriptLogging { + public static final String SCRIPT_LOGGER_NAME = "org.hibernate.orm.tooling.schema.script"; + public static final Logger SCRIPT_LOGGER = Logger.getLogger( SCRIPT_LOGGER_NAME ); + + public static final boolean TRACE_ENABLED = SCRIPT_LOGGER.isTraceEnabled(); + public static final boolean DEBUG_ENABLED = SCRIPT_LOGGER.isDebugEnabled(); + + public static final String AST_LOGGER_NAME = SCRIPT_LOGGER_NAME + ".graph"; + public static final Logger AST_LOGGER = Logger.getLogger( AST_LOGGER_NAME ); + + public static final boolean AST_TRACE_ENABLED = AST_LOGGER.isTraceEnabled(); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java index 4f1f467f3d..2e7ca0f13e 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java @@ -72,7 +72,7 @@ public class InformationExtractorJdbcDatabaseMetaDataImpl implements Information "" ) ); - if ( ! StringHelper.isEmptyOrWhiteSpace( extraPhysycalTableTypesConfig ) ) { + if ( ! StringHelper.isBlank( extraPhysycalTableTypesConfig ) ) { this.extraPhysicalTableTypes = StringHelper.splitTrimmingTokens( ",;", extraPhysycalTableTypesConfig, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/collection/EmbeddableWithManyToMany_HHH_11302_Test.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/collection/EmbeddableWithManyToMany_HHH_11302_Test.java index 9bac37a98a..79d8c31548 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/collection/EmbeddableWithManyToMany_HHH_11302_Test.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/collection/EmbeddableWithManyToMany_HHH_11302_Test.java @@ -26,6 +26,7 @@ import javax.persistence.Table; import javax.persistence.Version; import org.hibernate.boot.MetadataSources; +import org.hibernate.internal.util.StringHelper; import org.hibernate.testing.TestForIssue; @@ -114,7 +115,7 @@ public class EmbeddableWithManyToMany_HHH_11302_Test result += "id: " + id; } result += ", version: " + version; - if ( type != null && !type.trim().isEmpty() ) { + if ( !StringHelper.isBlank( type ) ) { result += ", type: " + type; } return result; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/collection/EmbeddableWithOneToMany_HHH_11302_Test.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/collection/EmbeddableWithOneToMany_HHH_11302_Test.java index 8ed2e810b0..6594de7567 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/collection/EmbeddableWithOneToMany_HHH_11302_Test.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/collection/EmbeddableWithOneToMany_HHH_11302_Test.java @@ -26,6 +26,7 @@ import javax.persistence.Table; import javax.persistence.Version; import org.hibernate.boot.MetadataSources; +import org.hibernate.internal.util.StringHelper; import org.hibernate.testing.TestForIssue; @@ -114,7 +115,7 @@ public class EmbeddableWithOneToMany_HHH_11302_Test result += "id: " + id; } result += ", version: " + version; - if ( type != null && !type.trim().isEmpty() ) { + if ( !StringHelper.isBlank( type ) ) { result += ", type: " + type; } return result; diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SharingReferenceTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SharingReferenceTest.java new file mode 100644 index 0000000000..26bdac94ee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/SharingReferenceTest.java @@ -0,0 +1,259 @@ +/* + * 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 . + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.DirtyCheckEnhancementContext; +import org.hibernate.test.bytecode.enhancement.lazy.proxy.inlinedirtychecking.NoDirtyCheckEnhancementContext; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +/** + * @author Andrea Boriero + */ +@RunWith(BytecodeEnhancerRunner.class) +@CustomEnhancementContext({ DirtyCheckEnhancementContext.class, NoDirtyCheckEnhancementContext.class }) +public class SharingReferenceTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( Ceo.class ); + sources.addAnnotatedClass( Manager.class ); + sources.addAnnotatedClass( Supervisor.class ); + sources.addAnnotatedClass( Worker.class ); + } + + @Before + public void setUp() { + inTransaction( + session -> { + Ceo ceo = new Ceo(); + ceo.setName( "Jill" ); + session.persist( ceo ); + + Manager m1 = new Manager(); + m1.setName( "Jane" ); + m1.setCeo( ceo ); + session.persist( m1 ); + + Manager m2 = new Manager(); + m2.setName( "Jannet" ); + m2.setCeo( ceo ); + session.persist( m2 ); + + Supervisor s1 = new Supervisor(); + s1.setName( "Bob" ); + s1.getManagers().add( m1 ); + s1.getManagers().add( m2 ); //comment out this line and the test will pass + s1.setCeo( ceo ); + session.persist( s1 ); + + Worker worker = new Worker(); + worker.setName( "James" ); + worker.setSupervisor( s1 ); + session.persist( worker ); + } + ); + } + + @Test + public void testFind() { + inTransaction( + session -> { + Ceo ceo = session.find( Ceo.class, 1L ); + assertEquals( "Jill", ceo.getName() ); + + Worker worker = session.find( Worker.class, 1L ); + assertEquals( worker.getName(), "James" ); + + Supervisor supervisor = worker.getSupervisor(); + Manager manager = supervisor.getManagers().get( 0 ); + assertSame( ceo, manager.getCeo() ); + assertSame( ceo, supervisor.getCeo() ); + } + ); + + } + + @Entity(name = "Ceo") + public static class Ceo { + + private long id; + private String name; + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ceqSeq") + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Manager") + public static class Manager { + + private long id; + private String name; + private Ceo ceo; + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "managerSeq") + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + public Ceo getCeo() { + return ceo; + } + + public void setCeo(Ceo ceo) { + this.ceo = ceo; + } + } + + @Entity(name = "Supervisor") + public static class Supervisor { + + private long id; + private String name; + private List managers = new ArrayList<>(); + private Ceo ceo; + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "supervisorSeq") + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + public List getManagers() { + return managers; + } + + public void setManagers(List managers) { + this.managers = managers; + } + + @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + public Ceo getCeo() { + return ceo; + } + + public void setCeo(Ceo ceo) { + this.ceo = ceo; + } + } + + @Entity(name = "Worker") + public static class Worker { + + private long id; + private String name; + private Supervisor supervisor; + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "workerSeq") + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + public Supervisor getSupervisor() { + return supervisor; + } + + public void setSupervisor(Supervisor supervisor) { + this.supervisor = supervisor; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/custom/MyCustomSqlTypeDescriptor.java b/hibernate-core/src/test/java/org/hibernate/test/converter/custom/MyCustomSqlTypeDescriptor.java index a9f336232d..2773bd9561 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/converter/custom/MyCustomSqlTypeDescriptor.java +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/custom/MyCustomSqlTypeDescriptor.java @@ -11,6 +11,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; +import org.hibernate.internal.util.StringHelper; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; @@ -55,7 +56,7 @@ public class MyCustomSqlTypeDescriptor implements SqlTypeDescriptor { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { final String valueStr = javaTypeDescriptor.unwrap( value, String.class, options ); - if ( valueStr == null || valueStr.trim().isEmpty() ) { + if ( StringHelper.isBlank( valueStr ) ) { st.setNull( index, getSqlType() ); } else { @@ -66,7 +67,7 @@ public class MyCustomSqlTypeDescriptor implements SqlTypeDescriptor { @Override protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { final String valueStr = javaTypeDescriptor.unwrap( value, String.class, options ); - if ( valueStr == null || valueStr.trim().isEmpty() ) { + if ( StringHelper.isBlank( valueStr ) ) { st.setNull( name, getSqlType() ); } else { diff --git a/hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/SingleTableUpdateQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/SingleTableUpdateQueryTest.java new file mode 100644 index 0000000000..a25d979445 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclass/SingleTableUpdateQueryTest.java @@ -0,0 +1,101 @@ +/* + * 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 . + */ +package org.hibernate.test.joinedsubclass; + +import java.util.Map; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.junit.Test; + +import org.hamcrest.core.StringStartsWith; + +import static org.hamcrest.CoreMatchers.not; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertThat; + +/** + * @author Nathan Xu + */ +@TestForIssue( jiraKey = "HHH-14153" ) +public class SingleTableUpdateQueryTest extends BaseEntityManagerFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + public String[] getMappings() { + return new String[] { "org/hibernate/test/joinedsubclass/Person.hbm.xml" }; + } + + @Override + protected void addConfigOptions(Map options) { + sqlStatementInterceptor = new SQLStatementInterceptor( options ); + } + + @Test + public void testSingeTableUpdateWithoutOtherTableInvolvement() { + sqlStatementInterceptor.clear(); + + /* + * update + * JPerson + * set + * name=(name||?) + * where + * sex=? + */ + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "UPDATE Person p SET p.name = concat(p.name, :nameSuffix) WHERE p.sex = :sex" ) + .setParameter( "nameSuffix", "(f)" ) + .setParameter( "sex", 'f' ) + .executeUpdate(); + } ); + + sqlStatementInterceptor.getSqlQueries().forEach( + // currently SQLStatementInterceptor is not able to catch id table creation SQL + sql -> assertThat( sql, not( StringStartsWith.startsWith( "insert into " ) ) ) + ); + } + + @Test + public void testSingeTableUpdateWithOtherTableInvolvement() { + sqlStatementInterceptor.clear(); + + /* + * update + * JPerson + * set + * name=name + * where + * ( + * person_id + * ) IN ( + * select + * customer0_.person_id as person_id + * from + * JManager customer0_ + * inner join + * JPerson customer0_1_ + * on customer0_.person_id=customer0_1_.person_id + * ) + */ + + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createQuery( "UPDATE Customer SET name = name" ) // 'name' is from 'Person' parent entity + .executeUpdate(); + } ); + + sqlStatementInterceptor.getSqlQueries().forEach( + // currently SQLStatementInterceptor is not able to catch id table creation SQL + sql -> assertThat( sql, not( StringStartsWith.startsWith( "insert into " ) ) ) + ); + } + +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/tool/schema/scripts/MultiLineImportExtractorTest.java b/hibernate-core/src/test/java/org/hibernate/test/tool/schema/scripts/MultiLineImportExtractorTest.java index 205284e2ee..e64bd59e43 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/tool/schema/scripts/MultiLineImportExtractorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/tool/schema/scripts/MultiLineImportExtractorTest.java @@ -9,6 +9,7 @@ package org.hibernate.test.tool.schema.scripts; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.StringReader; import java.util.List; import org.hibernate.dialect.Dialect; @@ -46,7 +47,7 @@ public class MultiLineImportExtractorTest { assertThat( commands.get( 1 ), is( "INSERT INTO test_data VALUES (1, 'sample')" ) ); - assertThat( commands.get( 2 ), is( "DELETE FROM test_data" ) ); + assertThat( commands.get( 2 ), is( "DELETE FROM test_data" ) ); assertThat( commands.get( 3 ), startsWith( "INSERT INTO test_data VALUES (2," ) ); assertThat( commands.get( 3 ), containsString( "-- line 2" ) ); @@ -54,8 +55,16 @@ public class MultiLineImportExtractorTest { assertThat( commands.get( 4 ), startsWith( "INSERT INTO test_data VALUES (3" ) ); assertThat( commands.get( 4 ), not( containsString( "third record" ) ) ); - assertThat( commands.get( 5 ), startsWith( "INSERT INTO test_data" + System.lineSeparator() + "VALUES" ) ); + assertThat( commands.get( 5 ).replace( "\t", "" ), is( "INSERT INTO test_data VALUES ( 4 , NULL )" ) ); } } } + + @Test + public void testExtractionFromEmptyScript() { + StringReader reader = new StringReader( "" ); + final List commands = extractor.extractCommands( reader, Dialect.getDialect() ); + assertThat( commands, notNullValue() ); + assertThat( commands.size(), is( 0 ) ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/util/StringHelperTest.java b/hibernate-core/src/test/java/org/hibernate/test/util/StringHelperTest.java index 545a638753..8e18cc04fd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/util/StringHelperTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/util/StringHelperTest.java @@ -18,6 +18,7 @@ import org.hibernate.internal.util.StringHelper; import org.hibernate.testing.junit4.BaseUnitTestCase; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -44,6 +45,18 @@ public class StringHelperTest extends BaseUnitTestCase { assertEquals( "internal.util.StringHelper", StringHelper.partiallyUnqualify( STRING_HELPER_FQN, BASE_PACKAGE ) ); } + @Test + public void testIsBlank() { + assertFalse( StringHelper.isBlank( "A" ) ); + assertFalse( StringHelper.isBlank( " a" ) ); + assertFalse( StringHelper.isBlank( "a " ) ); + assertFalse( StringHelper.isBlank( "a\t" ) ); + assertTrue( StringHelper.isBlank( "\t\n" ) ); + assertTrue( StringHelper.isBlank( null ) ); + assertTrue( StringHelper.isBlank( "" ) ); + assertTrue( StringHelper.isBlank( " " ) ); + } + @Test public void testBasePackageCollapsing() { assertNull( StringHelper.collapseQualifierBase( null, BASE_PACKAGE ) );