• 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%xmlSchemas
    

    在解压目录下还有几个 .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 框架之系统架构与映射原理

  • 相关阅读:
    运算符
    变量数据类型及其转换
    JAVA基础知识
    关于Eclipse及JDK安装过程中的一些问题
    python并发编程相关概念总结
    python网络编程01
    python网络编程
    python面向对象编程设计与开发
    python面向对象编程实例
    文件、函数、装饰器、迭代器实例
  • 原文地址:https://www.cnblogs.com/wjcx-sqh/p/7059251.html
Copyright © 2020-2023  润新知