经过hibernate source code 阅读一我们可以看出,hibernate其实在builderSessionFactory就已经把SQL语句封装好了
经过代码跟踪我发现在类:org.hibernate.mapping.SimpleValue中定义了这样一个属性:private final List columns = new ArrayList();
一开始在下面这个方法中就吧XML中的节点元素解析之后添加到column集合里面来了,以供后面拼接SQL语句和其他操作使用
public void addColumn(Column column) {
if ( !columns.contains(column) ) columns.add(column);
column.setValue(this);
column.setTypeIndex( columns.size()-1 );
}
上面我们看到了hibernate是在何时拼接出SQL语句的,接下来我们看看hibernate是在何时执行SQL语句的:
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class SimpleTest{
public static void main(String[] args) {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();
Tparam param = new Tparam();
param.setFcode(655l);
param.setFremark("hibernate test");
session.save(param);
tx.commit();
}
}
难道是这句:session.save(param)?
我仔细看了一下代码,他这里面做了很多事情,我个人感觉就是持久化对象到缓存(map)中,并没有真正的将对象持久化到数据库
public Serializable save(Object obj) throws HibernateException {
return save(null, obj);
}
public Serializable save(String entityName, Object object) throws HibernateException {
return fireSave( new SaveOrUpdateEvent(entityName, object, this) );
}
private Serializable fireSave(SaveOrUpdateEvent event) {
errorIfClosed();
checkTransactionSynchStatus();
SaveOrUpdateEventListener[] saveEventListener = listeners.getSaveEventListeners();
for ( int i = 0; i < saveEventListener.length; i++ ) {
saveEventListener[i].onSaveOrUpdate(event);
}
return event.getResultId();
}
/**
* Handle the given update event.
*
* @param event The update event to be handled.
*/
public void onSaveOrUpdate(SaveOrUpdateEvent event) {
final SessionImplementor source = event.getSession();
final Object object = event.getObject();
final Serializable requestedId = event.getRequestedId();
if ( requestedId != null ) {
//assign the requested id to the proxy, *before*
//reassociating the proxy
if ( object instanceof HibernateProxy ) {
( ( HibernateProxy ) object ).getHibernateLazyInitializer().setIdentifier( requestedId );
}
}
if ( reassociateIfUninitializedProxy( object, source ) ) {
log.trace( "reassociated uninitialized proxy" );
// an uninitialized proxy, noop, don't even need to
// return an id, since it is never a save()
}
else {
//initialize properties of the event:
final Object entity = source.getPersistenceContext().unproxyAndReassociate( object );
event.setEntity( entity );
event.setEntry( source.getPersistenceContext().getEntry( entity ) );
//return the id in the event object
event.setResultId( performSaveOrUpdate( event ) );
}
}
protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event) {
// this implementation is supposed to tolerate incorrect unsaved-value
// mappings, for the purpose of backward-compatibility
EntityEntry entry = event.getSession().getPersistenceContext().getEntry( event.getEntity() );
if ( entry!=null && entry.getStatus() != Status.DELETED ) {
return entityIsPersistent(event);
}
else {
return entityIsTransient(event);
}
}
protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event) {
// this implementation is supposed to tolerate incorrect unsaved-value
// mappings, for the purpose of backward-compatibility
EntityEntry entry = event.getSession().getPersistenceContext().getEntry( event.getEntity() );
if ( entry!=null && entry.getStatus() != Status.DELETED ) {
return entityIsPersistent(event);
}
else {
return entityIsTransient(event);
}
}
/**
* Save the transient instance, assigning the right identifier
*
* @param event The initiating event.
*
* @return The entity's identifier value after saving.
*/
protected Serializable saveWithGeneratedOrRequestedId(SaveOrUpdateEvent event) {
return saveWithGeneratedId(
event.getEntity(),
event.getEntityName(),
null,
event.getSession(),
true
);
}
/**
* 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 == IdentifierGeneratorFactory.SHORT_CIRCUIT_INDICATOR ) {
return source.getIdentifier( entity );
}
else if ( generatedId == IdentifierGeneratorFactory.POST_INSERT_INDICATOR ) {
return performSave( entity, null, persister, true, anything, source, requiresImmediateIdAccess );
}
else {
if ( log.isDebugEnabled() ) {
log.debug(
"generated identifier: " +
persister.getIdentifierType().toLoggableString( generatedId, source.getFactory() ) +
", using strategy: " +
persister.getIdentifierGenerator().getClass().getName()
//TODO: define toString()s for generators
);
}
return performSave( entity, generatedId, persister, false, anything, source, true );
}
}
/**
* Ppepares the save call by checking the session caches for a pre-existing
* entity and performing any lifecycle callbacks.
*
* @param entity The entity to be saved.
* @param id The id by which to save the entity.
* @param persister The entity's persister instance.
* @param useIdentityColumn Is an identity column being used?
* @param anything Generally cascade-specific information.
* @param source The session from which the event originated.
* @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 performSave(
Object entity,
Serializable id,
EntityPersister persister,
boolean useIdentityColumn,
Object anything,
EventSource source,
boolean requiresImmediateIdAccess) {
if ( log.isTraceEnabled() ) {
log.trace(
"saving " +
MessageHelper.infoString( persister, id, source.getFactory() )
);
}
EntityKey key;
if ( !useIdentityColumn ) {
key = new EntityKey( id, persister, source.getEntityMode() );
Object old = source.getPersistenceContext().getEntity( key );
if ( old != null ) {
if ( source.getPersistenceContext().getEntry( old ).getStatus() == Status.DELETED ) {
source.forceFlush( source.getPersistenceContext().getEntry( old ) );
}
else {
throw new NonUniqueObjectException( id, persister.getEntityName() );
}
}
persister.setIdentifier( entity, id, source.getEntityMode() );
}
else {
key = null;
}
if ( invokeSaveLifecycle( entity, persister, source ) ) {
return id; //EARLY EXIT
}
return performSaveOrReplicate(
entity,
key,
persister,
useIdentityColumn,
anything,
source,
requiresImmediateIdAccess
);
}
/**
* 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) {
validate( entity, persister, source );
Serializable id = key == null ? null : key.getIdentifier();
boolean inTxn = source.getJDBCContext().isTransactionInProgress();
boolean shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess;
if ( useIdentityColumn && !shouldDelayIdentityInserts ) {
log.trace( "executing insertions" );
source.getActionQueue().executeInserts();
}
// 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()?
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, source.getEntityMode() );
}
TypeFactory.deepCopy(
values,
types,
persister.getPropertyUpdateability(),
values,
source
);
new ForeignKeys.Nullifier( entity, false, useIdentityColumn, source )
.nullifyTransientReferences( values, types );
new Nullability( source ).checkNullability( values, persister, false );
if ( useIdentityColumn ) {
EntityIdentityInsertAction insert = new EntityIdentityInsertAction(
values, entity, persister, source, shouldDelayIdentityInserts
);
if ( !shouldDelayIdentityInserts ) {
log.debug( "executing identity-insert immediately" );
source.getActionQueue().execute( insert );
id = insert.getGeneratedId();
//now done in EntityIdentityInsertAction
//persister.setIdentifier( entity, id, source.getEntityMode() );
key = new EntityKey( id, persister, source.getEntityMode() );
source.getPersistenceContext().checkUniqueness( key, entity );
//source.getBatcher().executeBatch(); //found another way to ensure that all batched joined inserts have been executed
}
else {
log.debug( "delaying identity-insert due to no transaction in progress" );
source.getActionQueue().addAction( insert );
key = insert.getDelayedEntityKey();
}
}
Object version = Versioning.getVersion( values, persister );
source.getPersistenceContext().addEntity(
entity,
Status.MANAGED,
values,
key,
version,
LockMode.WRITE,
useIdentityColumn,
persister,
isVersionIncrementDisabled(),
false
);
//source.getPersistenceContext().removeNonExist( new EntityKey( id, persister, source.getEntityMode() ) );
if ( !useIdentityColumn ) {
source.getActionQueue().addAction(
new EntityInsertAction( id, values, entity, version, persister, source )
);
}
cascadeAfterSave( source, persister, entity, anything );
markInterceptorDirty( entity, persister, source );
return id;
}
/**
* Generates an appropriate EntityEntry instance and adds it
* to the event source's internal caches.
*/
public EntityEntry addEntry(
final Object entity,
final Status status,
final Object[] loadedState,
final Object rowId,
final Serializable id,
final Object version,
final LockMode lockMode,
final boolean existsInDatabase,
final EntityPersister persister,
final boolean disableVersionIncrement,
boolean lazyPropertiesAreUnfetched) {
EntityEntry e = new EntityEntry(
status,
loadedState,
rowId,
id,
version,
lockMode,
existsInDatabase,
persister,
session.getEntityMode(),
disableVersionIncrement,
lazyPropertiesAreUnfetched
);
entityEntries.put(entity, e);
setHasNonReadOnlyEnties(status);
return e;
}
通过以上代码我们可以看到最终返回了一个id,而这个id用来给实体持久化到数据库中的时候使用,除此之外我看到的就是持久化的操作
那既然save方法没有将对象持久化到数据库,那就剩下最后一步了tx.commit();我们看看他都做了些什么:
public void commit() throws HibernateException {
if (!begun) {
throw new TransactionException("Transaction not successfully started");
}
log.debug("commit");
if ( !transactionContext.isFlushModeNever() && callback ) {
transactionContext.managedFlush(); //if an exception occurs during flush, user must call rollback()
}
notifyLocalSynchsBeforeTransactionCompletion();
if ( callback ) {
jdbcContext.beforeTransactionCompletion( this );
}
try {
commitAndResetAutoCommit();
log.debug("committed JDBC Connection");
committed = true;
if ( callback ) {
jdbcContext.afterTransactionCompletion( true, this );
}
notifyLocalSynchsAfterTransactionCompletion( Status.STATUS_COMMITTED );
}
catch (SQLException e) {
log.error("JDBC commit failed", e);
commitFailed = true;
if ( callback ) {
jdbcContext.afterTransactionCompletion( false, this );
}
notifyLocalSynchsAfterTransactionCompletion( Status.STATUS_UNKNOWN );
throw new TransactionException("JDBC commit failed", e);
}
finally {
closeIfRequired();
}
}
public void managedFlush() {
if ( isClosed() ) {
log.trace( "skipping auto-flush due to session closed" );
return;
}
log.trace("automatically flushing session");
flush();
if ( childSessionsByEntityMode != null ) {
Iterator iter = childSessionsByEntityMode.values().iterator();
while ( iter.hasNext() ) {
( (Session) iter.next() ).flush();
}
}
}
public void flush() throws HibernateException {
errorIfClosed();
checkTransactionSynchStatus();
if ( persistenceContext.getCascadeLevel() > 0 ) {
throw new HibernateException("Flush during cascade is dangerous");
}
FlushEventListener[] flushEventListener = listeners.getFlushEventListeners();
for ( int i = 0; i < flushEventListener.length; i++ ) {
flushEventListener[i].onFlush( new FlushEvent(this) );
}
}
/** 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();
if ( source.getPersistenceContext().hasNonReadOnlyEntities() ) {
flushEverythingToExecutions(event);
performExecutions(source);
postFlush(source);
if ( source.getFactory().getStatistics().isStatisticsEnabled() ) {
source.getFactory().getStatisticsImplementor().flush();
}
}
}
/**
* 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>
*/
protected void performExecutions(EventSource session) throws HibernateException {
log.trace("executing flush");
try {
session.getJDBCContext().getConnectionManager().flushBeginning();
// we need to lock the collection caches before
// executing entity inserts/updates in order to
// account for bidi associations
session.getActionQueue().prepareActions();
session.getActionQueue().executeActions();
}
catch (HibernateException he) {
log.error("Could not synchronize database state with session", he);
throw he;
}
finally {
session.getJDBCContext().getConnectionManager().flushEnding();
}
}
/**
* Perform all currently queued actions.
*
* @throws HibernateException error executing queued actions.
*/
public void executeActions() throws HibernateException {
executeActions( insertions );
executeActions( updates );
executeActions( collectionRemovals );
executeActions( collectionUpdates );
executeActions( collectionCreations );
executeActions( deletions );
}
private void executeActions(List list) throws HibernateException {
int size = list.size();
for ( int i = 0; i < size; i++ ) {
execute( (Executable) list.get(i) );
}
list.clear();
session.getBatcher().executeBatch();
}
public void executeBatch() throws HibernateException {
if (batchUpdate!=null) {
try {
try {
doExecuteBatch(batchUpdate);
}
finally {
closeStatement(batchUpdate);
}
}
catch (SQLException sqle) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"Could not execute JDBC batch update",
batchUpdateSQL
);
}
finally {
batchUpdate=null;
batchUpdateSQL=null;
}
}
}
protected void doExecuteBatch(PreparedStatement ps) throws SQLException, HibernateException {
if ( batchSize == 0 ) {
log.debug( "no batched statements to execute" );
}
else {
if ( log.isDebugEnabled() ) {
log.debug( "Executing batch size: " + batchSize );
}
try {
checkRowCounts( ps.executeBatch(), ps );
}
catch (RuntimeException re) {
log.error( "Exception executing batch: ", re );
throw re;
}
finally {
batchSize = 0;
}
}
}