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 MapsData 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 .NetMyBatis 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.configprovidersSQLServer.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 }
执行SQL或存储过程

其实,两者没有太大区别,只是为了分开而分开。

注意,上述只是调用 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 />
SQLServer
 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 />
DB2

基本使用

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 info in Console

务必注意,标签 <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&quot;.log&quot;" />
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 log into file by log4net

其中,<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;
PR_QueryNameByID

然后,在 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的学习记录 - 几个问题

iBATIS.NET - DataMapper Application Framework - DataMapper Developer Guide

深入分析 iBATIS Java 框架之系统架构与映射原理

posted @ 2017-06-21 14:08  万箭穿心,习惯就好。  阅读(776)  评论(0编辑  收藏  举报