聊聊jpa的batch操作的实现

codecraft

本文主要研究一下jpa的batch操作的实现

save方法

SessionImpl.persist

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/internal/SessionImpl.java

    @Override
    public void persist(String entityName, Object object) throws HibernateException {
        firePersist( new PersistEvent( entityName, object, this ) );
    }

    private void firePersist(PersistEvent event) {
        errorIfClosed();
        checkTransactionSynchStatus();
        checkNoUnresolvedActionsBeforeOperation();
        for ( PersistEventListener listener : listeners( EventType.PERSIST ) ) {
            listener.onPersist( event );
        }
        checkNoUnresolvedActionsAfterOperation();
    }
触发了persist事件

DefaultPersistEventListener.onPersist

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/event/internal/DefaultPersistEventListener.java

    /**
     * Handle the given create event.
     *
     * @param event The create event to be handled.
     *
     * @throws HibernateException
     */
    public void onPersist(PersistEvent event) throws HibernateException {
        onPersist( event, new IdentityHashMap( 10 ) );
    }

    /**
     * Handle the given create event.
     *
     * @param event The create event to be handled.
     *
     * @throws HibernateException
     */
    public void onPersist(PersistEvent event, Map createCache) throws HibernateException {
        final SessionImplementor source = event.getSession();
        final Object object = event.getObject();

        final Object entity;
        if ( object instanceof HibernateProxy ) {
            LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer();
            if ( li.isUninitialized() ) {
                if ( li.getSession() == source ) {
                    return; //NOTE EARLY EXIT!
                }
                else {
                    throw new PersistentObjectException( "uninitialized proxy passed to persist()" );
                }
            }
            entity = li.getImplementation();
        }
        else {
            entity = object;
        }

        final String entityName;
        if ( event.getEntityName() != null ) {
            entityName = event.getEntityName();
        }
        else {
            entityName = source.bestGuessEntityName( entity );
            event.setEntityName( entityName );
        }

        final EntityEntry entityEntry = source.getPersistenceContext().getEntry( entity );
        EntityState entityState = getEntityState( entity, entityName, entityEntry, source );
        if ( entityState == EntityState.DETACHED ) {
            // JPA 2, in its version of a "foreign generated", allows the id attribute value
            // to be manually set by the user, even though this manual value is irrelevant.
            // The issue is that this causes problems with the Hibernate unsaved-value strategy
            // which comes into play here in determining detached/transient state.
            //
            // Detect if we have this situation and if so null out the id value and calculate the
            // entity state again.

            // NOTE: entityEntry must be null to get here, so we cannot use any of its values
            EntityPersister persister = source.getFactory().getEntityPersister( entityName );
            if ( ForeignGenerator.class.isInstance( persister.getIdentifierGenerator() ) ) {
                if ( LOG.isDebugEnabled() && persister.getIdentifier( entity, source ) != null ) {
                    LOG.debug( "Resetting entity id attribute to null for foreign generator" );
                }
                persister.setIdentifier( entity, null, source );
                entityState = getEntityState( entity, entityName, entityEntry, source );
            }
        }

        switch ( entityState ) {
            case DETACHED: {
                throw new PersistentObjectException(
                        "detached entity passed to persist: " +
                                getLoggableName( event.getEntityName(), entity )
                );
            }
            case PERSISTENT: {
                entityIsPersistent( event, createCache );
                break;
            }
            case TRANSIENT: {
                entityIsTransient( event, createCache );
                break;
            }
            case DELETED: {
                entityEntry.setStatus( Status.MANAGED );
                entityEntry.setDeletedState( null );
                event.getSession().getActionQueue().unScheduleDeletion( entityEntry, event.getObject() );
                entityIsDeleted( event, createCache );
                break;
            }
            default: {
                throw new ObjectDeletedException(
                        "deleted entity passed to persist",
                        null,
                        getLoggableName( event.getEntityName(), entity )
                );
            }
        }

    }
调用entityIsTransient方法

entityIsTransient

    /**
     * Handle the given create event.
     *
     * @param event The save event to be handled.
     * @param createCache The copy cache of entity instance to merge/copy instance.
     */
    @SuppressWarnings({"unchecked"})
    protected void entityIsTransient(PersistEvent event, Map createCache) {
        LOG.trace( "Saving transient instance" );

        final EventSource source = event.getSession();
        final Object entity = source.getPersistenceContext().unproxy( event.getObject() );

        if ( createCache.put( entity, entity ) == null ) {
            saveWithGeneratedId( entity, event.getEntityName(), createCache, source, false );
        }
    }

AbstractSaveEventListener.saveWithGeneratedId

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/event/internal/AbstractSaveEventListener.java

    /**
     * Prepares the save call using a newly generated id.
     *
     * @param entity The entity to be saved
     * @param entityName The entity-name for the entity to be saved
     * @param anything Generally cascade-specific information.
     * @param source The session which is the source of this save event.
     * @param requiresImmediateIdAccess does the event context require
     * access to the identifier immediately after execution of this method (if
     * not, post-insert style id generators may be postponed if we are outside
     * a transaction).
     *
     * @return The id used to save the entity; may be null depending on the
     *         type of id generator used and the requiresImmediateIdAccess value
     */
    protected Serializable saveWithGeneratedId(
            Object entity,
            String entityName,
            Object anything,
            EventSource source,
            boolean requiresImmediateIdAccess) {
        EntityPersister persister = source.getEntityPersister( entityName, entity );
        Serializable generatedId = persister.getIdentifierGenerator().generate( source, entity );
        if ( generatedId == null ) {
            throw new IdentifierGenerationException( "null id generated for:" + entity.getClass() );
        }
        else if ( generatedId == IdentifierGeneratorHelper.SHORT_CIRCUIT_INDICATOR ) {
            return source.getIdentifier( entity );
        }
        else if ( generatedId == IdentifierGeneratorHelper.POST_INSERT_INDICATOR ) {
            return performSave( entity, null, persister, true, anything, source, requiresImmediateIdAccess );
        }
        else {
            // TODO: define toString()s for generators
            if ( LOG.isDebugEnabled() ) {
                LOG.debugf(
                        "Generated identifier: %s, using strategy: %s",
                        persister.getIdentifierType().toLoggableString( generatedId, source.getFactory() ),
                        persister.getIdentifierGenerator().getClass().getName()
                );
            }

            return performSave( entity, generatedId, persister, false, anything, source, true );
        }
    }
最后performSave是调用performSaveOrReplicate

performSaveOrReplicate

    /**
     * Performs all the actual work needed to save an entity (well to get the save moved to
     * the execution queue).
     *
     * @param entity The entity to be saved
     * @param key The id to be used for saving the entity (or null, in the case of identity columns)
     * @param persister The entity's persister instance.
     * @param useIdentityColumn Should an identity column be used for id generation?
     * @param anything Generally cascade-specific information.
     * @param source The session which is the source of the current event.
     * @param requiresImmediateIdAccess Is access to the identifier required immediately
     * after the completion of the save?  persist(), for example, does not require this...
     *
     * @return The id used to save the entity; may be null depending on the
     *         type of id generator used and the requiresImmediateIdAccess value
     */
    protected Serializable performSaveOrReplicate(
            Object entity,
            EntityKey key,
            EntityPersister persister,
            boolean useIdentityColumn,
            Object anything,
            EventSource source,
            boolean requiresImmediateIdAccess) {

        Serializable id = key == null ? null : key.getIdentifier();

        boolean inTxn = source.isTransactionInProgress();
        boolean shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess;

        // Put a placeholder in entries, so we don't recurse back and try to save() the
        // same object again. QUESTION: should this be done before onSave() is called?
        // likewise, should it be done before onUpdate()?
        EntityEntry original = source.getPersistenceContext().addEntry(
                entity,
                Status.SAVING,
                null,
                null,
                id,
                null,
                LockMode.WRITE,
                useIdentityColumn,
                persister,
                false,
                false
        );

        cascadeBeforeSave( source, persister, entity, anything );

        Object[] values = persister.getPropertyValuesToInsert( entity, getMergeMap( anything ), source );
        Type[] types = persister.getPropertyTypes();

        boolean substitute = substituteValuesIfNecessary( entity, id, values, persister, source );

        if ( persister.hasCollections() ) {
            substitute = substitute || visitCollectionsBeforeSave( entity, id, values, types, source );
        }

        if ( substitute ) {
            persister.setPropertyValues( entity, values );
        }

        TypeHelper.deepCopy(
                values,
                types,
                persister.getPropertyUpdateability(),
                values,
                source
        );

        AbstractEntityInsertAction insert = addInsertAction(
                values, id, entity, persister, useIdentityColumn, source, shouldDelayIdentityInserts
        );

        // postpone initializing id in case the insert has non-nullable transient dependencies
        // that are not resolved until cascadeAfterSave() is executed
        cascadeAfterSave( source, persister, entity, anything );
        if ( useIdentityColumn && insert.isEarlyInsert() ) {
            if ( !EntityIdentityInsertAction.class.isInstance( insert ) ) {
                throw new IllegalStateException(
                        "Insert should be using an identity column, but action is of unexpected type: " +
                                insert.getClass().getName()
                );
            }
            id = ((EntityIdentityInsertAction) insert).getGeneratedId();

            insert.handleNaturalIdPostSaveNotifications( id );
        }

        markInterceptorDirty( entity, persister, source );

        EntityEntry newEntry = source.getPersistenceContext().getEntry( entity );

        if ( newEntry != original ) {
            EntityEntryExtraState extraState = newEntry.getExtraState( EntityEntryExtraState.class );
            if ( extraState == null ) {
                newEntry.addExtraState( original.getExtraState( EntityEntryExtraState.class ) );
            }
        }

        return id;
    }
这里调用了addInsertAction

addInsertAction

    private AbstractEntityInsertAction addInsertAction(
            Object[] values,
            Serializable id,
            Object entity,
            EntityPersister persister,
            boolean useIdentityColumn,
            EventSource source,
            boolean shouldDelayIdentityInserts) {
        if ( useIdentityColumn ) {
            EntityIdentityInsertAction insert = new EntityIdentityInsertAction(
                    values, entity, persister, isVersionIncrementDisabled(), source, shouldDelayIdentityInserts
            );
            source.getActionQueue().addAction( insert );
            return insert;
        }
        else {
            Object version = Versioning.getVersion( values, persister );
            EntityInsertAction insert = new EntityInsertAction(
                    id, values, entity, version, persister, isVersionIncrementDisabled(), source
            );
            source.getActionQueue().addAction( insert );
            return insert;
        }
    }

ActionQueue.addAction

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/engine/spi/ActionQueue.java

    /**
     * Adds an entity insert action
     *
     * @param action The action representing the entity insertion
     */
    public void addAction(EntityInsertAction action) {
        LOG.tracev( "Adding an EntityInsertAction for [{0}] object", action.getEntityName() );
        addInsertAction( action );
    }

    private void addInsertAction(AbstractEntityInsertAction insert) {
        if ( insert.isEarlyInsert() ) {
            // For early inserts, must execute inserts before finding non-nullable transient entities.
            // TODO: find out why this is necessary
            LOG.tracev( "Executing inserts before finding non-nullable transient entities for early insert: [{0}]", insert );
            executeInserts();
        }
        NonNullableTransientDependencies nonNullableTransientDependencies = insert.findNonNullableTransientEntities();
        if ( nonNullableTransientDependencies == null ) {
            LOG.tracev( "Adding insert with no non-nullable, transient entities: [{0}]", insert );
            addResolvedEntityInsertAction( insert );
        }
        else {
            if ( LOG.isTraceEnabled() ) {
                LOG.tracev( "Adding insert with non-nullable, transient entities; insert=[{0}], dependencies=[{1}]", insert,
                            nonNullableTransientDependencies.toLoggableString( insert.getSession() ) );
            }
            if( unresolvedInsertions == null ) {
                unresolvedInsertions = new UnresolvedEntityInsertActions();
            }
            unresolvedInsertions.addUnresolvedEntityInsertAction( insert, nonNullableTransientDependencies );
        }
    }

    private void addResolvedEntityInsertAction(AbstractEntityInsertAction insert) {
        if ( insert.isEarlyInsert() ) {
            LOG.trace( "Executing insertions before resolved early-insert" );
            executeInserts();
            LOG.debug( "Executing identity-insert immediately" );
            execute( insert );
        }
        else {
            LOG.trace( "Adding resolved non-early insert action." );
            addAction( AbstractEntityInsertAction.class, insert );
        }
        insert.makeEntityManaged();
        if( unresolvedInsertions != null ) {
            for (AbstractEntityInsertAction resolvedAction : unresolvedInsertions.resolveDependentActions(insert.getInstance(), session)) {
                addResolvedEntityInsertAction(resolvedAction);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private <T extends Executable & Comparable & Serializable> void addAction(Class<T> executableClass, T action) {
        EXECUTABLE_LISTS_MAP.get( executableClass ).getOrInit( this ).add( action );
    }
EXECUTABLE_LISTS_MAP.get( executableClass )即EXECUTABLE_LISTS_MAP.get(AbstractEntityInsertAction.class)-->返回ActionQueue
private static abstract class ListProvider<T extends Executable & Comparable & Serializable> {
        abstract ExecutableList<T> get(ActionQueue instance);
        abstract ExecutableList<T> init(ActionQueue instance);
        ExecutableList<T> getOrInit( ActionQueue instance ) {
            ExecutableList<T> list = get( instance );
            if ( list == null ) {
                list = init( instance );
            }
            return list;
        }
    }
添加到ExecutableList

flush方法

SessionImpl.flush

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/internal/SessionImpl.java

@Override
    public void flush() throws HibernateException {
        errorIfClosed();
        checkTransactionSynchStatus();
        if ( persistenceContext.getCascadeLevel() > 0 ) {
            throw new HibernateException( "Flush during cascade is dangerous" );
        }
        FlushEvent flushEvent = new FlushEvent( this );
        for ( FlushEventListener listener : listeners( EventType.FLUSH ) ) {
            listener.onFlush( flushEvent );
        }
        delayedAfterCompletion();
    }

DefaultFlushEventListener.onFlush

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/event/internal/DefaultFlushEventListener.java

/** Handle the given flush event.
     *
     * @param event The flush event to be handled.
     * @throws HibernateException
     */
    public void onFlush(FlushEvent event) throws HibernateException {
        final EventSource source = event.getSession();
        final PersistenceContext persistenceContext = source.getPersistenceContext();

        if ( persistenceContext.getNumberOfManagedEntities() > 0 ||
                persistenceContext.getCollectionEntries().size() > 0 ) {

            try {
                source.getEventListenerManager().flushStart();

                flushEverythingToExecutions( event );
                performExecutions( source );
                postFlush( source );
            }
            finally {
                source.getEventListenerManager().flushEnd(
                        event.getNumberOfEntitiesProcessed(),
                        event.getNumberOfCollectionsProcessed()
                );
            }

            postPostFlush( source );

            if ( source.getFactory().getStatistics().isStatisticsEnabled() ) {
                source.getFactory().getStatisticsImplementor().flush();
            }
        }
    }
这里调用了performExecutions

AbstractFlushingEventListener.performExecutions

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/event/internal/AbstractFlushingEventListener.java

    /**
     * Execute all SQL (and second-level cache updates) in a special order so that foreign-key constraints cannot
     * be violated: <ol>
     * <li> Inserts, in the order they were performed
     * <li> Updates
     * <li> Deletion of collection elements
     * <li> Insertion of collection elements
     * <li> Deletes, in the order they were performed
     * </ol>
     *
     * @param session The session being flushed
     */
    protected void performExecutions(EventSource session) {
        LOG.trace( "Executing flush" );

        // IMPL NOTE : here we alter the flushing flag of the persistence context to allow
        //        during-flush callbacks more leniency in regards to initializing proxies and
        //        lazy collections during their processing.
        // For more information, see HHH-2763
        try {
            session.getJdbcCoordinator().flushBeginning();
            session.getPersistenceContext().setFlushing( true );
            // we need to lock the collection caches before executing entity inserts/updates in order to
            // account for bi-directional associations
            session.getActionQueue().prepareActions();
            session.getActionQueue().executeActions();
        }
        finally {
            session.getPersistenceContext().setFlushing( false );
            session.getJdbcCoordinator().flushEnding();
        }
    }
这里调用了session.getActionQueue().executeActions();

ActionQueue.executeActions

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/engine/spi/ActionQueue.java

    /**
     * Perform all currently queued actions.
     * 
     * @throws HibernateException error executing queued actions.
     */
    public void executeActions() throws HibernateException {
        if ( hasUnresolvedEntityInsertActions() ) {
            throw new IllegalStateException( "About to execute actions, but there are unresolved entity insert actions." );
        }

        for ( ListProvider listProvider : EXECUTABLE_LISTS_MAP.values() ) {
            ExecutableList<?> l = listProvider.get( this );
            if ( l != null && !l.isEmpty() ) {
                executeActions( l );
            }
        }
    }

        /**
     * Perform {@link org.hibernate.action.spi.Executable#execute()} on each element of the list
     * 
     * @param list The list of Executable elements to be performed
     *
     * @throws HibernateException
     */
    private <E extends Executable & Comparable<?> & Serializable> void executeActions(ExecutableList<E> list) throws HibernateException {
        // todo : consider ways to improve the double iteration of Executables here:
        //        1) we explicitly iterate list here to perform Executable#execute()
        //        2) ExecutableList#getQuerySpaces also iterates the Executables to collect query spaces.
        try {
            for ( E e : list ) {
                try {
                    e.execute();
                }
                finally {
                    if( e.getBeforeTransactionCompletionProcess() != null ) {
                        if( beforeTransactionProcesses == null ) {
                            beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue( session );
                        }
                        beforeTransactionProcesses.register(e.getBeforeTransactionCompletionProcess());
                    }
                    if( e.getAfterTransactionCompletionProcess() != null ) {
                        if( afterTransactionProcesses == null ) {
                            afterTransactionProcesses = new AfterTransactionCompletionProcessQueue( session );
                        }
                        afterTransactionProcesses.register(e.getAfterTransactionCompletionProcess());
                    }
                }
            }
        }
        finally {
            if ( session.getFactory().getSessionFactoryOptions().isQueryCacheEnabled() ) {
                // Strictly speaking, only a subset of the list may have been processed if a RuntimeException occurs.
                // We still invalidate all spaces. I don't see this as a big deal - after all, RuntimeExceptions are
                // unexpected.
                Set<Serializable> propertySpaces = list.getQuerySpaces();
                invalidateSpaces( propertySpaces.toArray( new Serializable[propertySpaces.size()] ) );
            }
        }

        list.clear();
        session.getJdbcCoordinator().executeBatch();
    }
这里在for循环里头调用了e.execute();同时在循环之后,finally之后调用了session.getJdbcCoordinator().executeBatch();
正符合了jdbc statement的executeBatch的调用模式,可以预见e.execute()执行了addBatch的操作,同时在达到一个batch的时候会先调用executeBatch()

EntityInsertAction.execute

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/action/internal/EntityInsertAction.java

    @Override
    public void execute() throws HibernateException {
        nullifyTransientReferencesIfNotAlready();

        final EntityPersister persister = getPersister();
        final SessionImplementor session = getSession();
        final Object instance = getInstance();
        final Serializable id = getId();

        final boolean veto = preInsert();

        // Don't need to lock the cache here, since if someone
        // else inserted the same pk first, the insert would fail

        if ( !veto ) {
            
            persister.insert( id, getState(), instance, session );
            PersistenceContext persistenceContext = session.getPersistenceContext();
            final EntityEntry entry = persistenceContext.getEntry( instance );
            if ( entry == null ) {
                throw new AssertionFailure( "possible non-threadsafe access to session" );
            }
            
            entry.postInsert( getState() );
    
            if ( persister.hasInsertGeneratedProperties() ) {
                persister.processInsertGeneratedProperties( id, instance, getState(), session );
                if ( persister.isVersionPropertyGenerated() ) {
                    version = Versioning.getVersion( getState(), persister );
                }
                entry.postUpdate( instance, getState(), version );
            }

            persistenceContext.registerInsertedKey( persister, getId() );
        }

        final SessionFactoryImplementor factory = session.getFactory();

        if ( isCachePutEnabled( persister, session ) ) {
            final CacheEntry ce = persister.buildCacheEntry(
                    instance,
                    getState(),
                    version,
                    session
            );
            cacheEntry = persister.getCacheEntryStructure().structure( ce );
            final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy();
            final Object ck = cache.generateCacheKey( id, persister, factory, session.getTenantIdentifier() );

            final boolean put = cacheInsert( persister, ck );

            if ( put && factory.getStatistics().isStatisticsEnabled() ) {
                factory.getStatisticsImplementor().secondLevelCachePut( cache.getRegion().getName() );
            }
        }

        handleNaturalIdPostSaveNotifications( id );

        postInsert();

        if ( factory.getStatistics().isStatisticsEnabled() && !veto ) {
            factory.getStatisticsImplementor().insertEntity( getPersister().getEntityName() );
        }

        markExecuted();
    }
调用了persister的insert方法

AbstractEntityPersister.insert

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/persister/entity/AbstractEntityPersister.java

    public void insert(Serializable id, Object[] fields, Object object, SessionImplementor session) {
        // apply any pre-insert in-memory value generation
        preInsertInMemoryValueGeneration( fields, object, session );

        final int span = getTableSpan();
        if ( entityMetamodel.isDynamicInsert() ) {
            // For the case of dynamic-insert="true", we need to generate the INSERT SQL
            boolean[] notNull = getPropertiesToInsert( fields );
            for ( int j = 0; j < span; j++ ) {
                insert( id, fields, notNull, j, generateInsertString( notNull, j ), object, session );
            }
        }
        else {
            // For the case of dynamic-insert="false", use the static SQL
            for ( int j = 0; j < span; j++ ) {
                insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
            }
        }
    }

insert

    /**
     * Perform an SQL INSERT.
     * <p/>
     * This for is used for all non-root tables as well as the root table
     * in cases where the identifier value is known before the insert occurs.
     */
    protected void insert(
            final Serializable id,
            final Object[] fields,
            final boolean[] notNull,
            final int j,
            final String sql,
            final Object object,
            final SessionImplementor session) throws HibernateException {

        if ( isInverseTable( j ) ) {
            return;
        }

        //note: it is conceptually possible that a UserType could map null to
        //      a non-null value, so the following is arguable:
        if ( isNullableTable( j ) && isAllNull( fields, j ) ) {
            return;
        }

        if ( LOG.isTraceEnabled() ) {
            LOG.tracev( "Inserting entity: {0}", MessageHelper.infoString( this, id, getFactory() ) );
            if ( j == 0 && isVersioned() ) {
                LOG.tracev( "Version: {0}", Versioning.getVersion( fields, this ) );
            }
        }

        // TODO : shouldn't inserts be Expectations.NONE?
        final Expectation expectation = Expectations.appropriateExpectation( insertResultCheckStyles[j] );
        // we can't batch joined inserts, *especially* not if it is an identity insert;
        // nor can we batch statements where the expectation is based on an output param
        final boolean useBatch = j == 0 && expectation.canBeBatched();
        if ( useBatch && inserBatchKey == null ) {
            inserBatchKey = new BasicBatchKey(
                    getEntityName() + "#INSERT",
                    expectation
            );
        }
        final boolean callable = isInsertCallable( j );

        try {
            // Render the SQL query
            final PreparedStatement insert;
            if ( useBatch ) {
                insert = session
                        .getJdbcCoordinator()
                        .getBatch( inserBatchKey )
                        .getBatchStatement( sql, callable );
            }
            else {
                insert = session
                        .getJdbcCoordinator()
                        .getStatementPreparer()
                        .prepareStatement( sql, callable );
            }

            try {
                int index = 1;
                index += expectation.prepare( insert );

                // Write the values of fields onto the prepared statement - we MUST use the state at the time the
                // insert was issued (cos of foreign key constraints). Not necessarily the object's current state

                dehydrate( id, fields, null, notNull, propertyColumnInsertable, j, insert, session, index, false );

                if ( useBatch ) {
                    session.getJdbcCoordinator().getBatch( inserBatchKey ).addToBatch();
                }
                else {
                    expectation.verifyOutcome(
                            session.getJdbcCoordinator()
                                    .getResultSetReturn()
                                    .executeUpdate( insert ), insert, -1
                    );
                }

            }
            catch (SQLException e) {
                if ( useBatch ) {
                    session.getJdbcCoordinator().abortBatch();
                }
                throw e;
            }
            finally {
                if ( !useBatch ) {
                    session.getJdbcCoordinator().getResourceRegistry().release( insert );
                    session.getJdbcCoordinator().afterStatementExecution();
                }
            }
        }
        catch (SQLException e) {
            throw getFactory().getSQLExceptionHelper().convert(
                    e,
                    "could not insert: " + MessageHelper.infoString( this ),
                    sql
            );
        }

    }
useBatch为true,调用session.getJdbcCoordinator().getBatch( inserBatchKey ).addToBatch()
这里的insertBatchKey为com.example.domain.DemoUser#INSERT

JdbcCoordinatorImpl.getBatch

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java

    @Override
    public Batch getBatch(BatchKey key) {
        if ( currentBatch != null ) {
            if ( currentBatch.getKey().equals( key ) ) {
                return currentBatch;
            }
            else {
                currentBatch.execute();
                currentBatch.release();
            }
        }
        currentBatch = batchBuilder().buildBatch( key, this );
        return currentBatch;
    }

BatchingBatch.addToBatch

hibernate-core-5.0.12.Final-sources.jar!/org/hibernate/engine/jdbc/batch/internal/BatchingBatch.java


    @Override
    public void addToBatch() {
        try {
            currentStatement.addBatch();
        }
        catch ( SQLException e ) {
            LOG.debugf( "SQLException escaped proxy", e );
            throw sqlExceptionHelper().convert( e, "could not perform addBatch", currentStatementSql );
        }
        statementPosition++;
        if ( statementPosition >= getKey().getBatchedStatementCount() ) {
            batchPosition++;
            if ( batchPosition == batchSize ) {
                notifyObserversImplicitExecution();
                performExecution();
                batchPosition = 0;
                batchExecuted = true;
            }
            statementPosition = 0;
        }
    }
这里在批量够的话,会执行performExecution

performExecution

private void performExecution() {
        LOG.debugf( "Executing batch size: %s", batchPosition );
        try {
            for ( Map.Entry<String,PreparedStatement> entry : getStatements().entrySet() ) {
                try {
                    final PreparedStatement statement = entry.getValue();
                    final int[] rowCounts;
                    try {
                        getJdbcCoordinator().getJdbcSessionOwner().getJdbcSessionContext().getObserver().jdbcExecuteBatchStart();
                        rowCounts = statement.executeBatch();
                    }
                    finally {
                        getJdbcCoordinator().getJdbcSessionOwner().getJdbcSessionContext().getObserver().jdbcExecuteBatchEnd();
                    }
                    checkRowCounts( rowCounts, statement );
                }
                catch ( SQLException e ) {
                    abortBatch();
                    throw sqlExceptionHelper().convert( e, "could not execute batch", entry.getKey() );
                }
            }
        }
        catch ( RuntimeException re ) {
            LOG.unableToExecuteBatch( re.getMessage() );
            throw re;
        }
        finally {
            batchPosition = 0;
        }
    }
可以看到这里调用了statement.executeBatch()

小结

  • jpa的save方法首先将数据添加造action queue里头
  • 在flush的时候,再通过insert action构造statement的batch操作,然后到达一个批量的时候才perform
  • jpa的batch操作也是在jdbc的statment的addBatch和executeBatch上的封装,具体可以详见ActionQueue.executeActions

具体模式如下

    public void jdbcBatchOperationTemplate(List<Employee> data){
        String sql = "insert into employee (name, city, phone) values (?, ?, ?)";

        Connection conn = null;
        PreparedStatement pstmt = null;

        final int batchSize = 1000;
        int count = 0;

        try{
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement(sql);

            for (Employee item: data) {
                pstmt.setString(1,item.getName());
                pstmt.setString(2,item.getCity());
                pstmt.setString(3,item.getPhone());

                //添加到batch
                pstmt.addBatch();

                //小批量提交,避免OOM
                if(++count % batchSize == 0) {
                    pstmt.executeBatch();
                }
            }

            pstmt.executeBatch(); //提交剩余的数据

        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            DbUtils.closeQuietly(pstmt);
            DbUtils.closeQuietly(conn);
        }
    }
唯一的区别是jpa是在save的时候是将所有数据都提交到action queue,最后再flush的时候触发类似上面的addBatch和executeBatch操作。

对于使用@GeneratedValue(strategy = GenerationType.AUTO),在每次save添加到action queue之前都会调用数据库获取id。也就是假设要批量insert1000条数据,则save放到action queue之前会调用1000次获取他们的id,然后最后flush的时候,再将action queue的1000条数据,分批batch执行,相当于上面模板的方法data参数是1000个有id的Employee对象。

    select
        nextval ('hibernate_sequence')

doc

阅读 4k

code-craft
spring boot , docker and so on 欢迎关注微信公众号: geek_luandun

当一个代码的工匠回首往事时,不因虚度年华而悔恨,也不因碌碌无为而羞愧,这样,当他老的时候,可以很自豪告诉世人,我曾经将代码注入生命去打造互联网的浪潮之巅,那是个很疯狂的时代,我在一波波的浪潮上留下了或重如泰山或轻如鸿毛的几笔。

11.3k 声望
1.1k 粉丝
0 条评论

当一个代码的工匠回首往事时,不因虚度年华而悔恨,也不因碌碌无为而羞愧,这样,当他老的时候,可以很自豪告诉世人,我曾经将代码注入生命去打造互联网的浪潮之巅,那是个很疯狂的时代,我在一波波的浪潮上留下了或重如泰山或轻如鸿毛的几笔。

11.3k 声望
1.1k 粉丝
宣传栏