diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index c945057f00b2..109d37d56a3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -203,8 +203,8 @@ public interface SharedSessionContract extends QueryProducer, AutoCloseable, Ser /** * Set the session-level JDBC batch size. Override the - * {@linkplain org.hibernate.boot.spi.SessionFactoryOptions#getJdbcBatchSize() factory-level} - * JDBC batch size controlled by the configuration property + * {@linkplain org.hibernate.boot.spi.SessionFactoryOptions#getJdbcBatchSize + * factory-level} JDBC batch size controlled by the configuration property * {@value org.hibernate.cfg.AvailableSettings#STATEMENT_BATCH_SIZE}. * * @param jdbcBatchSize the new session-level JDBC batch size diff --git a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java index c36019afe6ef..ff107c212672 100644 --- a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java +++ b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java @@ -60,6 +60,15 @@ *
  • when an exception is thrown by a stateless session, the current * transaction is not automatically marked for rollback. * + *

    + * Since version 7, the configuration property + * {@value org.hibernate.cfg.BatchSettings#STATEMENT_BATCH_SIZE} has no effect + * on a stateless session. Automatic batching may be enabled by explicitly + * {@linkplain #setJdbcBatchSize setting the batch size}. However, automatic + * batching has the side effect of delaying execution of the batched operation, + * thus undermining the synchronous nature of operations performed through a + * stateless session. A preferred approach is to explicitly batch operations via + * {@link #insertMultiple}, {@link #updateMultiple}, or {@link #deleteMultiple}. * * @author Gavin King */ @@ -85,6 +94,16 @@ public interface StatelessSession extends SharedSessionContract { */ Object insert(Object entity); + /** + * Insert multiple records. + * + * @param entities a list of transient instances to be inserted + * + * @since 7.0 + */ + @Incubating + void insertMultiple(List entities); + /** * Insert a record. *

    @@ -108,6 +127,16 @@ public interface StatelessSession extends SharedSessionContract { */ void update(Object entity); + /** + * Update multiple records. + * + * @param entities a list of detached instances to be updated + * + * @since 7.0 + */ + @Incubating + void updateMultiple(List entities); + /** * Update a record. *

    @@ -129,6 +158,16 @@ public interface StatelessSession extends SharedSessionContract { */ void delete(Object entity); + /** + * Delete multiple records. + * + * @param entities a list of detached instances to be deleted + * + * @since 7.0 + */ + @Incubating + void deleteMultiple(List entities); + /** * Delete a record. *

    @@ -164,6 +203,19 @@ public interface StatelessSession extends SharedSessionContract { @Incubating void upsert(Object entity); + /** + * Perform an upsert, that is, to insert the record if it does + * not exist, or update the record if it already exists, for + * each given record. + * + * @param entities a list of detached instances and new + * instances with assigned identifiers + * + * @since 7.0 + */ + @Incubating + void upsertMultiple(List entities); + /** * Use a SQL {@code merge into} statement to perform an upsert. * diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index fc4f024dc86f..db36fa91c2e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -1402,18 +1402,19 @@ public ExceptionConverter getExceptionConverter() { return exceptionConverter; } + @Override public Integer getJdbcBatchSize() { return jdbcBatchSize; } @Override - public EventManager getEventManager() { - return fastSessionServices.getEventManager(); + public void setJdbcBatchSize(Integer jdbcBatchSize) { + this.jdbcBatchSize = jdbcBatchSize; } @Override - public void setJdbcBatchSize(Integer jdbcBatchSize) { - this.jdbcBatchSize = jdbcBatchSize; + public EventManager getEventManager() { + return fastSessionServices.getEventManager(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java index 8fea386caf84..377dbe06ac8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -105,6 +105,7 @@ public StatelessSessionImpl(SessionFactoryImpl factory, SessionCreationOptions o temporaryPersistenceContext = new StatefulPersistenceContext( this ); influencers = new LoadQueryInfluencers( getFactory() ); setUpMultitenancy( factory, influencers ); + setJdbcBatchSize( 0 ); } @Override @@ -119,6 +120,20 @@ public Object insert(Object entity) { return insert( null, entity ); } + @Override + public void insertMultiple(List entities) { + final Integer batchSize = getJdbcBatchSize(); + setJdbcBatchSize( entities.size() ); + try { + for ( Object entity : entities ) { + insert( null, entity ); + } + } + finally { + setJdbcBatchSize( batchSize ); + } + } + @Override public Object insert(String entityName, Object entity) { checkOpen(); @@ -180,6 +195,20 @@ public void delete(Object entity) { delete( null, entity ); } + @Override + public void deleteMultiple(List entities) { + final Integer batchSize = getJdbcBatchSize(); + setJdbcBatchSize( entities.size() ); + try { + for ( Object entity : entities ) { + delete( null, entity ); + } + } + finally { + setJdbcBatchSize( batchSize ); + } + } + @Override public void delete(String entityName, Object entity) { checkOpen(); @@ -215,8 +244,17 @@ public void update(Object entity) { } @Override - public void upsert(Object entity) { - upsert( null, entity ); + public void updateMultiple(List entities) { + final Integer batchSize = getJdbcBatchSize(); + setJdbcBatchSize( entities.size() ); + try { + for ( Object entity : entities ) { + update( null, entity ); + } + } + finally { + setJdbcBatchSize( batchSize ); + } } @Override @@ -257,6 +295,25 @@ public void update(String entityName, Object entity) { } } + @Override + public void upsert(Object entity) { + upsert( null, entity ); + } + + @Override + public void upsertMultiple(List entities) { + final Integer batchSize = getJdbcBatchSize(); + setJdbcBatchSize( entities.size() ); + try { + for ( Object entity : entities ) { + upsert( null, entity ); + } + } + finally { + setJdbcBatchSize( batchSize ); + } + } + @Override public void upsert(String entityName, Object entity) { checkOpen(); diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionOwner.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionOwner.java index 249d62624d62..fd0f82095fa4 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionOwner.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionOwner.java @@ -12,7 +12,7 @@ /** * Contract for something that controls a {@link JdbcSessionContext}. *

    - * The term "JDBC session" is taken from the SQL specification which + * The term JDBC session is taken from the SQL specification which * calls a connection and its associated transaction context a "session". * * @apiNote The name comes from the design idea of a {@code JdbcSession} @@ -28,9 +28,9 @@ public interface JdbcSessionOwner { JdbcConnectionAccess getJdbcConnectionAccess(); /** - * Obtain the builder for TransactionCoordinator instances + * Obtain the {@link TransactionCoordinator}. * - * @return The TransactionCoordinatorBuilder + * @return The {@code TransactionCoordinator} */ TransactionCoordinator getTransactionCoordinator(); @@ -43,7 +43,7 @@ public interface JdbcSessionOwner { void startTransactionBoundary(); /** - * A after-begin callback from the coordinator to its owner. + * An after-begin callback from the coordinator to its owner. */ void afterTransactionBegin(); @@ -63,8 +63,8 @@ public interface JdbcSessionOwner { void flushBeforeTransactionCompletion(); /** - * Get the Session-level JDBC batch size. - * @return Session-level JDBC batch size + * Get the session-level JDBC batch size. + * @return session-level JDBC batch size * * @since 5.2 */ diff --git a/migration-guide.adoc b/migration-guide.adoc index 530c331ddbbc..71dd79038c6a 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -340,6 +340,14 @@ must be explicitly set to true. The signature of the `Configurable#configure` method changed from accepting just a `ServiceRegistry` instance to the new `GeneratorCreationContext` interface, which exposes a lot more useful information when configuring the generator itself. The old signature has been deprecated for removal, so you should migrate any custom `Configurable` generator implementation to the new one. +[[stateless-session-jdbc-batching]] +== JDBC batching with `StatelessSession` + +Automatic JDBC batching has the side effect of delaying the execution of the batched operation, and this undermines the synchronous nature of operations performed through a stateless session. +In Hibernate 7, the configuration property `hibernate.jdbc.batch_size` now has no effect on a stateless session. +Automatic batching may be enabled by explicitly calling `setJdbcBatchSize()`. +However, the preferred approach is to explicitly batch operations via `insertMultiple()`, `updateMultiple()`, or `deleteMultiple()`. + [[hbm-transform]] == hbm.xml Transformation