序
本文主要研究一下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')
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。