iBatis --> MyBatis
从 Clinton Begin 到 Google(从 iBatis 到 MyBatis,从 Apache Software Foundation 到 Google Code),Apache 开源代码项目,O/R Mapping 解决方案,基于 SQL 映射(将SQL语句映射为Java/.Net对象)的支持 Java 和 .Net 的数据访问工具和持久层框架。
iBatis 作为一个映射层,在对象(类-字段)和数据库(数据表-列)之间传递数据,并保持两者与映射层的相互独立,低耦合。
- 小巧简单、轻量级,快速开发,提供满足要求、灵活简单的解决方案
- iBATIS 提供独立于数据库的接口和 API
- 支持存储过程、内嵌的 SQL、动态 SQL
- SQL语句在单独的 XML 文件中,与程序代码分离,可移植性
- SQL 语句需要手动灵活配置,半自动化(相比全自动化的Hibernate)
iBatis 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAO),同时提供一个利用该框架开发的 JPetStore 实例。
- SQL Maps:整个iBatis Database Layer的核心价值,通过XML文件实现从实体到SQL语句的映射,显著节约数据库操作的代码量
- DAO:提供接口操作数据,隐藏实现细节
iBatis 运行方式类似 ADO.Net,连接数据库、设置参数、执行语句、获取并返回结果、关闭并释放资源。
关于 ADO.Net
改进的ADO数据访问模型,Microsoft 提供的一组用于和数据源进行交互的面向对象类库,.NET 编程环境中优先使用的数据访问接口。
- 内存表示:DataSet 对象
- 数据访问:DataTable 的 Rows 集合 索引访问
相关信息可参见:ADO.Net 入门学习;
下面直接给出 iBatis 的框架结构
关于 iBatis 的具体信息参见官网:iBatis --> MyBatis; MyBatis for .Net; MyBatis for Java;
SqlMapXxxx.config
iBatis SqlMap 配置文件(DataMapper 配置文件),类似项目的 web.config 或 app.config
- 指定 MyXxxx.xml 和 providers.config 文件的位置
- 定义 DataMapper 的其他配置选项
SQL Map 将对象持久化至关系型数据库,方便维护(无需修改代码,只需动态维护XML文件即可)
在项目中,推荐一个数据源(数据库)对应一个 SqlMapXxxx.config 文件 和 一个 providers.config 文件
- DB2:SqlMapDB2.config、providersDB2.config
- SQLServer:SqlMapSQLServer.config、providersSQLServer.config
SqlMapXxxx.config 用于将 MyXxxx.xml 文件载入到 iBatis 框架。
<?xml version="1.0" encoding="utf-8"?> <sqlMapConfig xmlns="http://ibatis.apache.org/dataMapper" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> // 自增长属性等配置信息 <propertys> <property /> </propertys> // 配置信息 <settings> <setting /> </settings> // 数据库相关信息 <providers resource="bin/DBConfig/iBatis/providersDB2.config"/> <database> <provider name="iDb2.9"/> <dataSource name="SourceName" connectionString="Database=xxx;User ID=xxx; Password = xxx;"/> </database> // SQL映射信息文件 <sqlMaps> <sqlMap resource="bin/DBConfig/SqlMap/MyXxxx.xml" /> </sqlMaps> </sqlMapConfig>
首先在该配置中,凡是引用外部文件,均可通过
- resource:推荐,从项目的根目录加载
- url:从文件的绝对路径加载
- embedded:作为程序集的资源文件加载
配置信息 <setting /> 有关参数解释如下:
maxRequests:同时执行SQL语句的最大线程数 maxSessions:同一时间内活动的最大session数 maxTransactions:同时进入SqlMapClient.startTransaction()的最大线程数 cacheModelsEnabled:全局性启用/禁用SqlMapClient的所有缓存model lazyLoadingEnabled:全局性启用/禁用SqlMapClient的所有延迟加载 enhancementEnabled:全局性启用/禁用运行时字节码增强,优化访问JavaBean属性的性能,同时优化延迟加载性能 useStatementNamespaces:如果启用本属性,必须使用全限定名来引用mapped statement。 Mapped statement的全限定名由sql-map的名称和mapped-statement的名称合成
配置信息 <sqlMaps> + <sqlMap> 指向 SQL 映射文件。
此外,上述信息引用 DB2 数据库,如果是引用 SQLServer 数据库,配置信息如下
<providers resource="bin/DBConfig/IBatis/providersSQLServer.config"/> <database> <provider name="sqlServer2.0"/> <dataSource name="SourceName" connectionString="Data Source=IP;Initial Catalog=dbName;User ID=xxx;Password=xxx;connection reset=false;"/> </database>
当向存在自增长列的表插入数据时,需要配置如下属性
// for SQLServer <properties> <property key="selectKeyForXxxx" value="select @@IDENTITY as value" /> </properties> // for SQLServer (推荐) <properties> <property key="selectKeyForXxxx" value="select SCOPE_IDENTITY() as value" /> </properties> // for DB2 <properties> <property key="selectKeyForXxxx" value="VALUES IDENTITY_VAL_LOCAL()" /> </properties>
关于 SCOPE_IDENTITY() 和 @@IDENTITY 的区别,参见:http://www.cnblogs.com/MingDe/archive/2011/10/12/2208749.html
在 MyXxxx.xml 文件中使用形式如下
<insert id="insertUser" parameterClass="User"> insert into Users values(null,#userName# ,#password#,#userType#); <selectKey resultClass="int" keyProperty="uid"> ${selectKeyForXxxx} </selectKey> </insert>
该配置也可以直接在 MyXxxx.xml 文件的 <insert> 标签中配置。
<insert id="insertUser" parameterClass="User"> insert into Users values(null,#userName# ,#password#,#userType#); <selectKey resultClass="int" keyProperty="uid"> SELECT @@IDENTITY AS uid // uid是数据库表列名 </selectKey> </insert>
MyXxxx.xml
SQL 语句映射文件,iBatis 通过该 XML 文件来映射 SQL 语句的输入和输出以及语句本身。
<?xml version="1.0" encoding="utf-8" ?> <sqlMap namespace = "与文件同名即可" xmlns="http://ibatis.apache.org/mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > <!--别名设置--> <alias> <typeAlias alias="别名" type="命名空间.类名, 命名空间"/> </alias> <!--返回值配置--> <resultMaps> <resultMap id="resultMapID" class="别名"> <result property="Param1" column="数据库列1" /> <result property="Param2" column="数据库列2" /> </resultMap> </resultMaps> <!--动态SQL配置--> <statements> <!-- SQL公用片段, 用于组装SQL语句 --> <sql id="sql_select_count"> select count(*) </sql> <sql id="sql_select_all"> select * </sql> <sql id ="sql_operation_where"> from ( tableName or (select语句) where <dynamic> 动态组装 where 条件 </dynamic> </sql> // 增删改查(Insert/Delete/Update/Select), 根据需要自行组装SQL语句 <!-- Select --> <select id="selectID" parameterClass="别名" resultClass="resultMapID or 特定返回值类型" remapResults="true"> <include refid="sql_select_all"/> <include refid="sql_operation_where"/> )temp where 条件 </select> <!-- Insert --> <insert id="insertID" parameterClass="别名"> SQL语句 or 存储过程 </insert> <!-- Update --> <update id="updateID" parameterClass="别名"> SQL语句 or 存储过程 </update> <!-- Delete --> <delete id="deleteID" parameterClass="别名"> SQL语句 or 存储过程 </delete> </statements> </sqlMap>
下面对映射文件中的几个重要标签作解释
[0]. sqlMap
SQL 映射文件的根结点。因为 iBatis 运行时会把所有映射文件一次性加载,所以属性 namespace 命名空间必须唯一标识映射文件 。
同一命名空间下,标签的 id 属性不能重复。若不同命名空间下存在相同的标签 id,需要使用 命名空间.标签id 访问。
[1]. resultMaps + resultMap
返回值配置,iBatis 映射文件中最重要最强大的元素。
[2]. alias + typeAlias
别名配置,化繁为简,简短的别名代替完全限定的类名。
[3]. statements
动态SQL配置。动态SQL作为iBatis的强大功能,灵活的动态SQL标签、提高SQL语句的重用性。
- <sql>:SQL公用片段,可以用于组装SQL语句
- <select> <insert> <update> <delete>:增删改查标签,可以利用 <sql> 自行组装SQL语句
[4]. <![CDATA[ xxxxxx ]]>
通过 <![CDATA[……]]> 节点,可以避免SQL中与XML规范相冲突的字符对XML映射文件的合法性造成影响。
The most common conflict between SQL and XML is the greater-than and less-than symbols (><).
关于 <![CDATA[ ... ]]> 的使用,对于非数据库字段,应使用该标签包装。
[5]. parameterClass + resultClass
parameterClass 表示输入参数类型完整类名,resultClass 表示返回结果类型完整类名,两者均可通过 alias 简化。
存储过程使用 parameterMap,除存储过程外的其他 <statement> 内的标签均采用 parameterClass 和 resultClass 配置方式。
动态SQL
在利用 <sql> 标签组装增删改查标签时,动态条件写在 <dynamic>、一元标签、二元标签 和 <iterator> 中,该标签可以放在
- select 列表
- where 过滤条件
中,下面详细介绍如何拼接动态 SQL 语句以及应该注意的问题。
(a)关于直接执行 SQL 语句,务必注意以下几点
1). 属性 remapResults="true" 配置 2). 推荐使用 $sql$ or <![CDATA[ $sql$ ]]> ,但是不要用 #sql#
(b)插入类型的存储过程,推荐在存储过程的最后添加
// 返回最新主键 SELECT @@IDENTITY AS value
(C)关于验证 where 和 AND/OR 在动态SQL中的位置问题
下面给出 IBatis 调用 SQL 语句的几个简单例子
[1]. 根据 ID 查询 Name
<select id="selectNameByID" parameterClass="int" resultClass="string"> select name from dbo.StudentInfo where id=#id# </select>
然后给出调用方式
int id = 101; string statement = "MyXxxx.selectNameByID"; string stuName = DataOperationHelper.CallSql<string>(statement, id);
下面的代码,封装了2个方法,分别用于执行SQL语句和存储过程
1 /// <summary> 2 /// 调用SQL 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 /// <param name="statement"></param> 6 /// <param name="inParam"></param> 7 /// <returns></returns> 8 public static T CallSql<T>(string statement, object inParam) 9 { 10 T outParam = default(T); 11 12 try { 13 outParam = iSqlMapper.QueryForObject<T>(statement, inParam); 14 } 15 catch(Exception ex) { 16 Logger.Error("Sql: " + ex.Message + ex.StackTrace); 17 throw (new Exception(ex.Message)); 18 } 19 20 Logger.Info("exec Sql " + statement + " success"); 21 return outParam; 22 } 23 24 /// <summary> 25 /// 调用存储过程 26 /// </summary> 27 /// <typeparam name="T"></typeparam> 28 /// <param name="statement"></param> 29 /// <param name="inParam"></param> 30 /// <returns></returns> 31 public static T CallProc<T>(string statement, object inParam) 32 { 33 T outParam = default(T); 34 35 try 36 { 37 outParam = iSqlMapper.QueryForObject<T>(statement, inParam); 38 } 39 catch (Exception ex) 40 { 41 Logger.Error("Proc: " + ex.Message + ex.StackTrace); 42 throw (new Exception(ex.Message)); 43 } 44 45 Logger.Info("exec Proc " + statement + " success"); 46 return outParam; 47 }
其实,两者没有太大区别,只是为了分开而分开。
注意,上述只是调用 QueryForObject 获取一个对象,如果是获取列表,需要调用 QueryForList。
存储过程
如果是利用 iBatis 调用存储过程,需要添加如下标签
[1]. parameterMaps + parameterMap
存储过程参数配置,标签地位等同 resultMaps + resultMap 标签。
<!-- 存储过程输入参数配置 --> <parameterMaps> <parameterMap id="parameterMapID" class="参数类型(Hashtable or map)"> <parameter property="Param1" column="数据库列1" direction="Input" /> <parameter property="Param2" column="数据库列2" type="string" dbType="binary" direction="Input" /> <parameter ... ... /> <parameter property="Param_x" column="数据库列x" direction="Output" /> <parameter property="Param_y" column="数据库列y" direction="Output" /> </parameterMap> </parameterMaps>
[2]. procedure
存储过程设置,在 <statements> 标签中配置。
<statements> <procedure id="procedureID" parameterMap="parameterMapID" resultMap="resultMapID" remapResults="true"> <![CDATA[ DatabaseName.ProcedureName (eg: RVC.Pr_QueryUserInfo) ]]> </procedure> </statements>
注意,如果是返回特定返回值类型,应该采用:resultClass="特定返回值类型"
[3]. remapResults
在使用 iBatis 中,务必设置该属性为 true,用于重置查询结果,否则,由于 iBatis 缓存机制的原因,会得到重复的查询结果。
除此之外,注意存储过程的 return 只能返回 int 类型,若要返回其它类型,请移步 output
OGNL 表达式
iBatis 提供强大的 OGNL 表达式来消除其他元素。
- if 语句:<if>,最常见的场景是在动态SQL中有条件地包括where子句的一部分
- choose, when, otherwise 语句:类似switch语句
- where 语句:<where>,避免出现 SELECT * FROM TabName WHERE 的情况
- foreach 语句:<foreach>,允许指定使用集合
providers.config
提供常用数据库驱动程序支持信息清单,DataMapper 在该文件中查找选定数据库的 provider 的定义。
// provider.config 配置文件结构 <?xml version="1.0" encoding="utf-8"?> <providers xmlns="http://ibatis.apache.org/providers" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <clear/> <provider name="DBName" description="The description of the DB" enabled="true or false" default="true or false" parameterPrefix="@" ... ... /> <provider /> ... ... </providers>
注意其中的几个参数,如果多个数据库驱动 enabled="true",可以设置其中一个的 default="true" 默认启动,parameterPrefix 表示参数化SQL语句中参数的前缀。
下面分别给出一个 SQLServer 和 DB2 的示例
1 <provider 2 name="sqlServer2.0" 3 enabled="true" 4 description="Microsoft SQL Server, provider V2.0.0.0 in framework .NET V2.0" 5 assemblyName="System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 6 connectionClass="System.Data.SqlClient.SqlConnection" 7 commandClass="System.Data.SqlClient.SqlCommand" 8 parameterClass="System.Data.SqlClient.SqlParameter" 9 parameterDbTypeClass="System.Data.SqlDbType" 10 parameterDbTypeProperty="SqlDbType" 11 dataAdapterClass="System.Data.SqlClient.SqlDataAdapter" 12 commandBuilderClass=" System.Data.SqlClient.SqlCommandBuilder" 13 usePositionalParameters = "false" 14 useParameterPrefixInSql = "true" 15 useParameterPrefixInParameter = "true" 16 parameterPrefix="@" 17 allowMARS="false" 18 allowMultiQueries="true" 19 />
1 <provider 2 name="iDb2.9" 3 description="IBM DB2 Provider, V 10.0" 4 enabled="true" 5 default="true" 6 assemblyName="IBM.Data.DB2, Culture=neutral, PublicKeyToken=7c307b91aa13d208, Custom=null" 7 connectionClass="IBM.Data.DB2.DB2Connection" 8 commandClass="IBM.Data.DB2.DB2Command" 9 parameterClass="IBM.Data.DB2.DB2Parameter" 10 parameterDbTypeClass="IBM.Data.DB2.DB2Type" 11 parameterDbTypeProperty="DB2Type" 12 dataAdapterClass="IBM.Data.DB2.DB2DataAdapter" 13 commandBuilderClass="IBM.Data.DB2.DB2CommandBuilder" 14 usePositionalParameters="true" 15 useParameterPrefixInSql="false" 16 useParameterPrefixInParameter="false" 17 parameterPrefix="" 18 allowMARS="false" 19 />
基本使用
iBatis 最重要的三个文件
SqlMapXxxx.config、MyXxxx.xml、providersXxxx.config
注意,SqlMapXxxx.config、providersXxxx.config 文件应放在 DataMapper 运行时可以找到的地方。
首先,要下载 iBatis .Net 使用的 .dll 文件,可自行百度 iBatis.Net 哈
- IBatis.DataMapper.1.6.2.bin.zip
- IBatis.DataAccess.1.9.2.bin.zip
涉及的主要 .dll 文件如下
IBatisNet.Common.dll: 由DataAccess和DataMapper组成的共享程序集 IBatisNet.DataMapper.dll: DataMapper框架 IBatisNet.DataAccess.dll: DataAccess框架 IBatisNet.Common.Logging.Log4Net.dll: Log4Net集成记录器, 和Log4Net.dll配合使用
此外,下载的 ibatis.net 在解压目录下有几个 .xsd 文件,该文件是 .xml 文件的验证文件
SqlMapConfig.xsd, SqlMap.xsd, provider.xsd, DaoConfig.xsd
将其拷贝到 VS 安装目录,用于在 VS 中支持 XML 自动提示
%VsInstallDir%\xml\Schemas
在解压目录下还有几个 .xml 文件
IBatisNet.Common.xml、IBatisNet.DataMapper.xml、IBatisNet.DataAccess.xml、IBatisNet.Common.Logging.Log4Net.xml、log4net.xml
将其拷贝到 VS .NET Framework 的安装目录,用于编写代码时获得程序的API说明
C:/WINDOWS/Microsoft.NET/Framework/v4.0.30319/zh-CN
[1]. ISqlMapper
该 API 提供数据访问层涉及到的方法
// 查询方法 [1]. QueryForObject() [2]. QueryForList() [3]. QueryForMap() [4]. QueryForDictionary() // 增、删、更新方法 object Insert(string statementName, object parameterObject); int Update(string statementName, object parameterObject); int Delete(string statementName, object parameterObject);
注意,”Map” 这个名称是 Java 里的,按 .NET 的精神应该是 Dictionary,所以 iBatis.NET同时提供了 "Dictionary" 对应的方法。
[2]. 自动结果映射
该特性可以通过三种方式使用
- 单列查询
- 固定类列表查询
- 动态列表查询
映射参数用于定义参数的有序列表,与查询语句的占位符相匹配。
[3]. iBatis初始化
private static ISqlMapper iSqlMapper = null; /// filePath: 配置文件 SqlMapXxxx.config (相对)路径 public static void StartIBatisMapXxxx(string filePath) { DomSqlMapBuilder builder = new DomSqlMapBuilder(); iSqlMapper = builder.Configure(filePath); // 非Web请求线程需要加上该行 iSqlMapper.SessionStore = new IBatisNet.DataMapper.SessionStore.HybridWebThreadSessionStore(iSqlMapper.Id); Logger = LogManager.GetLogger("iBatis"); }
在 iBatis 中,SqlMapper 对象默认是单例模式实现。除上述方法外,也可以通过 Mapper 类的静态 Instance() 属性实例化。
Demo 测试
下面给出一个使用 iBatis 的简单 Demo,注意一点,新建项目时务必确认 .Net 框架的版本
默认是 .NET Framework 4 Client Profile,替换为 .NET Framework 4
首先给出配置文件(包含日志记录配置信息),在 app.config或web.config 中配置
(1)直接在控制台显示
1 <configuration> 2 <configSections> 3 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" /> 4 <sectionGroup name="iBATIS"> 5 <section name="logging" type="IBatisNet.Common.Logging.ConfigurationSectionHandler, IBatisNet.Common" /> 6 </sectionGroup> 7 </configSections> 8 9 <iBATIS> 10 <logging> 11 <logFactoryAdapter type="IBatisNet.Common.Logging.Impl.ConsoleOutLoggerFA, IBatisNet.Common"> 12 <arg key="showLogName" value="true" /> 13 <arg key="showDataTime" value="true" /> 14 <arg key="level" value="ALL" /> 15 <arg key="dateTimeFormat" value="[MM-dd HH:mm:ss]" /> 16 </logFactoryAdapter> 17 </logging> 18 </iBATIS> 19 </configuration>
务必注意,标签 <iBATIS> 万万不能写成 <iBatis>,否则无法输出
此时,配置文件中没有使用 log4net 配置,要想在程序中执行 Logger.Info/Warn 记录日志,必须初始化
IBatisNet.Common.Logging.ILog Logger = IBatisNet.Common.Logging.LogManager.GetLogger("iBatis");
采用 IBatisNet.Common.Logging 组件初始化 Logger。
(2)日志输出到文件
1 <configuration> 2 <configSections> 3 <sectionGroup name="iBATIS"> 4 <section name="logging" type="IBatisNet.Common.Logging.ConfigurationSectionHandler, IBatisNet.Common"/> 5 </sectionGroup> 6 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/> 7 </configSections> 8 9 <!-- (2). 利用log4net输出日志到文件 --> 10 <iBATIS> 11 <logging> 12 <logFactoryAdapter type="IBatisNet.Common.Logging.Impl.Log4NetLoggerFA, IBatisNet.Common.Logging.Log4Net"> 13 <arg key="configType" value="inline"/> 14 </logFactoryAdapter> 15 </logging> 16 </iBATIS> 17 18 <log4net> 19 <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> 20 <file value="Log\" /> 21 <appendToFile value= "true" /> 22 <datePattern value= "yyyyMMdd".log"" /> 23 <rollingStyle value= "Date" /> 24 <staticLogFileName value="false" /> 25 <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> 26 <layout type="log4net.Layout.PatternLayout, log4net"> 27 <param name="ConversionPattern" value="[%d{HH:mm:ss,fff}] [%t] %-5p %c-(%line) %m%n" /> 28 </layout> 29 </appender> 30 31 <appender name="RollingConsoleAppender" type="log4net.Appender.ConsoleAppender"> 32 <layout type="log4net.Layout.PatternLayout"> 33 <param name="ConversionPattern" value="[%d{HH:mm:ss,fff}] [%t] %-5p %c-(%line) %m%n"/> 34 </layout> 35 </appender> 36 37 <root name="iBatis"> 38 <level value="ALL"/> 39 <appender-ref ref="RollingLogFileAppender"/> 40 <appender-ref ref="RollingConsoleAppender"/> 41 </root> 42 43 <!-- 打印错误信息的级别 44 <logger name="IBatisNet.DataMapper.Configuration.Cache.CacheModel"> 45 <level value="DEBUG"/> 46 </logger> 47 <logger name="IBatisNet.DataMapper.Configuration.Statements.PreparedStatementFactory"> 48 <level value="DEBUG"/> 49 </logger> 50 <logger name="IBatisNet.DataMapper.LazyLoadList"> 51 <level value="DEBUG"/> 52 </logger> 53 <logger name="IBatisNet.DataAccess.DaoSession"> 54 <level value="DEBUG"/> 55 </logger> 56 <logger name="IBatisNet.DataMapper.SqlMapSession"> 57 <level value="DEBUG"/> 58 </logger> 59 <logger name="IBatisNet.Common.Transaction.TransactionScope"> 60 <level value="DEBUG"/> 61 </logger> 62 <logger name="IBatisNet.DataAccess.Configuration.DaoProxy"> 63 <level value="DEBUG"/> 64 </logger> 65 --> 66 </log4net> 67 68 <startup> 69 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> 70 </startup> 71 </configuration>
其中,<iBATIS> 标签的 configType 属性可设置为 inline、external、file、file-watch,推荐 inline:可以直接在 App.config 或 Web.Config 中配置 log4net 节点。
务必注意,log4net 的版本,应该采用下载包中的 1.2.10.0 版本,不要用最新的 2.0.8.0 版本。
下面给出在主函数中的初始化方法
string configFilePath = "app.config"; FileInfo fileinfo = new FileInfo(configFilePath); log4net.Config.XmlConfigurator.ConfigureAndWatch(fileinfo); ILog Logger = LogManager.GetLogger("iBatis");
因为此处采用 log4net 记录日志,可以直接使用 log4net 组件初始化 Logger。
(3)iBatis 调用存储过程
利用 iBatis 调用SQL语句,在前面的 动态SQL 部分学习过,此处不再重复,下面重点学习下调用存储过程。
相关信息可以参见:iBatis 调用存储过程
首先定义一个带输出参数的存储过程,根据 ID 查询姓名
1 USE [Stu_sqh] 2 GO 3 4 SET ANSI_NULLS ON 5 GO 6 SET QUOTED_IDENTIFIER ON 7 GO 8 9 IF exists(select 1 from sysobjects where id=object_id('PR_QueryNameByID') and xtype='P') 10 DROP PROC PR_QueryNameByID 11 GO 12 13 CREATE PROC PR_QueryNameByID( 14 @ID INT, 15 @O_RTCD INT OUT, 16 @O_NAME VARCHAR(20) OUT 17 ) 18 as 19 20 SET @O_RTCD = 0; 21 SET @O_Name = ''; 22 23 BEGIN 24 select @O_Name = NAME 25 FROM [Stu_sqh].[dbo].[StudentInfo] 26 WHERE ID = @ID; 27 END; 28 29 RETURN 0;
然后,在 MyXxxx.xml 中配置
<parameterMaps> <parameterMap id="QueryNameByIDParamMap" class="Hashtable"> <parameter property="ID" column="ID" direction="Input" /> <parameter property="O_RTCD" column="O_RTCD" direction="Output" /> <parameter property="O_NAME" column="O_NAME" direction="Output" /> </parameterMap> </parameterMaps> <procedure id="QueryNameByID" parameterMap="QueryNameByIDParamMap" resultClass="string" remapResults="true"> <![CDATA[ PR_QueryNameByID ]]> </procedure>
最后,调用该存储过程
Hashtable ht = new Hashtable(); ht.Add("ID", 101); ht.Add("O_RTCD", 0); ht.Add("O_NAME", ""); strig statement = "MyXxxx.QueryNameByID"; DataOperationHelper.CallProc<string>(statement, ht); string stuName = (string)ht["O_NAME"];
扩展使用
(1)关于数据库查询性能提升方法
[a]. 分页查询
最实际有效。
[b]. 延迟加载
Lazy Loading,需要时再加载。在 SqlMapXxxx.config 文件中添加如下配置
// 延迟加载机制 <setting lazyLoadingEnabled="true"/> // 字节码强制机制 <setting enhancementEnabled = "true" />
其中,字节码强制机制有利于 Lazy Loading 性能改进。
[c]. Cache机制
利用 Cache 查询,对于更新次数较少的数据比较有效,但是必须谨慎使用Cache机制,避免出现第三方对数据的更新导致脏数据。
在 SqlMapXxxx.config 文件中添加如下配置
// Cache机制 <setting cacheModelsEnabled="true"/>
结合 CacheModel,在配置文件中,主要是 type、readOnly、serialize 三个属性
<cacheModel id="product-cache" type ="LRU" readOnly="true" serialize="false" />
其中,readOnly 表示缓存数据只读,读性能好,但数据更新会降低效率;serialize = true 表示全局数据缓存,反之表示局部缓存、仅对当前 Session 有效。
对于 type,表示缓存实现机制,有 3 种类型
- FIFO:先进先出型缓存,最先放入 Cache 中的数据将被最先清除
- LRU:最近最少使用型缓存,当 Cache 达到预先设定的最大容量时,iBatis 会按照 “最少使用” 原则将使用频率最少的对象从缓冲中清除
- Memory:通常采用 WEAK 的 MEMORY 型 Cache 配置
此处给出一个 CacheModel 的配置结点示例
<cacheModels> <cacheModel id = "person-cache" implementation = "MEMORY" > <flushInterval hours = "24"/> <flushOnExecute statement = "UpdateAccountViaInlineParameters"/> <flushOnExecute statement = "UpdateAccountViaParameterMap"/> <property name = "Type" value = "Weak"/> </cacheModel> </cacheModels>
对 CacheModel 的几个重要元素说明如下
flushInterval:设定缓存有效期 CacheSize:当前Cachemodel中最大的数据对象数量 flushOnExecute:指定执行特定的Statement时,将缓存清空
特别注意 flushOnExecute,如 Update 操作将更新数据库信息,会导致缓存中的数据信息与数据库中的实际数据信息发生偏差,因此必须将缓存清空避免脏数据出现。
然后,在 <select> 标签中,对查询到的数据采用 cache 机制
<statements> <select id="SelectAllPerson" resultMap="PersonInfoResult" cacheModel="person-cache"> select ID, NAME, BIRTH_DATE, WEIGHT_KG, HEIGHT_M from PERSON </select> </statements>
通过该 id 获取到数据后,使用 CacheModel 的 “person-cache” 进行缓存,当再次调用该 id 数据查询时,直接从缓存中取数据,不用再去查询数据库。
(2)关于避免 N+1查询 问题
首先了解什么是 N+1 查询问题,主要考虑在大数据集的情况,特别是对于主从表(父子表)的查询,容易产生N+1查询问题,由于试图加载父记录的多个子记录引起的。在查询父记录时,只需要1条语句,若返回N条记录,那么就需要再执行N条语句来查询子记录,引发"N+1"问题。
如何避免,提供 2 种方式:
- 延迟加载:配置属性 lazyLoad="true",并没完全解决数据库I/O问题,最坏情况下,对数据库的访问次数与非延迟加载是一样的。
- 连接语句(join)方式来完全避免N+1查询问题:在 iBatis 中使用 groupBy 特性:方法快,但是内存占用大
- 自定义组件 RowHandler
相关信息参见:iBatis系列-数据库查询;
(3)获取特定类型的数据
[a]. 获取 SQL 语句
在某些场景下,需要获取执行的动态SQL语句,如,SQL语句执行失败、将该语句打印到日志
public static string GetRuntimeSQL(ISqlMapper iSqlMapper, string selectFun, object inParam) { string sql = string.Empty; try { IMappedStatement iMappedStatement = iSqlMapper.GetMappedStatement(selectFun); ISqlMapSession iSession = iSqlMapper.CreateSqlMapSession(); RequestScope requestScope = iMappedStatement.Statement.Sql.GetRequestScope(iMappedStatement, inParam, iSession); iMappedStatement.PreparedCommand.Create(requestScope, iSession, iMappedStatement.Statement, inParam); sql = requestScope.PreparedStatement.PreparedSql; IDbCommand cmd = requestScope.IDbCommand; foreach (IDbDataParameter it in cmd.Parameters) { sql += "; [" + it.ParameterName + " = " + it.Value + "] "; } iSession.CloseConnection(); } catch(Exception ex) { Logger.Info("GetRuntimeSQL exec failed: " + ex.Message + ex.StackTrace); return null; } return sql; }
[b]. 获取存储过程
获取方法类似获取 SQL 语句,额外维护一个参数方向的字典集,输出信息会更加明确
proc = requestScope.PreparedStatement.PreparedSql; // 存储过程名字 IDbCommand cmd = requestScope.IDbCommand; foreach (IDbDataParameter it in cmd.Parameters) { proc += "; [" + it.ParameterName + " = " + it.Value + "] "; } iSession.CloseConnection();
具体方法可参见:iBatis.Net实现返回DataTable和DataSet对象
[c]. 返回 DataSet 类型数据
在某些情况下,需要返回 DataSet 格式的数据,如果返回 DataTable,则 dataSet.Table[0];
public static DataSet QueryForDataSet(ISqlMapper iSqlMapper, string selectFun, object inParam) { DataSet dataSet = new DataSet(); try { IMappedStatement iMappedStatement = iSqlMapper.GetMappedStatement(selectFun); ISqlMapSession iSession = iSqlMapper.CreateSqlMapSession(); RequestScope requestScope = iMappedStatement.Statement.Sql.GetRequestScope(iMappedStatement, inParam, iSession); iMappedStatement.PreparedCommand.Create(requestScope, iSession, iMappedStatement.Statement, inParam); FieldInfo info = requestScope.IDbCommand.GetType().GetField("_innerDbCommand", BindingFlags.NonPublic | BindingFlags.Instance); IDbCommand cmd = (IDbCommand)info.GetValue(requestScope.IDbCommand); IDbDataAdapter adapter = iSession.CreateDataAdapter(cmd); adapter.Fill(dataSet); } catch (Exception ex) { Logger.Info("QueryForDataSet exec failed: " + ex.Message + ex.StackTrace); return null; } return dataSet; }
注意,在 iBatis 1.6 版本之后,用 DbCommandDecorator 包装了 DbCommand,直接
iSession.CreateDataAdapter(requestScope.IDbCommand).Fill(ds)
会提示
无法将类型为“IBatisNet.DataMapper.Commands.DbCommandDecorator”的对象强制转换为类型“System.Data.SqlClient.SqlCommand”
可以采用反射的方法,从 DbCommandDecorator 中取出 innerDbCommand 字段即可。
下面给出调用方式和相应的配置
<select id="selectInfoByID" parameterClass="int" resultClass="System.Data.DataSet"> select * from dbo.StudentInfo where id=#id# </select> int id = 101; string statement = "MyXxxx.selectInfoByID"; ISqlMapper iSqlMapper = DataOperationHelper.iSqlMapper; DataSet ds = DataOperationHelper.QueryForDataSet(iSqlMapper, statement, id); Console.WriteLine(ds.GetXml());
注意,务必写成 System.Data.DataSet,不能简写。
此处,返回 DataSet/DataTable,并没有用到 iBatis 的内部查询方法,区别与 CallSql 和 CallProc。
调用存储过程返回 DataSet/DataTable 可能会遇到的问题,参见:http://blog.csdn.net/netjxz/article/details/1675430
使用 iBatis 应注意的问题
[1]. # 与 $
- # 是内联参数,可以进行编译、类型匹配,安全性能好,$ 是文本替换,不支持数据类型匹配;
- # 可用于变量替换,$ 仅仅是字符串拼接、存在 SQL 注入风险;
- 对于可能为整型变量可能为字符串变量的地方,务必使用 #,对于一定是整型变量的地方,可以使用 $;
对于变量,推荐使用 #,可以有效防止 SQL 注入。
update #tableName# set STATUS = #status# where ID = #id#
注意注意再注意,重要的事情说3遍,谨慎使用替换($)语法。
[2]. iBatis 的 .dll 文件与 VS .NET FrameWork 4.0 版本不匹配的问题
问题:项目中引用了 .dll 文件,但是编译运行报错:不存在相关的 .dll 文件
解决方法:将 VS 的.NET FrameWork 4.0 框架改为 .NET FrameWork 3.5
[3]. 提示数据库驱动程序不对
问题:引用的 System.Data.SqlClient.dll 版本和 providers.config 中记录的版本不一致
解决方法:通过 Assembly 读取 System.Data.SqlClient.dll 文件的 FullName,填入providers.config 文件相应驱动的 assemblyName 参数
System.Reflection.Assembly.LoadFile(@"...\System.Data.OracleClient.dll").FullName
关于在 Java 中使用 iBatis 可参见上述在 .Net 中的用法,有待于进一步学习。
参考
iBatis .Net - 博客园;框架:Ibatis.Net学习;
iBATIS.NET - DataMapper Application Framework - DataMapper Developer Guide;