读《IBatis in action 》 总结
iBATIS 学习总结
一、 了解iBATIS
1.什么是iBATIS
“一个映射层,在对象和数据库间传递数据,并保持两者与映射层本身独立”
iBatis 不是直接在类和数据表或字段与列之间进行关联,而是把SQL语句的参数和返回结果映射至类。iBatis是出于类和数据表之间的一个中间层。这使得它在类和数据表之间进行映射时更加灵活。
iBATIS 是把SQL语句的参数和返回结果映射至类
SQL映射:iBATIS使用一个简单的XML描述文件来映射SQL语句的输入和输出
Eg:
<select id=”getAddress” parameterClass=”int” resultClass=”Address”>
Select ……
From
Where ard_ID=#id#
</select>
这里的Address类包含了与select 子句中的每一列的属性
C#语言执行此条语句代码:
Address address=(Address)sqlMap.QueryForObject(“getAddress”,5)
2.工作原理
根本一点:iBATIS可用于代替ADO.NET
iBATIS会近似于ado.net运行方式,iBATIS会连接到数据库,设置参数,执行语句,获取结果,然后关闭和释放资源。
3.使用范围及原因
3.1在小型、简单的系统中使用iBATIS
有三种原因使得iBATIS很适合于这种小型的应用程序:
首先,iBATIS本身就是小巧而简单的。它不需要服务器和任何其它中间件(middleware)。不需要任何额外机制的支持。iBATIS不依赖于其它第三方组件。一份最小的iBATIS安装只需引用一个dll文件和244KB的磁盘空间。除了SQL映射文件,再不需要其它安装,因此只需几分钟时间,您就可以拥有一个可以使用的数据持久层了
其次,iBATIS不会影响到既有的应用程序或数据库的设计。因此,如果您有一个小型应用程序,已经有了部分实现,甚至已经发布了,都可以使用iBATIS对持久层进行重构。因为iBATIS的简单,它不会使您的程序结构过于复杂,这一点O/RM工具或代码生成器未必能够保证,因为它们总是基于对应用程序或数据库所作的某种假设。
最后,iBATIS也适合于大型软件系统。
3.2在大型的,企业级系统中使用iBATIS
首先:iBATIS 不对数据库或是对象模型做任何假设
其次:iBATIS可以有效第处理很大规模的数据。而且支持获取某个范围的数据
最后:iBATIS允许将对象一多种法师映射至数据库
4. 为什么要使用iBATIS
简单:最简单的持久层框架之一
性能
分离关注点,很好的实现程序的架构和分层
分工,sql语句和应用层代码的完全隔离
可移植性,支持三种开发平台Java,Ruby,C#
开源和可信度
5哪些情况不适合用iBATIS ?
(较低层次的框架,ADO.NET,中等层次框架iBATIS,较高层次的框架 O/RM)
(1)完全拥有控制权直至永远
对应用程度和数据库设计拥有完全的控制权,可以使用O/RM工具,如NHibernate
如果失去了对数据库的控制权,那么就该仔细考虑它对我们的持久层策略带来的影响
(2)如果程序中的SQL完全是动态生成的
iBATIS有着很强大的动态SQL特性,支持高级查询,甚至是一些动态的更新功能。但如果程序中的每条语句都是动态生成的,那么您最好还是使用原生的ADO.NET,或者构建自己的框架。
(3)如果不是使用关系型数据库
二、 iBATIS安装和配置
1. 为自己的应用项目添加引用
下载IBatis.DataAccess 和IBtis.DataMapper
找到IBatisNet.Common.dll 、IBatisNet.DataAccess.dll、IBtisNet.DataMapper.dll添加到应用项目中
2. 添加XML文件项
添加了对程序集的引用后,向项目添加三类文件按:
(1) providers.config – DataMapper在该文件中查找您选择的数据库Provider的定义
(2) SqlMap. Xml——包含了SQL查询的映射文件。根据项目的需要,可能会包含一个或是多个类似的文件,eg:Person.xml,Login.xml
(3) SqlMap.config(核心配置文件)——DataMapper配置文件,用于指定SqlMap.xml和providers.config文件的位置。同时还定义了其它的DataMapper配置选项,如缓存。我们需要为项目中的每个数据源编写一个SqlMap.config文件
注意:SqlMap.config、providers.config 文件夹应放在DataMapper运行时可以找到的地方。例如,我们的app.confg和web.config所在的位置
(1) SqlMap.config文件
<?xml version="1.0" encoding="utf-8"?>
<sqlMapConfig xmlns="http://ibatis.apache.org/dataMapper"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<!—配置属性项 (不是必须的)-->
<properties resource="properties.config"/> // 指定文件properties的位置是从应用程序的跟目录加载
url=“” //若是使用url 表示绝对路径
embedded //从程序集的资源中加载
<settings>
<setting useStatementNamespaces="false"/>
<setting cacheModelsEnabled="true"/>
<setting validateSqlMap="false"/>
</settings>
<!-- Not required if providers.config is located in default location -->
<providers embedded="resources.providers.config, IBatisNet.Test"/> //文件的来源 resource
url
//Provider.config文件是位于iBATIS的DataMapper发行包包含了一个标准的providers.config 。将其加载过来
//注意,如果您使用的是SQL Server 2005,那么可以打开MARS(Multiple Active Result Set)选项,即设置allowMARS=”true”,并在连接字符串中添加MultipleActiveResultSets=true。在使用Provider前记住检查一下它的enabled特性值
配置数据库 sqlServer
<database>
<provider name="sqlServer1.1"/>
<dataSource name="NPetshop"
connectionString="user id=${username};
password=${password};
data source=${datasource};
database=${database};"/>
</database>
<alias>
通过<typeAlias>元素,我们可以指定一个简短的别名来代替完全限定的类名
<typeAlias alias="Account" type="IBatisNet.Test.Domain.Account(类全名) , IBatisNet.Test. Domain "/>
<typeAlias alias="YesNoBool"
type="IBatisNet.Test.Domain.YesNoBoolTypeHandlerCallback, IBatisNet.Test"/>
</alias>
<typeHandlers>
<typeHandler type="bool" dbType="Varchar" callback="YesNoBool"/>
</typeHandlers>
<!—连接xml 即各表的SQL语句 -->
<sqlMaps>
<sqlMap resource="${root}Maps/Account.xml"/>
<sqlMap resource="${root}Maps/Category.xml"/>
<sqlMap resource="${root}Maps/Product.xml"/>
</sqlMaps>
</sqlMapConfig>
<properties>元素
有时,配置文件中的同一个值会出现在多处。通常情况下,我们将程序在服务器间迁移时,某些配置选项的值要进行修改。为了更好地管理这些配置选项的值,我们可指定一个标准的属性文件(含有name=value对),将DataMapper的部分配置转移到其中。在属性文件中的值将成为shell变量,可以在DataMapper配置文件和Data Map定义文件中使用。例如,如果属性文件中包含了
<?xml version="1.0" encoding="utf-8" ?>
<settings>
<add key="UserId" value="sa" />
</settings>
那么在DataMapper配置文件(SqlMap.config)中的任何元素都可以使用变量${UserId}来插入值”sa”。例如:
<dataSource name="Northwind" connectionString=”UId=${UserId};”
使用属性文件使生成、测试、部署的过程变得简单。
<setting>元素
框架中有三个默认的设置(选项值)。对一个应用程序合适的设置可能会不适合于另一个程序。在<settings>元素中我们可以配置这些选项,对相应的DataMapper实例进行优化。每一个<settings>特性都有一个默认值,因此,您可以省略其中的特性甚至是整个<settings>元素。<settings>的特性值及其含义为:
<typeHandle>
<typeHandler>元素用于对自定义类型处理器(Custom Type Handler)的配置。Custom Type Handler扩展了DataMapper的功能,可以处理如下的情形:特定于数据库Provider的类型,数据库Provider未处理的类型,在特定程序中特殊设计。
Attribute |
Description |
cacheModelsEnabled |
该选项在全局范围内启用或禁用一个DataMapper实例的所有Cache Model。这在调试时可能会很方便。 Example:cacheModelsEnabled=”true” 默认值 :true(启用) |
useStatementNamespaces |
如果启用该选项,那么在引用映射语句时必须总是使用完全限定的名称,即Sql Map的命名空间和语句的id。如queryForObject(“Namespace.statementId”); Example:useStatementNamespaces=”false” 默认值 :false(禁用) |
validateSqlMap |
该选项在全局范围内启用或禁用了针对Sql Map文件的校验。这在调试时可能会很方便。 |
useReflectionOptimizer |
该选项在全局范围内启用或禁用在访问C#对象属性或字段对反射的使用。反射优化器会提供获取、生成、实例化参数和返回结果对象的类型。 Example:useReflectionOptimizer =”true” 默认值 :true(启用) |
(2) SqlMap.xml
<?xml version="1.0" encoding="utf-8" ?>
<sqlMap namespace="Account" xmlns="http://ibatis.apache.org/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<alias>
<typeAlias alias="Person" type="Model.Person,Model"/>
</alias>
<resultMaps>
<resultMap id="SelectAllResult" class="Person">
<result property="Id" column="PER_ID" />
<result property="FirstName" column="PER_FIRST_NAME" />
<result property="LastName" column="PER_LAST_NAME" />
<result property="BirthDate" column="PER_BIRTH_DATE" />
<result property="WeightInKilograms" column="PER_WEIGHT_KG" />
<result property="HeightInMeters" column="PER_HEIGHT_M" />
</resultMap>
</resultMaps>
<cacheModel id="person-cache" implementation="MEMORY">
<flushInterval hours="24"/>
<flushOnExecute statement="UpdateAccountViaInIineParameters"/>
<flushOnExecute statement="UpdateAccountViaParameterMap"/>
<property name="Type" value="Weak"/>
</cacheModel>
<statements>
<select id="Exists" resultClass="int" parameterClass="int">
select count(1) from PERSON
where PER_ID=#value#
</select>
<insert id="InsertPerson" parameterclass="Person">
<selectKey property="Id" type="post" resultClass="int">
${selectKey}
</selectKey>
insert into Person
( PER_FIRST_NAME,
PER_LAST_NAME,
PER_BIRTH_DATE,
PER_WEIGHT_KG,
PER_HEIGHT_M)
values(#FirstName#,#LastName#,#BirthDate#,#WeightInKilograms#,#HeightInMeters#)
</insert>
<update id="UpdatePerson" parameterclass="Person">
update Person set
PER_FIRST_NAME =#FirstName#,
PER_LAST_NAME =#LastName#,
PER_BIRTH_DATE =#BirthDate#,
PER_WEIGHT_KG=#WeightInKilograms#,
PER_HEIGHT_M=#HeightInMeters#
where
PER_ID = #Id# ?
</update>
<delete id="DeletePerson" parameterclass="Person">
delete from Person where PER_ID=#Id#
</delete>
<select id="SelectAllPerson" resultMap="SelectAllResult" cacheModel="person-cache">
select
PER_ID,
PER_FIRST_NAME,
PER_LAST_NAME,
PER_BIRTH_DATE,
PER_WEIGHT_KG,
PER_HEIGHT_M
from PERSON
</select>
<select id="SelectByPersonId" resultMap ="SelectAllResult" resultClass="Person" parameterClass="int">
select PER_ID,PER_FIRST_NAME,PER_LAST_NAME,PER_BIRTH_DATE,PER_WEIGHT_KG,PER_HEIGHT_M from PERSON
where PER_ID=#value#
</select>
</statements>
</sqlMap>
3. 与VS.NET集成
要在VS.NET的XML编辑器中的Schema和我们的配置文件之间建立关联,应该将Schema文件(SqlMap.xsd,SqlMapConfig.xsd,providers.xsd)添加到VS.NET项目或者VS.NET安装目录。显然第二种选择会让我们一劳永逸。VS.NET安装目录可能是:
C:\Program Files\Microsoft Visual Studio 8\Xml\Schemas(VS.NET 2005)
或
C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Packages\schemas\xml(VS.NET 2003)
或
C:\Program Files\Microsoft Visual Studio .NET\Common7\Packages\schemas\xml(VS.NET 2002)。
三、 使用基础
1. SqlMap的API
相当于我们数据访问层涉及到的方法
ISqlMapper接口有30多个方法:
(1) QueryForObject()方法
object QueryForObject(string statementName, object parameterObject);
object QueryForObject(string statementName, object parameterObject, object resultObject);
需要注意的是,调用QueryForObject()方法时,如果查询结果多于一行,那么iBATIS只会接受第一行记录,其它的记录则忽略不计。
(2)QueryForList()方法
IList QueryForList(string statementName, object parameterObject);
第三个版本返回查询结果的一个子集——跳过skip参数指定数目的记录,然后返回max参数指定数目的结果。因此,如果映射语句本来返回的是100行记录,但您只需要11-20行,那么只需要将skip和max参数分别设置为10、10即可。
(3)QueryForMap()方法
IDictionary QueryForMap(string statementName, object parameterObject, string keyProperty);
Eg:
IDictionary accounts = sqlMapper.QueryForMap("Account.getAll", null, "accountId");
译注:”Map”这个名称是Java里的,按.NET的精神,应该是Dictionary,所以iBATIS.NET提供了该方法的别名方法:QueryForDictionary。
2.映射语句的类型
语句类型:
Select
Insert
Update
Delete
Procedure
Statement :可以包含任意类型的语句
3.使用语句
(1)在内联参数中使用#
第一种方式是使用#语法。下面这个简单的例子演示了如何传入一个简单的内联参数,然后通过accountId获取单个的Account对象:
<select id="getByIdValue" resultClass="Account">
这里的#value#字符串告诉iBATIS,该语句接受一个简单参数。这条语句可以如是调用:
Account account = sqlMapper.QueryForObject(“Account.getByIdValue”, 1) as Account;
(2)使用$
使用本文替换语法
accountList = sqlMapper.QueryForList(“Account.getByLikeCity”, “burg”);
(3)关于SQL注入
对于使用$过程中,倘若我们有用户输入:burg’;drop table Account;--
就表示
我们的那些聪明的用户从数据库中查询出了所有以burg结尾的记录,这还没什么大问题。但他还从数据库删除了一张表(如果只是一个,也算幸运了——如果他真的够聪明,他会知道机会难得,从而会试着删除多张表)。字符串末尾的“--”告诉数据库忽略drop语句后的所有内容,因此该语句不会抛出异常。
如果是由于你的代码问题,让这种事情发生在生产环境中的真实应用程序中,那么这一天你在办公室里就不太好过了。就像我们提到过的,谨慎使用替换($)语法。
(4)自动结果映射
共有三种方式来使用这个特性:单列查询,固定类列表查询以及动态里列表查询
<select id="getAllAccountIdValues" resultClass="int">
List<int> accountIds = sqlMapper.QueryForList<int>(“Account.getAllAccountIdValues”, null);
如果查询所得的字段列表在运行时会发生改变,动态的结果映射也可能会用到。代码清单4.2 演示了这种情况:
3.映射参数
参数映射:定义了一个参数的有序列表,它与查询语句的占位符相匹配。
映射参数的特性:
特性 |
描述 |
property |
表示参数对应的对象的属性名称。如果输入参数对象为IDictionary,则property是相应key的名称。同一property值可以出现多次,这取决于它在语句中出现的次数。 |
column |
用于定义存储过程的参数名称。 |
direction |
可用于指定存储过程参数的方向。其值可以是Input,Output或InputOutput。 |
dbType |
用于显式地指定参数对应的列类型。对于某些操作,一些ADO.NET provider不能判断列的类型,此时dbType必须指定。 此特性仅在列为nullable时是必需的。此外,在显式指定日期类型时也需要此特性。尽管.NET仅有一种日期值类型(System.DateTime),大多数数据库却不止一个。通常情况下,数据库至少有三种不同的日期类型(Date,DateTime,TimeStamp)。为使映射过程能够正确,我们可能需要指定列的dbType。 |
type |
用于指定前面的property的CLR类型。在向存储过程传入InputOput和Output类型的参数时,该特性很有用。 正常情况下,property的类型可通过反射获得,但对于IDictionary类型的参数就无能为力了,此时类型被假定为Object。 |
nullValue |
nullValue可以设置为任何的有效值(这取决于property的类型)。 注意:对于值类型(int,double,datetime等)的属性,它们是不能为null的,那如何向数据库中插入null值呢?可以采用nullValue,比如对于Age列(int类型),我们可以指定nullValue为0,这意味着如果该属性未设置(C#中,int属性的默认值为0),那么会向数据库插入NULL。在.NET2.0中也可以使用nullable类型,此时更为方便。请参考我的小文。 |
size |
设置列的最大尺寸。 |
precision |
设置数字值的精度。 |
scale |
设置小数的位数。 |
typeHandler |
用于自定义类型处理器(Custom Type Handler)。 |
使用内联参数
<statement id="insertProduct" parameterClass="Product">
insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
values (#id#, #description#)
</statement>
使用dbType (类型)
<statement id="insertProduct" parameterClass="Product">
insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
values (#id:int#, #description:VarChar#)
</statement>
下面这个例子指定了nullValue:
<statement id="insertProduct" parameterClass="Product">
insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
values (#id:int:-999999#, #description:VarChar#)
</statement>
类似于Java中的DataMapper,内联参数还有另外一种语法,使用逗号。
<update id="UpdateAccountViaInlineParameters" parameterClass="Account">
update Accounts set
Account_FirstName = #FirstName#,
Account_LastName = #LastName#,
Account_Email = #EmailAddress,type=string,dbType=Varchar,nullValue=no_email@provided.com#
where
Account_ID = #Id#
</update>
标准类型的参数
<statement id="getProduct" parameterClass="System.Int32">
select * from PRODUCT where PRD_ID = #value#
</statement>
假定PRD_ID为数字类型,在调用该语句时,可以传入一个标准的int对象。#value#参数会替换为传入的int值。此处的value仅仅是一个占位符,您可以根据需要将其替换为其它名称。
为方便起见,iBATIS框架提供了基元类型的别名,比如,int可用于代替System.Int32。
IDictionary类型参数
<statement id="getProduct" parameterClass="System.Collections.IDictionary">
select * from PRODUCT
where PRD_CAT_ID = #catId#
and PRD_CODE = #code#
</statement>
四、 执行非查询语句
1.非查询SQL语句相关的API
Insert方法
Object Insert(string statementName, object parameterObject)
statementName 即我们SqlMap.xml中对应的Insert语句的id,
Update方法
int Update(string statementName, object parameterObject);
Delete方法
int Delete(string statementName, object parameterObject);
procedure 存储过程
statement 可以包含任何类型的语句
2. 查询数据
(1)使用内联参数
就是将参数定义在需要使用的地方。(内)
<insert id="insertWithInlineInfo"> //这叫映射语句
insert into account (
accountId,
username, password,
memberSince,
firstName, lastName,
address1, address2,
city, state, postalCode,
country, version)
values (
#accountId:NUMBER#,
#username:VARCHAR#, #password:VARCHAR#,
#memberSince:TIMESTAMP#,
#firstName:VARCHAR#, #lastName:VARCHAR#,
#address1:VARCHAR#, #address2:VARCHAR#,
#city:VARCHAR#, #state:VARCHAR#, #postalCode:VARCHAR#,
#country:VARCHAR#, #version:NUMBER#
)
</insert>
(在单元测试中)执行代码:
Account account = new Account();
account.AccountId = 9999;
account.Username = "inlineins";
account.Password = "poohbear";
account.FirstName = "Inline";
account.LastName = "Example";
SqlMapper.Insert("Account.insertWithInlineInfo", account);
(2)使用外部参数
就是提前将所需的参数定义好
<parameterMap id="fullParameterMapExample" class="Account">
<parameter property="accountId" dbType="NUMBER" />
<parameter property="username" dbType="VARCHAR" />
<parameter property="password" dbType="VARCHAR" />
<parameter property="memberSince" dbType="TIMESTAMP" />
<parameter property="firstName" dbType="VARCHAR" />
<parameter property="lastName" dbType="VARCHAR" />
<parameter property="address1" dbType="VARCHAR" />
<parameter property="address2" dbType="VARCHAR" />
<parameter property="city" dbType="VARCHAR" />
<parameter property="state" dbType="VARCHAR" />
<parameter property="postalCode" dbType="VARCHAR" />
<parameter property="country" dbType="VARCHAR" />
<parameter property="version" dbType="NUMBER" />
</parameterMap>
<insert id="insertWithExternalInfo"
parameterMap="fullParameterMapExample">
insert into account (
accountId,
username, password,
memberSince
firstName, lastName,
address1, address2,
city, state, postalCode,
country, version)
values (?,?,?,?,?,?,?,?,?,?,?,?,?)
</insert>
(3)自动生成主键
<!-- Oracle SEQUENCE Example using .NET 1.1 System.Data.OracleClient -->
<insert id="insertProduct-ORACLE" parameterClass="product">
<selectKey resultClass="int" type="pre" property="Id" >
SELECT STOCKIDSEQUENCE.NEXTVAL AS VALUE FROM DUAL
</selectKey>
insert into PRODUCT (PRD_ID,PRD_DESCRIPTION) values (#id#,#description#)
</insert>
<!-- Microsoft SQL Server IDENTITY Column Example -->
<insert id="insertProduct-MS-SQL" parameterClass="product">
insert into PRODUCT (PRD_DESCRIPTION)
values (#description#)
<selectKey resultClass="int" type="post" property="id" >
select @@IDENTITY as value
</selectKey>
</insert>
3.事务
try
{
sqlMapper.BeginTransaction();
sqlMapper.QueryForObject("Account.insertAndReturn", a);
sqlMapper.CommitTransaction();
}
catch(Exception)
{
sqlMapper.RollBackTransaction();
}
基于JTA 的事务管理机制
外部事物
五、 动态查询机制
动态标签分类
<dynamic>
一元标签
二元标签
<iterate>
1. 动态查询机制
<select id="getUsers"
parameterClass="user"
resultMap="get-user-result">
select
id,
name,
sex
from t_user
<dynamic prepend="WHERE">
<isNotEmpty prepend="AND" property="name"> //判定节点
(name like #name#)
</isNotEmpty>
<isNotEmpty prepend="AND" property="address">
(address like #address#)
</isNotEmpty>
</dynamic>
</select>
通过dynamic 节点,我们定义了一个动态的WHERE 子句。此WHERE 子句中将
可能包含两个针对name 和address 字段的判断条件。而这两个字段是否加入检索取决
于用户所提供的查询条件(字段是否为空[isNotEmpty])。
对于一个典型的Web程序而言,我们通过HttpServletRequest获得表单中的字段名
并将其设入查询参数,如:
user.setName(request.getParameter("name"));
user.setAddress(request.getParameter("address"));
sqlMap.queryForList("User.getUsers", user);
在执行queryForList("User.getUsers", user)时,ibatis 即根据配置文
件中设定的SQL动态生成规则,创建相应的SQL语句。
上面的示例中,我们通过判定节点isNotEmpty,指定了关于name 和address
2.一元判定
Eg: isNotEmpty就是典型的一元判定。
一元判定节点有:
节点名 描述
<isPropertyAvailable> 参数类中是否提供了此属性
<isNotPropertyAvailable> 与<isPropertyAvailable>相反
<isNull> 属性值是否为NULL
<isNotNull> 与<isNull>相反
<isEmpty> 如果属性为Collection或者String,其size是否<1,
3.二元判定
二元判定节点有:
节点名 属性值与compareValues的关系
<isEqual> 相等。
<isNotEqual> 不等。
<isGreaterThan> 大于
<isGreaterEqual> 大于等于
<isLessThan> 小于
<isLessEqual> 小于等于
Eg:
<isGreaterThan prepend="AND" property="age"
compareValue="18">
(age=#age#)
</isGreaterThan>
其中,property="age"指定了属性名”age”,compareValue=”18”指明
了判定值为”18”。
上面判定节点isGreaterThan 对应的语义是:如果age 属性大于
18(compareValue),则在SQL中加入(age=#age#)条件。
4.动态SQl片段
<!-- 动态条件分页查询 -->
<sql id="sql_count">
select count(*)
</sql>
<sql id="sql_select">
select *
</sql>
<sql id="sql_where">
from icp
<dynamic prepend="where">
<isNotEmpty prepend="and" property="name">
name like '%$name$%'
</isNotEmpty>
<isNotEmpty prepend="and" property="path">
path like '%path$%'
</isNotEmpty>
<isNotEmpty prepend="and" property="area_id">
area_id = #area_id#
</isNotEmpty>
<isNotEmpty prepend="and" property="hided">
hided = #hided#
</isNotEmpty>
</dynamic>
<dynamic prepend="">
<isNotNull property="_start">
<isNotNull property="_size">
limit #_start#, #_size#
</isNotNull>
</isNotNull>
</dynamic>
</sql>
<select id="findByParamsForCount" parameterClass="map" resultClass="int">
<include refid="sql_count"/>
<include refid="sql_where"/>
</select>
<select id="findByParams" parameterClass="map" resultMap="icp.result_base">
<include refid="sql_select"/>
<include refid="sql_where"/>
</select>
4. 数字范围查询
5. 所传参数名称是捏造所得,非数据库字段,比如_img_size_ge、_img_size_lt字段
6. <isNotEmpty prepend="and" property="_img_size_ge">
<![CDATA[
img_size >= #_img_size_ge#
]]>
</isNotEmpty>
<isNotEmpty prepend="and" property="_img_size_lt">
<![CDATA[
img_size < #_img_size_lt#
]]>
</isNotEmpty>
多次使用一个参数也是允许的
<isNotEmpty prepend="and" property="_now">
<![CDATA[
execplantime >= #_now#
]]>
</isNotEmpty>
<isNotEmpty prepend="and" property="_now">
<![CDATA[
closeplantime <= #_now#
]]>
</isNotEmpty>
4、时间范围查询
<isNotEmpty prepend="" property="_starttime">
<isNotEmpty prepend="and" property="_endtime">
<![CDATA[
createtime >= #_starttime#
and createtime < #_endtime#
]]>
</isNotEmpty>
</isNotEmpty
6.in查询
<isNotEmpty prepend="and" property="_in_state">
state in ('$_in_state$')
</isNotEmpty>
7.like查询
<isNotEmpty prepend="and" property="chnameone">
(chnameone like '%$chnameone$%' or spellinitial like '%$chnameone$%')
</isNotEmpty>
<isNotEmpty prepend="and" property="chnametwo">
chnametwo like '%$chnametwo$%'
</isNotEmpty>
8.or条件
<isEqual prepend="and" property="_exeable" compareValue="N">
<![CDATA[
(t.finished='11' or t.failure=3)
]]>
</isEqual>
<isEqual prepend="and" property="_exeable" compareValue="Y">
<![CDATA[
t.finished in ('10','19') and t.failure<3
]]>
</isEqual>
9.where子查询
<isNotEmpty prepend="" property="exprogramcode">
<isNotEmpty prepend="" property="isRational">
<isEqual prepend="and" property="isRational" compareValue="N">
code not in
(select t.contentcode
from cms_ccm_programcontent t
where t.contenttype='MZNRLX_MA'
and t.programcode = #exprogramcode#)
</isEqual>
</isNotEmpty>
</isNotEmpty>
<select id="findByProgramcode" parameterClass="string" resultMap="cms_ccm_material.result">
select *
from cms_ccm_material
where code in
(select t.contentcode
from cms_ccm_programcontent t
where t.contenttype = 'MZNRLX_MA'
and programcode = #value#)
order by updatetime desc
</select>
10函数的使用
<!-- 添加 -->
<insert id="insert" parameterClass="RuleMaster">
insert into rulemaster(
name,
createtime,
updatetime,
remark
) values (
#name#,
now(),
now(),
#remark#
)
<selectKey keyProperty="id" resultClass="long">
select LAST_INSERT_ID()
</selectKey>
</insert>
<!-- 更新 -->
<update id="update" parameterClass="RuleMaster">
update rulemaster set
name = #name#,
updatetime = now(),
remark = #remark#
where id = #id#
</update>
多表连接查询
Eg:
两张表Account和Degree,使用Account_ID关联,需要查出两张表的所有纪录
首先:修改实体类,增加以下属性:
然后:修改配置文件,这也是最重要的地方(PS:IBatis.Net中的配置文件真的很强)
在resultMaps节加入:
这里最主要的就是使用了resultMapping属性,resultMapping="Account.Degree-result",其中Account是当前配置文件的namespace:
<sqlMap namespace="Account" ......
在statements节加入:
11.map 结果集
<!-- 动态条件分页查询 -->
<sql id="sql_count">
select count(a.*)
</sql>
<sql id="sql_select">
select a.id vid,
a.img imgurl,
a.img_s imgfile,
b.vfilename vfilename,
b.name name,
c.id sid,
c.url url,
c.filename filename,
c.status status
</sql>
<sql id="sql_where">
From secfiles c, juji b, videoinfo a
where
a.id = b. videoid
and b.id = c.segmentid
and c.status = 0
order by a.id asc,b.id asc,c.sortnum asc
<dynamic prepend="">
<isNotNull property="_start">
<isNotNull property="_size">
limit #_start#, #_size#
</isNotNull>
</isNotNull>
</dynamic>
</sql>
<!-- 返回没有下载的记录总数 -->
<select id="getUndownFilesForCount" parameterClass="map" resultClass="int">
<include refid="sql_count"/>
<include refid="sql_where"/>
</select>
<!-- 返回没有下载的记录 -->
<select id="getUndownFiles" parameterClass="map" resultClass="java.util.HashMap">
<include refid="sql_select"/>
<include refid="sql_where"/>
</select>
六、 高级查询
1.延迟加载
(1)修改:SqlMapConfig.xml 文件<setting>中的的lazyLoadingEnable 的属性为ture
(2)如果想用延迟加载的cglib enhanced 版本,下载并应用到程序路径下,修改<setting>的enhancementEnable属性为true.
但是这是一个全局设置,的SQL map 配置文件都会使用延迟加载
<settings
……
lazyLoadingEnabled=”true”
enhancementEnabled=”true”
……/>
lazyLoadingEnabled设置系统是否实现延迟加载机制,enhancementEnabled设置是否用字节码强制机制,通过字节码强制机制可以为lazy loadding带来性能方面的改进。
提升数据库查询性能的方式有三:
1.分页查询。(最实际有效)
2.延时加载。
3.利用cache查询(对修改次数很少的数据)。
2.避免N+1查询问题
(大数据集问题)
两种方法:1)在iBatis中使用groupBy特性 ;
此种方法查找很快,但是,内存占用仍是个问题
2)使用一个自定义组件RowHandler
Eg:
注意:groupBy属性的指的是一个属性的名字,而不是列名
</select>
3.继承
Mapping Inheritance
Ibatis 支持继承。它是使用一个特殊的映射叫,discitminator
Using a discriminator you can determine the type of class to be instantiated based on a value in the database!
Eg:
如果discrimination没有找到一个值与它的的submaps 匹配,那么就应用它的父result map!如果找到这样的值,那么只有sub map被应用,除非sub maps明确的表明扩展the parent map
<result Map id=”book” class =”testdomain.Book” extends=”document”>
<result property =”pages” column=”……”/>
</resultMap>
3. 其他用途
(other miscellaneous users)
(1)使用<statement>和DDL
(data definition Language)
您可以使用DDL去定义表和索引,实现其他的操作
<statement id=”dropTable”>
Drop Table Account cascade;
</statement>
注意:不能保证您的数据库像这样支持DDL语句
(2)处理大数据集
有一种接口方法可以解决这个问题:Rowhandler
Public interface RowHandler
{
Over handleRow (object valueObject)
}
在映射文件的结果集中,每一行都调用了handleRow.。
使用这个接口,你可以不用一次将大量的数据加载到内存中。只有一行你的代码调用的数据加载到内存中,其他对象都别丢弃了。这个过程反复进行直到所有的结果被处理。
P172
七、 Cache机制
ibatis 的缓存机制使用必须特别谨慎。特别是flushOnExecute 的设定(见
“ibatis 配置”一节中的相关内容),需要考虑到所有可能引起实际数据与缓存数据不符的操作。如本模块中其他Statement 对数据的更新,其他模块对数据的更新,甚
至第三方系统对数据的更新。否则,脏数据的出现将为系统的正常运行造成极大隐患。如果不能完全确定数据更新操作的波及范围,建议避免Cache 的盲目使用。
结合cacheModel 来看:
<cacheModel
id="product-cache"
type ="LRU"
readOnly="true"
serialize="false">
</cacheModel>
可以看到,Cache 有如下几个比较重要的属性:
. readOnly
. serialize
. type
readOnly 值的是缓存中的数据对象是否只读。这里的只读并不是意味着数据对象一旦放入缓存中就无法再对数据进行修改。而是当数据对象发生变化的时候,如数据对象的某个属性发生了变化,则此数据对象就将被从缓存中废除,下次需要重新从数据库读取数据,构造新的数据对象。readOnly="false" 则意味着缓存中的数据对象可更新,如user 对象的name 属性发生改变。
只读Cache 能提供更高的读取性能,但一旦数据发生改变,则效率降低。系统设计时需根据系统的实际情况(数据发生更新的概率有多大)来决定Cache 的读写策略。
serialize
如果需要全局的数据缓存,CacheModel 的serialize 属性必须被设为true 。否则数据
缓存只对当前Session(可简单理解为当前线程)有效,局部缓存对系统的整体性能提
升有限。
在serialize="true" 的情况下,如果有多个Session 同时从Cache 中读取某个
数据对象,Cache 将为每个Session 返回一个对象的复本,也就是说,每个Session 将
得到包含相同信息的不同对象实例。因而Session 可以对其从Cache 获得的数据进行
存取而无需担心多线程并发情况下的同步冲突。
1.Memory
<cacheModel id="user_cache" type="MEMORY">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="reference-type" value="WEAK" />
</cacheModel>
reference-type 属性可以有以下几种配置:
1. STRONG
即基于传统的Java 对象引用机制,除非对Cache 显式清空(如到了flushInterval
设定的时间;执行了flushOnExecute 所指定的方法;或代码中对Cache 执行了清除
操作等),否则引用将被持续保留。
此类型的设定适用于缓存常用的数据对象, 或者当前系统内存非常充裕的情况。
2. SOFT
基于SoftReference 的缓存实现,只有JVM 内存不足的时候,才会对缓冲池中的数
据对象进行回收。
此类型的设定适用于系统内存较为充裕,且系统并发量比较稳定的情况。
3. WEAK
基于WeakReference 的缓存实现,当JVM 垃圾回收时,缓存中的数据对象将被JVM
收回。
一般情况下,可以采用WEAK 的MEMORY 型Cache 配置。
2.LRU 型Cache
当Cache 达到预先设定的最大容量时,ibatis 会按照“最少使用”原则将使用频率最少的对象从缓冲中清除。
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
可配置的参数有:
. flushInterval 指定了多长时间清除缓存,上例中指定每24小时强行清空缓存区的所有内容。
. size Cache 的最大容量。
3.FIFO 型Cache
先进先出型缓存,最先放入Cache 中的数据将被最先废除。可配置参数与LRU 型相同:
<cacheModel id="userCache" type="FIFO">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
4. OSCache
与上面几种类型的Cache 不同,OSCache 来自第三方组织Opensymphony 。可以通过以下网址获得OSCache 的最新版本(http://www.opensymphony.com/oscache/)。
在生产部署时,建议采用OSCache,OSCache 是得到了广泛使用的开源Cache 实现(Hibernate 中也提供了对OSCache 的支持),它基于更加可靠高效的设计,更重要的是,
最新版本的OSCache 已经支持Cache 集群。如果系统需要部署在集群中,或者需要部署在
多机负载均衡模式的环境中以获得性能上的优势,那么OSCache 在这里则是不二之选。
Ibatis 中对于OSCache 的配置相当简单:
<cacheModel id="userCache" type="OSCACHE">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
OSCache 拥有自己的配置文件(oscache.properties)J。
下面是一个典型的OSCache 配置文件:
#是否使用内存作为缓存空间
cache.memory=true
#缓存管理事件监听器,通过这个监听器可以获知当前Cache 的运行情况
cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.JMSBroa
dcastingListener
#如果使用磁盘缓存(cache.memory=false),则需要指定磁盘存储接口实现
#cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.Disk
PersistenceListener
# 磁盘缓存所使用的文件存储路径
# cache.path=c:\\myapp\\cache
# 缓存调度算法,可选的有LRU,FIFO 和无限缓存(UnlimitedCache)
# cache.algorithm=com.opensymphony.oscache.base.algorithm.FIFOCache
# cache.algorithm=com.opensymphony.oscache.base.algorithm.UnlimitedCache
cache.algorithm=com.opensymphony.oscache.base.algorithm.LRUCache
#内存缓存的最大容量
cache.capacity=1000
# 是否限制磁盘缓存的容量
# cache.unlimited.disk=false
# 基于JMS 的集群缓存同步配置
#cache.cluster.jms.topic.factory=java:comp/env/jms/TopicConnectionFactory
#cache.cluster.jms.topic.name=java:comp/env/jms/OSCacheTopic
#cache.cluster.jms.node.name=node1
# 基于JAVAGROUP 的集群缓存同步配置
#cache.cluster.properties=UDP(mcast_addr=231.12.21.132;mcast_port=45566;ip_
ttl=32;mcast_send_buf_size=150000;mcast_recv_buf_size=80000):PING(timeout
=2000;num_initial_members=3):MERGE2(min_interval=5000;max_interval=10000
):FD_SOCK:VERIFY_SUSPECT(timeout=1500):pbcast.NAKACK(gc_lag=50;retransm
it_timeout=300,600,1200,2400,4800):pbcast.STABLE(desired_avg_gossip=20000):
UNICAST(timeout=5000):FRAG(frag_size=8096;down_thread=false;up_thread=fal
se):pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_loc
al_addr=true)
#cache.cluster.multicast.ip=231.12.21.132
配置好之后,将此文件放在CLASSPATH 中,OSCache 在初始化时会自动找到此
文件并根据其中的配置创建缓存实例。
八、 其他
1.延迟加载
延迟加载(Lazy Loading)机制。即当真正需要数据的时候,才加
载数据。延迟加载机制能为我们的系统性能带来极大的提升。
配置文件
<settings ⑴
……
enhancementEnabled="true"
lazyLoadingEnabled="true"
……
/>
lazyLoadingEnabled 设定了系统是否使用延迟加载
机制;
enhancementEnabled 设定是否启用字节码强化机制(通过字节码强化机制可
以为Lazy Loading 带来性能方面的改进。
2.什么是Dao
九、 应用问题
1.获得DataSet类型的函
(如何在IBatis.Net中返回DataSet以及一些相关的内容)
public static DataSet QueryForDataSet(string statementName, object paramObject)
{
DataSet ds = new DataSet();
ISqlMapper mapper = GetMapper();
IMappedStatement statement = mapper.GetMappedStatement(statementName);
if (!mapper.IsSessionStarted)
{
mapper.OpenConnection();
}
RequestScope scope = statement.Statement.Sql.GetRequestScope(statement, paramObject, mapper.LocalSession);
statement.PreparedCommand.Create(scope, mapper.LocalSession, statement.Statement, paramObject);
mapper.LocalSession.CreateDataAdapter(scope.IDbCommand).Fill(ds);
return ds;
}
2.获得SQL语句
1. public static string GetSql(string statementName, object paramObject)
{
ISqlMapper mapper = GetMapper();
IMappedStatement statement = mapper.GetMappedStatement(statementName);
if (!mapper.IsSessionStarted)
{
mapper.OpenConnection();
}
RequestScope scope = statement.Statement.Sql.GetRequestScope(statement, paramObject, mapper.LocalSession);
return scope.PreparedStatement.PreparedSql;
}
4. xml文件的参数
parameterClass 参数类。指定了参数的完整类名(包括包路径)。可通过别名避免每次重复书写冗长的类名。
resultClass 结果类。指定结果类型的完整类名(包括包路径)可通过别名避免每次重复书写冗长的类名。
resultMap 结果映射,需结合resultMap 节点对映射关系
parameterMap 参数映射,需结合parameterMap 节点对映射
对于存储过程之外的statement 而言,建议使用parameterClass 作为参数配置方式
一般而言,对于insert 、update 、delete 、select 语句,优先采用parameterClass
和resultClass 。
parameterMap 使用较少,而resultMap 则大多用于嵌套查询以及存储过程的
5. 存储过程
SQL Map通过<procedure>元素支持存储过程。下面的例子说明如何使用具有输出参数的存储过程。 <parameterMap id="swapParameters" class="map" >
<parameter property="email1" jdbcType="VARCHAR" javaType="java.lang.String" mode="INOUT"/>
<parameter property="email2" jdbcType="VARCHAR" javaType="java.lang.String" mode="INOUT"/>
</parameterMap>
<procedure id="swapEmailAddresses" parameterMap="swapParameters" > {call swap_email_address (?, ?)} <
十、 遇到的问题总结
1. 版本问题
iBatis的二进制文件不与.NET FrameWork 4.0 (即vs2010)兼容
问题描述:
我们在“引用”中很好的引用了IBatisNet.Common.dll 、IBatisNet.DataAccess.dll、IBtisNet.DataMapper.dll,而且在程序中也再次成功的应用,一旦运行,便出现没有引用相应的程序集。
解决:
修改项目的框架,可以改为.NETFrameWork 3.5
右击项目名称——>属性——>点击目标框架——>选择.NETFrameWork 3.5
2. 加载配置文件不成功
(1)基本的加载代码不会错,那么就是SqlMap.config 配置文件 或是SqlMap.xml 文件写错了。
(2) 使用SQlServer数据库
<database>
<provider name="sqlServer1.1"/>
<dataSource name="NPetshop"
connectionString="user id=${username};
password=${password};
data source=${datasource};
database=${database};"/>
</database>
我的错误是“文件中无datasource”,很明显,一个s字母未大写导致的。
此外,连接数据和我们使用ADO.NET完全一样,可以使用windows身份,也可使用用户密码身份
Eg:
<database>
<provider name="sqlServer2005"/>
<dataSource name="iBatisNet" connectionString="Data Source=.;Initial Catalog=iBatisDemo;Integrated Security=True" />
3. SqlMap.xml的映射语句
那些映射语句看上去真的很简单,但是在实际运用中很出现很多意想不到的问题。
解决方法,透彻了解xml文件涉及到的属性
4. <![CDATA[……]]> 含义
通过<![CDATA[……]]>节点,可以避免SQL 中与XML 规范相冲突的字符对
XML 映射文件的合法性造成影响。
什么时候使用?