持久化操作由与持久对象关联的持久化类来完成,持久化类是实现IClassPersister接口的类,每个持久对象都有一个关联的持久化类,这些持久化类存储在会话工厂的classPersisters集合中,nhibernate允许用户通过自定义的持久化类来持久化数据。
IClassPersister接口定义了基本的CRUD操作,在nhibernate中由AbstractEntityPersister类实现,这是一个抽象类,有两个具体的派生类,分别是:EntityPersister和normalizedEntityPersister,前者用于一个表一个类的情况,后面用于一个表一个子类的情况。
在分析持久化操作之前先来介绍几个辅助类:
1. SqlString: 用于构造IDbCommand对象; 2. SqlStringBuilder: 用于构造SqlString对象; 3. SqlInsertBuilder: 用于构造Insert操作的SqlString对象; 4. SqlUpdateBuilder: 用于构造Update操作的SqlString对象; 5. SqlDeleteBuilder: 用于构造Delete操作的SqlString对象; 6. Parameter: 用于转换到实现IDbParameter接口对象; 7. IPrepare: 用于准备和存储IDbCommand接口,由PrepareImpl实现。
下面以一个有identity符识的对象为例说明其持久化的流程。
一. Insert
因为持久对象有identity标识符,所以执行Save操作时,是立即调用持久对象的持久化类来执行Insert操作,而不是加入到计划集合中(原因请参考 nhibernate源码分析之三)。但最终的处理方式是一致的。
//*** EntityPersister.cs ***
public override void Insert(object[] fields, object obj, ISessionImplementor session) { if(UseDynamicInsert) { bool[] notNull = GetNotNullInsertableColumns(fields); Insert(fields, notNull, GenerateInsertString(false, notNull), obj, session); } else { Insert(fields, PropertyInsertability, SqlInsertString, obj, session); } }Insert方法首先检查是否使用dynamic-insert(动态插入),如使用则只插入非空的字段,dynamic-insert可在映射文件中指定;然后通过GenerateInsertString方法取得insert操作的SqlString。
protected virtual SqlString GenerateInsertString(bool identityInsert, bool[] includeProperty) { SqlInsertBuilder builder = new SqlInsertBuilder(factory); builder.SetTableName(TableName);
for(int i = 0 ; i < hydrateSpan; i++) { if(includeProperty[i]) builder.AddColumn(propertyColumnNames[i], PropertyTypes[i]); }
if (IsPolymorphic) builder.AddColumn(DiscriminatorColumnName, DiscriminatorSQLString);
if(identityInsert==false) { builder.AddColumn(IdentifierColumnNames, IdentifierType); } else { if(dialect.IdentityInsertString!=null) { builder.AddColumn(IdentifierColumnNames[0], dialect.IdentityInsertString); } }
return builder.ToSqlString(); }首先构造一个SqlInsertBuilder对象,然后加入所有要插入的字段,hydrateSpan在AbstractEntityPersister类中定义,是持久对象的属性Count;然后判断持久对象是否为Polymorphic(通过字段值实现多态),如是则加入辨别列,辨别值可通过映射文件中的discriminator-value来指定;接着判断是否要加入标识列,最后返回一个SqlString对象。注意:在ToSqlString方法中,可以看到我们熟悉的INSERT INTO语句。
SqlInsertBuilder类的AddColumn方法有多个重载,这里只列出比较重要的一个(加入参数列)。
//*** SqlInsertBuilder.cs *** public SqlInsertBuilder AddColumn(string[] columnNames, IType propertyType) { Parameter[] parameters = Parameter.GenerateParameters(factory, columnNames, propertyType);
for(int i = 0; i < columnNames.Length; i++) { this.columnNames.Add(columnNames[i]); columnValues.Add(parameters[i]); } return this; }通过Parameter的静态方法GenerateParameters创建Parameter,然后加入到集合中。
//*** EntityPersister.cs ***
public object Insert(object[] fields, bool[] notNull, SqlString sql, object obj, ISessionImplementor session) {
IDbCommand statement = null; IDbCommand idSelect = null;
if(dialect.SupportsIdentitySelectInInsert) { statement = session.Preparer.PrepareCommand( dialect.AddIdentitySelectToInsert(sql) ); idSelect = statement; } else { statement = session.Preparer.PrepareCommand(sql); idSelect = session.Preparer.PrepareCommand(SqlIdentitySelect); }
try { Dehydrate(null, fields, PropertyInsertability, statement, session); } catch (Exception e) { throw new HibernateException("...", e); }
try { if(dialect.SupportsIdentitySelectInInsert==false) { statement.ExecuteNonQuery(); }
IDataReader rs = idSelect.ExecuteReader(); object id; try { if ( !rs.Read() ) throw new HibernateException("..."); id = IdentifierGeneratorFactory.Get( rs, IdentifierType.ReturnedClass ); } finally { rs.Close(); }
return id; } catch (Exception e) { throw e; } }首先声明两个IDbCommand对象,statement用于insert,idSelect用于检查标识符,然后通过IPreparer接口来准备IDbCommand对象, 如dialect(数据库方言)支持在Insert语句中后检索标识符,则将insert与检查标识符操作合并;然后调用Dehydrate对参数进行赋值;最后执行IDbCommand并返回id。
IPreparer用于准备IDbCommand对象,并将当前会话中已执行过的IDbCommand对象存储在hashtable表中,这样对于批量操作时可以提高性能,而IDbCommand对象则由SqlString对象创建。
//*** SqlString.cs ***
public IDbCommand BuildCommand(IDriver driver) { int paramIndex = 0; IDbCommand cmd = driver.CreateCommand();
StringBuilder builder = new StringBuilder(sqlParts.Length * 15); for(int i = 0; i < sqlParts.Length; i++) { object part = sqlParts[i]; Parameter parameter = part as Parameter; if(parameter!=null) { string paramName = "p" + paramIndex; builder.Append( parameter.GetSqlName(driver, paramName) ); IDbDataParameter dbParam = parameter.GetIDbDataParameter(cmd, driver, paramName); cmd.Parameters.Add(dbParam); paramIndex++; } else { builder.Append((string)part); } }
cmd.CommandText = builder.ToString(); return cmd; } 通过Driver创建IDbCommand对象,如果part(组成Sql语句的一部分)为Parameter,则创建IDbDataParameter并加入到IDbCommand中。所有的part就组成Sql语句。
//*** EncityPersister ***
protected virtual int Dehydrate(object id, object[] fields, bool[] includeProperty, IDbCommand st, ISessionImplementor session) { int index = 1;
index = 0; for (int j=0; j if ( includeProperty[j] ) { PropertyTypes[j].NullSafeSet( st, fields[j], index, session ); index += propertyColumnSpans[j[1] [2] 下一页 [Access]sql随机抽取记录 [Access]ASP&SQL让select查询结果随机排序的实现方法 [系统软件]SQL语句性能优化--LECCO SQL Expert [C语言系列]SQL Server到DB2连接服务器的实现 [C语言系列]SQL Server到SYBASE连接服务器的实现 [C语言系列]SQL Server到SQLBASE连接服务器的实现 [C语言系列]SQL Server连接VFP数据库的实现 [C语言系列]ASP+SQL Server之图象数据处理 [C语言系列]SQL Server连接ACCESS数据库的实现 [C语言系列]DBA的最佳选择—图形界面还是T-SQL命令?
|