IBATIS总结

来源:互联网 发布:中国强硬外交 知乎 编辑:程序博客网 时间:2024/05/08 03:01

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=AddresssqlMap.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语句和应用层代码的完全隔离

可移植性,支持三种开发平台JavaRubyC#

开源和可信度

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.dllIBtisNet.DataMapper.dll添加到应用项目中

2.     添加XML文件项

添加了对程序集的引用后,向项目添加三类文件按:

                                                                                   

(1)        providers.config – DataMapper在该文件中查找您选择的数据库Provider的定义

 

(2)        SqlMap. Xml——包含了SQL查询的映射文件。根据项目的需要,可能会包含一个或是多个类似的文件,eg:Person.xml,Login.xml

(3)        SqlMap.config(核心配置文件)——DataMapper配置文件,用于指定SqlMap.xmlproviders.config文件的位置。同时还定义了其它的DataMapper配置选项,如缓存。我们需要为项目中的每个数据源编写一个SqlMap.config文件                                                                            

                                                                                       

注意:SqlMap.configproviders.config文件夹应放在DataMapper运行时可以找到的地方。例如,我们的app.confgweb.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文件是位于iBATISDataMapper发行包包含了一个标准的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。这在调试时可能会很方便。

ExamplecacheModelsEnabled=true

默认值true(启用)

useStatementNamespaces

如果启用该选项,那么在引用映射语句时必须总是使用完全限定的名称,即Sql Map的命名空间和语句的id。如queryForObject(“Namespace.statementId”);

ExampleuseStatementNamespaces=false

默认值false(禁用)

validateSqlMap

该选项在全局范围内启用或禁用了针对Sql Map文件的校验。这在调试时可能会很方便。

useReflectionOptimizer

该选项在全局范围内启用或禁用在访问C#对象属性或字段对反射的使用。反射优化器会提供获取、生成、实例化参数和返回结果对象的类型。

Example:useReflectionOptimizer =”true”

默认值 :true(启用)

 

 

(2) SqlMap.xml

 

 

 

 

<?xmlversion="1.0"encoding="utf-8" ?>

 

<sqlMapnamespace="Account"xmlns="http://ibatis.apache.org/mapping"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >

 

  <alias>

    <typeAliasalias="Person"type="Model.Person,Model"/>

    </alias>

 

 

  <resultMaps>

 

    <resultMapid="SelectAllResult"class="Person">

      <resultproperty="Id"column="PER_ID" />

      <resultproperty="FirstName"column="PER_FIRST_NAME" />

      <resultproperty="LastName"column="PER_LAST_NAME" />

      <resultproperty="BirthDate"column="PER_BIRTH_DATE" />

      <resultproperty="WeightInKilograms"column="PER_WEIGHT_KG" />

      <resultproperty="HeightInMeters"column="PER_HEIGHT_M" />

    </resultMap>

  </resultMaps>

 

 

   <cacheModelid="person-cache"implementation="MEMORY">

       <flushIntervalhours="24"/>

       <flushOnExecutestatement="UpdateAccountViaInIineParameters"/>

    <flushOnExecutestatement="UpdateAccountViaParameterMap"/>

    <propertyname="Type"value="Weak"/>

 

  </cacheModel>

 

   <statements>

 

 

    <selectid="Exists"resultClass="int"parameterClass="int">

      select count(1) from PERSON

      where PER_ID=#value#

    </select>

 

      <insertid="InsertPerson"parameterclass="Person">

      <selectKeyproperty="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>

 

    <updateid="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>

 

    <deleteid="DeletePerson"parameterclass="Person">

      delete from Person where PER_ID=#Id#

    </delete>

 

        <selectid="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>

 

 

 

    <selectid="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.NETXML编辑器中的Schema和我们的配置文件之间建立关联,应该将Schema文件(SqlMap.xsdSqlMapConfig.xsdproviders.xsd)添加到VS.NET项目或者VS.NET安装目录。显然第二种选择会让我们一劳永逸。VS.NET安装目录可能是:

C:\Program Files\Microsoft Visual Studio 8\Xml\SchemasVS.NET 2005

C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Packages\schemas\xmlVS.NET 2003

C:\Program Files\Microsoft Visual Studio .NET\Common7\Packages\schemas\xmlVS.NET 2002)。

 

 

三、        使用基础

1.     SqlMapAPI

相当于我们数据访问层涉及到的方法

  ISqlMapper接口有30多个方法:

1 QueryForObject()方法

          object QueryForObject(string statementName, object parameterObject);
object QueryForObject(string statementName, object parameterObject, object
 resultObject);
T QueryForObject<T>(string statementName, object
 parameterObject);
T QueryForObject<T>(string statementName, object parameterObject, T instanceObject);

                                                                         

需要注意的是,调用QueryForObject()方法时,如果查询结果多于一行,那么iBATIS只会接受第一行记录,其它的记录则忽略不计。

2QueryForList()方法

 

IList QueryForList(string statementName, object parameterObject);
void QueryForList(string statementName, object
 parameterObject, IList resultObject);
IList QueryForList(
string statementName, object parameterObject, int skipResults, int
 maxResults);

IList<T> QueryForList<T>(
string statementName, object parameterObject);
void QueryForList<T>(string statementName, object
 parameterObject, IList<T> resultObject);
IList<T> QueryForList<T>(
string statementName, object parameterObject, int skipResults, int maxResults);

 

第三个版本返回查询结果的一个子集——跳过skip参数指定数目的记录,然后返回max参数指定数目的结果。因此,如果映射语句本来返回的是100行记录,但您只需要11-20行,那么只需要将skip和max参数分别设置为10、10即可。

3QueryForMap()方法

 

IDictionary QueryForMap(string statementName, object parameterObject, string keyProperty);
IDictionary QueryForMap(
string statementName, object parameterObject, string keyProperty, string valueProperty);

 

Eg

IDictionary accounts = sqlMapper.QueryForMap("Account.getAll", null, "accountId");
accounts = sqlMapper.QueryForMap("Account.getAll", null, "accountId", "accountName");

译注:”Map”这个名称是Java里的,按.NET的精神,应该是Dictionary,所以iBATIS.NET提供了该方法的别名方法:QueryForDictionary。

 

 

2.映射语句的类型

语句类型:

Select

Insert

Update

Delete

Procedure

Statement  :可以包含任意类型的语句

 

3.使用语句

1)在内联参数中使用#

 

第一种方式是使用#语法。下面这个简单的例子演示了如何传入一个简单的内联参数,然后通过accountId获取单个的Account对象:

<select id="getByIdValue" resultClass="Account">
    select
    accountId,
    username,
    password,
    firstName,
    lastName,
    address1,
    address2,
    city,
    state,
    postalCode,
    country
    from Account
    where accountId = #value#
</select>

 

 

 

这里的#value#字符串告诉iBATIS,该语句接受一个简单参数。这条语句可以如是调用:

Account account = sqlMapper.QueryForObject(“Account.getByIdValue”, 1) as Account;

2)使用$

使用本文替换语法

<select id="getByLikeCity" resultClass="Account">
    select
    accountId,
    username,
    password,
    firstName,
    lastName,
    address1,
    address2,
    city,
    state,
    postalCode,
    country
    from Account
    where city like ‘%$value$%’
</select>

 

accountList = sqlMapper.QueryForList(“Account.getByLikeCity”, “burg”);

 

3)关于SQL注入

对于使用$过程中,倘若我们有用户输入:burg’drop table Account;--

就表示

 

我们的那些聪明的用户从数据库中查询出了所有以burg结尾的记录,这还没什么大问题。但他还从数据库删除了一张表(如果只是一个,也算幸运了——如果他真的够聪明,他会知道机会难得,从而会试着删除多张表)。字符串末尾的“--”告诉数据库忽略drop语句后的所有内容,因此该语句不会抛出异常。

如果是由于你的代码问题,让这种事情发生在生产环境中的真实应用程序中,那么这一天你在办公室里就不太好过了。就像我们提到过的,谨慎使用替换($)语法。

4)自动结果映射

共有三种方式来使用这个特性:单列查询,固定类列表查询以及动态里列表查询

<select id="getAllAccountIdValues" resultClass="int">
Select accountId from Account
</select>

 

List<int> accountIds = sqlMapper.QueryForList<int>(“Account.getAllAccountIdValues”, null);

 

如果查询所得的字段列表在运行时会发生改变,动态的结果映射也可能会用到。代码清单4.2演示了这种情况:

<select id="getAccountRemapExample" resultClass="hashtable" >
    select
    accountId,
    username,
    <dynamic>
        <isEqual property="includePassword" compareValue="true" >
            password,
        </isEqual>
    </dynamic>
    firstName,
    lastName
    from Account
    <dynamic prepend=" where ">
        <isNotEmpty property="city">
            city like #city#
        </isNotEmpty>
        <isNotNull property="accountId" prepend=" and ">
            accountId = #accountId#
        </isNotNull>
    </dynamic>
</select>

 

 

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 子句中将
可能包含两个针对nameaddress 字段的判断条件。而这两个字段是否加入检索取决
于用户所提供的查询条件(字段是否为空[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,指定了关于nameaddress

 

 

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片段

 

 

 <!--动态条件分页查询-->
        
<sqlid="sql_count">
                select count(*)
        
</sql>

 

 
        <sqlid="sql_select">
                select *
        
</sql>

 

 
        <sqlid="sql_where">
                from icp
                
<dynamicprepend="where">
                        
<isNotEmptyprepend="and"property="name">
                                name like '%$name$%'
                        
</isNotEmpty>
                        
<isNotEmptyprepend="and"property="path">
                                path like '%path$%'
                        
</isNotEmpty>
                        
<isNotEmptyprepend="and"property="area_id">
                                area_id = #area_id#
                        
</isNotEmpty>
                        
<isNotEmptyprepend="and"property="hided">
                                hided = #hided#
                        
</isNotEmpty>
                
</dynamic>
                
<dynamic prepend="">
                        
<isNotNullproperty="_start">
                                
<isNotNullproperty="_size">
                                        limit #_start#, #_size#
                                
</isNotNull>
                        
</isNotNull>
                
</dynamic>
        
</sql>

 


        
<selectid="findByParamsForCount"parameterClass="map"resultClass="int">
                
<includerefid="sql_count"/>
                
<includerefid="sql_where"/>
        
</select>
       

 

 

 <selectid="findByParams"parameterClass="map"resultMap="icp.result_base">
                
<includerefid="sql_select"/>
                
<includerefid="sql_where"/>
        
</select>

 

 

4.     数字范围查询

5.     所传参数名称是捏造所得,非数据库字段,比如_img_size_ge_img_size_lt字段

6.                            <isNotEmptyprepend="and"property="_img_size_ge">
                                <![CDATA[
                                img_size >= #_img_size_ge#
                        ]]>
                        
</isNotEmpty>
                        
<isNotEmptyprepend="and"property="_img_size_lt">
                                <![CDATA[
                                img_size
< #_img_size_lt#
                        ]]
>
                        
</isNotEmpty>

 

 

多次使用一个参数也是允许的

                        <isNotEmptyprepend="and"property="_now">
                                <![CDATA[
                                            execplantime >= #_now#
                                     ]]>
                        
</isNotEmpty>
                        
<isNotEmptyprepend="and"property="_now">
                                <![CDATA[
                                            closeplantime
<= #_now#
                                     ]]
>
                        
</isNotEmpty>

 

 

 

4、时间范围查询

                        <isNotEmptyprepend="" property="_starttime">
                                
<isNotEmptyprepend="and"property="_endtime">
                                        <![CDATA[
                                        createtime >= #_starttime#
                                        and createtime
< #_endtime#
                                 ]]
>
                                
</isNotEmpty>
                        
</isNotEmpty

 

 

 

 

 

 

6.in查询

                        <isNotEmptyprepend="and"property="_in_state">
                                state in ('$_in_state$')
                        
</isNotEmpty>

 

7.like查询

                        <isNotEmptyprepend="and"property="chnameone">
                                (chnameone like '%$chnameone$%' or spellinitial like '%$chnameone$%')
                        
</isNotEmpty>
                        
<isNotEmptyprepend="and"property="chnametwo">
                                chnametwo like '%$chnametwo$%'
                        
</isNotEmpty>

 

 

 

8.or条件

                        <isEqualprepend="and"property="_exeable"compareValue="N">
                                <![CDATA[
                                (t.finished='11'    or t.failure=3)
                        ]]>
                        
</isEqual>

 

                        <isEqualprepend="and"property="_exeable"compareValue="Y">
                                <![CDATA[
                                t.finished in ('10','19') and t.failure
<3
                        ]]
>
                        
</isEqual>

 

 

9.where子查询

                        <isNotEmptyprepend="" property="exprogramcode">
                                
<isNotEmptyprepend="" property="isRational">
                                        
<isEqualprepend="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>

    <selectid="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函数的使用

 <!--添加-->
        
<insertid="insert"parameterClass="RuleMaster">
                insert into rulemaster(
                name,
                createtime,
                updatetime,
                remark
                ) values (
                #name#,
                now(),
                now(),

                #remark#
                )
                
<selectKeykeyProperty="id"resultClass="long">
                        select LAST_INSERT_ID()
                
</selectKey>
        
</insert>

 


        <!--
更新-->
        
<updateid="update"parameterClass="RuleMaster">
                update rulemaster set
                name = #name#,
                updatetime = now(),
                remark = #remark#
                where id = #id#
        
</update>

 

多表连接查询

Eg:

两张表AccountDegree,使用Account_ID关联,需要查出两张表的所有纪录

首先:修改实体类,增加以下属性:

        private Degree _degree;
        public Degree Degree
        {
            get
            {
                return _degree;
            }
            set
            {
                _degree = value;
            }
        }

然后:修改配置文件,这也是最重要的地方(PSIBatis.Net中的配置文件真的很强)
resultMaps节加入:

    <resultMap id="com2result"  class="Account" >
      <result property="Id"           column="Account_ID"/>
      <result property="FirstName"    column="Account_FirstName"/>
      <result property="LastName"     column="Account_LastName"/>
      <result property="EmailAddress" column="Account_Email" nullValue="no_email@provided.com"/>
      <result property="Degree"  resultMapping="Account.Degree-result"/>
    </resultMap>

    <resultMap id="Degree-result"  class="Degree">
      <result property="Id"           column="Account_ID"/>
      <result property="DegreeName"    column="DegreeName"/>
    </resultMap>

这里最主要的就是使用了resultMapping属性,resultMapping="Account.Degree-result",其中Account是当前配置文件的namespace
<sqlMap namespace="Account"  ......

statements节加入:

    <select id="GetCom2Tables"
     resultMap="com2result">
      select Accounts.*, Degree.*
      from Accounts,Degree
      where Accounts.Account_ID = Degree.Account_ID
    </select>

 

11.map 结果集

 

 

 <!--动态条件分页查询-->
        
<sqlid="sql_count">
                select count(a.*)
        
</sql>
        
<sqlid="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>
        
<sqlid="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="">
                        
<isNotNullproperty="_start">
                                
<isNotNullproperty="_size">
                                        limit #_start#, #_size#
                                
</isNotNull>
                        
</isNotNull>
                
</dynamic>
        
</sql>
        <!--
返回没有下载的记录总数-->
        
<selectid="getUndownFilesForCount"parameterClass="map"resultClass="int">
                
<includerefid="sql_count"/>
                
<includerefid="sql_where"/>
        
</select>
        <!--
返回没有下载的记录-->
        
<selectid="getUndownFiles"parameterClass="map"resultClass="java.util.HashMap">
                
<includerefid="sql_select"/>
                
<includerefid="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 MEMORYCache 配置。

 

2LRU 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/)。

在生产部署时,建议采用OSCacheOSCache是得到了广泛使用的开源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.propertiesJ

下面是一个典型的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 updatedelete 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.dllIBtisNet.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>

    <providername="sqlServer2005"/>

    <dataSource name="iBatisNet" connectionString="Data Source=.;Initial Catalog=iBatisDemo;Integrated Security=True" />

 

3.     SqlMap.xml的映射语句

那些映射语句看上去真的很简单,但是在实际运用中很出现很多意想不到的问题。

解决方法,透彻了解xml文件涉及到的属性

4.     <![CDATA[……]]>含义

通过<![CDATA[……]]>节点,可以避免SQL中与XML规范相冲突的字符对

XML 映射文件的合法性造成影响。

什么时候使用?

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 u盘密码忘了怎么办 京东会员号被黑怎么办 淘宝企业店铺三证不合一怎么办 淘宝企业店铺营业执照注销了怎么办 不想开淘宝企业店铺了怎么办 id图片跨页排版怎么办 合约机不想要了怎么办? 移动A3手机老卡怎么办 中国移动手机a3很卡怎么办 移动手机a4好卡怎么办 红米手机卡顿反应慢怎么办 红米3s网速慢怎么办 红米4a内存不足怎么办 红米3s手机发热怎么办 魅蓝s6信号差怎么办 oppo手机媒体音量没声音怎么办 红米note3反应慢怎么办 红米4g信号差怎么办 红米4g网速慢怎么办 红米24g信号不好怎么办 红米54g信号不稳定怎么办 红米4a玩游戏卡怎么办 红米4x卡顿怎么办 红米主板烧了怎么办 红米3按键失灵怎么办 l安卓手机运存不够用怎么办 红米2屏幕失灵怎么办 红米手机电池不耐用怎么办 红米手机没内存怎么办 红米2a卡顿怎么办 红米2a手机卡顿怎么办 红米5a内存不足怎么办 红米note1s内存不够怎么办 红米2手机没内存怎么办 红米note显示无服务怎么办 红米手机死屏了怎么办 红米4a信号不好怎么办 小米2a开不了机怎么办 红米4x屏幕失灵怎么办 红米4x外屏坏了怎么办 honor手机开不了机怎么办