zoukankan      html  css  js  c++  java
  • mybatis

    MyBatis

    https://www.w3cschool.cn/mybatis/

    MyBatis 是支持定制化 SQL存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

    MyBatis功能架构

    image-20200325201005541

    1. 解除sql与程序代码的耦合:通过提供DAL层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

    2. 提供映射标签,支持对象与数据库的orm字段关系映射

    3. 提供对象关系映射标签,支持对象关系组建维护

    4. 提供xml标签,支持编写动态sql

    MyBatis XML配置

    properties

    项目文件下创建xml文件,这里命名为mybatis.xml

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC" />
    <dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/mysql" />
    <property name="username" value="root" />
    <property name="password" value="root" />
    </dataSource>
    </environment>
    </environments>
    </configuration>

    其中的属性值可以通过properties文件进行配置,文件位置以eclipse为例

    image-20200325213013933

    image-20200325213128599

    key=value形式,设置xml里的属性值。

    xml中对应的形式是:

      <property name="driver" value="${driver}"/>
     <property name="url" value="${url}"/>
     <property name="username" value="${username}"/>
     <property name="password" value="${password}"/>

    mybatis加载配置的顺序

    1.读取上面提到的properties文件

    2.读取直接写在xml文件中的属性

    所以在properties文件中的属性优先级更高,会覆盖2中的值。

    settings

    mybatis的参数设置

    设置参数描述有效值
    cacheEnabled 该配置影响的所有映射器中配置的缓存的全局开关 true,false
    lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 true,false
    aggressiveLazyLoading 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。 true,false
    multipleResultSetsEnabled 是否允许单一语句返回多结果集(需要兼容驱动)。 true,false
    useColumnLabel 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 true,false
    useGeneratedKeys 允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 true,false
    autoMappingBehavior 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 NONE, PARTIAL, FULL
    defaultExecutorType 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 SIMPLE REUSE BATCH
    defaultStatementTimeout 设置超时时间,它决定驱动等待数据库响应的秒数。 Any positive integer
    safeRowBoundsEnabled 允许在嵌套语句中使用分页(RowBounds)。 true,false
    mapUnderscoreToCamelCase 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 true,false
    localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 SESSION,STATEMENT
    jdbcTypeForNull 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER
    lazyLoadTriggerMethods 指定哪个对象的方法触发一次延迟加载 A method name list separated by commas,equals,clone,hashCode,toString
    defaultScriptingLanguage 指定动态 SQL 生成的默认语言  
    callSettersOnNulls 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 true,false
    logPrefix 指定 MyBatis 增加到日志名称的前缀]()。 Any String
    logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J,LOG4J,LOG4J2,JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING,NO_LOGGING
    proxyFactory 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 CGLIB JAVASSIST

    settings示例

    <settings>
     <setting name="cacheEnabled" value="true"/>
     <setting name="lazyLoadingEnabled" value="true"/>
     <setting name="multipleResultSetsEnabled" value="true"/>
     <setting name="useColumnLabel" value="true"/>
     <setting name="useGeneratedKeys" value="false"/>
     <setting name="autoMappingBehavior" value="PARTIAL"/>
     <setting name="defaultExecutorType" value="SIMPLE"/>
     <setting name="defaultStatementTimeout" value="25"/>
     <setting name="safeRowBoundsEnabled" value="false"/>
     <setting name="mapUnderscoreToCamelCase" value="false"/>
     <setting name="localCacheScope" value="SESSION"/>
     <setting name="jdbcTypeForNull" value="OTHER"/>
     <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>

    typeAliases

    类别名
    直接别名
    <typeAliases>
     <typeAlias alias="Author" type="domain.blog.Author"/>
     <typeAlias alias="Blog" type="domain.blog.Blog"/>
     <typeAlias alias="Comment" type="domain.blog.Comment"/>
     <typeAlias alias="Post" type="domain.blog.Post"/>
     <typeAlias alias="Section" type="domain.blog.Section"/>
     <typeAlias alias="Tag" type="domain.blog.Tag"/>
    </typeAliases>
    间接别名

    指定包名

    <typeAliases>
     <package name="domain.blog"/>
    </typeAliases>

    一个包中可能有多个类,在类上没有注解的情况下,类的别名是类名(若首字母是大写字母,自动转为小写字母)。有注解的情况下,会把注解的作为别名。

    数据类型别名
    别名映射的类型
    _byte byte
    _long long
    _short short
    _int int
    _integer int
    _double double
    _float float
    _boolean boolean
    string String
    byte Byte
    long Long
    short Short
    int Integer
    integer Integer
    double Double
    float Float
    boolean Boolean
    date Date
    decimal BigDecimal
    bigdecimal BigDecimal
    object Object
    map Map
    hashmap HashMap
    list List
    arraylist ArrayList
    collection Collection
    iterator Iterator

    typeHandlers

    数据库数据类型和Java数据类型的对应关系

    类型处理器Java 类型JDBC 类型
    BooleanTypeHandler java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
    ByteTypeHandler java.lang.Byte, byte 数据库兼容的 NUMERICBYTE
    ShortTypeHandler java.lang.Short, short 数据库兼容的 NUMERICSHORT INTEGER
    IntegerTypeHandler java.lang.Integer, int 数据库兼容的 NUMERICINTEGER
    LongTypeHandler java.lang.Long, long 数据库兼容的 NUMERICLONG INTEGER
    FloatTypeHandler java.lang.Float, float 数据库兼容的 NUMERICFLOAT
    DoubleTypeHandler java.lang.Double, double 数据库兼容的 NUMERICDOUBLE
    BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERICDECIMAL
    StringTypeHandler java.lang.String CHAR, VARCHAR
    ClobTypeHandler java.lang.String CLOB, LONGVARCHAR
    NStringTypeHandler java.lang.String NVARCHAR, NCHAR
    NClobTypeHandler java.lang.String NCLOB
    ByteArrayTypeHandler byte[] 数据库兼容的字节流类型
    BlobTypeHandler byte[] BLOB, LONGVARBINARY
    DateTypeHandler java.util.Date TIMESTAMP
    DateOnlyTypeHandler java.util.Date DATE
    TimeOnlyTypeHandler java.util.Date TIME
    SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
    SqlDateTypeHandler java.sql.Date DATE
    SqlTimeTypeHandler java.sql.Time TIME
    ObjectTypeHandler Any OTHER 或未指定类型
    EnumTypeHandler Enumeration Type VARCHAR-任何兼容的字符串类型,存储枚举的名称(而不是索引)
    EnumOrdinalTypeHandler Enumeration Type 任何兼容的 NUMERICDOUBLE 类型,存储枚举的索引(而不是名称)。
    自定义数据类型

    实现org.apache.ibatis.type.TypeHandler接口

    继承org.apache.ibatis.type.BaseTypeHandler

     @MappedJdbcTypes(JdbcType.VARCHAR)
       public class ExampleTypeHandler extends BaseTypeHandler {

         @Override
         public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
           ps.setString(i, parameter);
        }

         @Override
         public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
           return rs.getString(columnName);
        }

         @Override
         public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
           return rs.getString(columnIndex);
        }

         @Override
         public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
           return cs.getString(columnIndex);
        }
      }
    <!-- mybatis-config.xml -->
    <typeHandlers>
     <typeHandler handler="xxx.xxx.xxx.ExampleTypeHandler"/>
    </typeHandlers>

    上面都设置成了String类型,这个的类型处理器将会覆盖已经存在的处理 Java 的 String 类型属性和 VARCHAR 参数及结果的类型处理器。

    通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:

    • 在类型处理器的配置元素(typeHandler element)上增加一个 javaType 属性(比如:javaType="String");

    • 在类型处理器的类上(TypeHandler class)增加一个 @MappedTypes 注解来指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解方式将被忽略。

    可以通过两种方式来指定被关联的 JDBC 类型:

    • 在类型处理器的配置元素上增加一个 javaType 属性(比如:javaType="VARCHAR");

    • 在类型处理器的类上(TypeHandler class)增加一个 @MappedJdbcTypes 注解来指定与其关联的 JDBC 类型列表。 如果在 javaType 属性中也同时指定,则注解方式将被忽略。

    查找类型处理器:

    <typeHandlers>
     <package name="org.mybatis.example"/>
    </typeHandlers>

    注意在使用自动检索(autodiscovery)功能的时候,只能通过注解方式来指定 JDBC 的类型。

    泛型类型处理器

    它可以处理多于一个类。为达到此目的, 需要增加一个接收该类作为参数的构造器,这样在构造一个类型处理器的时候 MyBatis 就会传入一个具体的类。

        //GenericTypeHandler.java
        public class GenericTypeHandler extends BaseTypeHandler {
    
          private Class type;
    
          public GenericTypeHandler(Class type) {
            if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
            this.type = type;
          }
          ...

    EnumTypeHandlerEnumOrdinalTypeHandler 都是泛型类型处理器(generic TypeHandlers), 我们将会在接下来的部分详细探讨。

    处理枚举类型

    若想映射枚举类型 Enum,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选一个来使用。

    比如说我们想存储取近似值时用到的舍入模式。默认情况下,MyBatis 会利用 EnumTypeHandler 来把 Enum 值转换成对应的名字。

    注意 EnumTypeHandler 在某种意义上来说是比较特别的,其他的处理器只针对某个特定的类,而它不同,它会处理任意继承了 Enum 的类

    不过,我们可能不想存储名字,相反我们的 DBA 会坚持使用整形值代码。那也一样轻而易举: 在配置文件中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可, 这样每个 RoundingMode 将通过他们的序数值来映射成对应的整形。

    <!-- mybatis-config.xml -->
    <typeHandlers>
      <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
    </typeHandlers>

     

    自动映射器(auto-mapper)

    将同样的 Enum 既映射成字符串又映射成整形.

    自动映射器自动地选用 EnumOrdinalTypeHandler 来处理, 所以如果我们想用普通的 EnumTypeHandler,就非要为那些 SQL 语句显式地设置要用到的类型处理器不可。

    (下一节才开始讲映射器文件,所以如果是首次阅读该文档,你可能需要先越过这一步,过会再来看。)

    <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
        <resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="funkyNumber" property="funkyNumber"/>
            <result column="roundingMode" property="roundingMode"/>
        </resultMap>
    
        <select id="getUser" resultMap="usermap">
            select * from users
        </select>
        <insert id="insert">
            insert into users (id, name, funkyNumber, roundingMode) values (
                #{id}, #{name}, #{funkyNumber}, #{roundingMode}
            )
        </insert>
    
        <resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="funkyNumber" property="funkyNumber"/>
            <result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
        </resultMap>
        <select id="getUser2" resultMap="usermap2">
            select * from users2
        </select>
        <insert id="insert2">
            insert into users2 (id, name, funkyNumber, roundingMode) values (
                #{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
            )
        </insert>
    
    </mapper>

    注意,这里的 select 语句强制使用 resultMap 来代替 resultType

    Mybatis XML映射文件

    数据库数据-----对象

    Mapper XML文件

    1. cache 缓存配置

    2. cache-ref 缓存配置引用

    3. resultMap描述如何从数据库结果集中加载对象

    4. sql 可被其他语句引用的语句块

    5. insert 映射插入文件

    6. update 映射更新语句

    7. delete 映射删除语句

    8. select 映射查询语句

    select

    select示例
    <select id="selectAllStudent" parameterType="int" resultType="hashmap">
    select * from student where id=#{id}
    </select>

    数据库中对应列的数据类型是int结果集中类型是HashMap,键是列名,值是行中的值。

    参数#{id},让mybatis创建一个预处理语句参数String selectPerson = "SELECT * FROM PERSON WHERE ID=?";

    select的属性
     <select
      id="selectPerson"
      parameterType="int"
      parameterMap="deprecated"
      resultType="hashmap"
      resultMap="personResultMap"
      flushCache="false"
      useCache="true"
      timeout="10000"
      fetchSize="256"
      statementType="PREPARED"
      resultSetType="FORWARD_ONLY">  
     </select>
    所有属性及其描述
    属性描述
    id 在命名空间中唯一的标识符,可以被用来引用这条语句。
    parameterType 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
    resultType 从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。
    resultMap 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用。
    flushCache 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。
    useCache 将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true。
    timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。
    fetchSize 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)。
    statementType STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
    resultSetType FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)。
    databaseId 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。
    resultOrdered 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。
    resultSets 这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。

    insert,update,delete

    insert,update,delete示例
    <insert id="insertAuthor">
      insert into Author (id,username,password,email,bio)
      values (#{id},#{username},#{password},#{email},#{bio})
    </insert>
    
    <update id="updateAuthor">
      update Author set
        username = #{username},
        password = #{password},
        email = #{email},
        bio = #{bio}
      where id = #{id}
    </update>
    
    <delete id="deleteAuthor">
      delete from Author where id = #{id}
    </delete>
    insert,update,delete属性设置
    <insert
      id="insertAuthor"
      parameterType="domain.blog.Author"
      flushCache="true"
      statementType="PREPARED"
      keyProperty=""
      keyColumn=""
      useGeneratedKeys=""
      timeout="20">
    
    <update
      id="updateAuthor"
      parameterType="domain.blog.Author"
      flushCache="true"
      statementType="PREPARED"
      timeout="20">
    
    <delete
      id="deleteAuthor"
      parameterType="domain.blog.Author"
      flushCache="true"
      statementType="PREPARED"
      timeout="20">
    insert,update,delete所有属性及其描述
    属性描述
    id 命名空间中的唯一标识符,可被用来代表这条语句。
    parameterType 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
    flushCache 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)。
    timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。
    statementType STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
    useGeneratedKeys (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
    keyProperty (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    keyColumn (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    databaseId 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。
    数据库主键支持主键自动生成

    如MySQL中的学生ID属性设置为了auto_increment就可以设置useGeneratedKey="true",keyProperty="id"如下:

    <insert id="insertStudent" useGeneratedKeys="true"
        keyProperty="id">
      insert into student (username,password,email,bio)
      values (#{username},#{password},#{email},#{bio})
    </insert>
    数据库主键支持主键自动生成
    <insert id="insertAuthor">
      <selectKey keyProperty="id" resultType="int" order="BEFORE">
        select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
      </selectKey>
      insert into Author
        (id, username, password, email,bio, favourite_section)
      values
        (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
    </insert>
    selectKey属性示例
    <selectKey
      keyProperty="id"
      resultType="int"
      order="BEFORE"
      statementType="PREPARED">
    属性描述
    keyProperty selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    keyColumn 匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    resultType 结果的类型。MyBatis 通常可以推算出来,但是为了更加确定写上也不会有什么问题。MyBatis 允许任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。
    order 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素 - 这和像 Oracle 的数据库相似,在插入语句内部可能有嵌入索引调用。
    statementType 与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。

    sql标签

    <sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
    • 通过sql的id包含在其他语句中

    <select id="selectUsers" resultType="map">
      select
        <include refid="userColumns"><property name="alias" value="t1"/></include>,
        <include refid="userColumns"><property name="alias" value="t2"/></include>
      from some_table t1
        cross join some_table t2
    </select>
    • 用在属性上

    <sql id="sometable">
      ${prefix}Table
    </sql>
    
    <sql id="someinclude">
      from
        <include refid="${include_target}"/>
    </sql>
    
    <select id="select" resultType="map">
      select
        field1, field2, field3
      <include refid="someinclude">
        <property name="prefix" value="Some"/>
        <property name="include_target" value="sometable"/>
      </include>
    </select>

    在其他sql语句中通过include引用。

    参数(Parameters)

    <insert id="insertUser" parameterType="User">
      insert into users (id, username, password)
      values (#{id}, #{username}, #{password})
    </insert>

    自动映射对象,同时参数也可以被指定为特殊的数据类型。

        #{property,javaType=int,jdbcType=NUMERIC}

    javaType 通常可以从参数对象中来去确定。

    为了以后定制类型处理方式,你也可以指定一个特殊的类型处理器类(或别名),比如:

    #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

    对于数值类型,还有一个小数保留位数的设置,来确定小数点后保留的位数。

    #{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

    最后,mode 属性允许你指定 IN,OUT 或 INOUT 参数。如果参数为 OUT 或 INOUT,参数对象属性的真实值将会被改变,就像你在获取输出参数时所期望的那样。如果 mode 为 OUT(或 INOUT),而且 jdbcType 为 CURSOR(也就是 Oracle 的 REFCURSOR),你必须指定一个 resultMap 来映射结果集到参数类型。要注意这里的 javaType 属性是可选的,如果左边的空白是 jdbcType 的 CURSOR 类型,它会自动地被设置为结果集。

    #{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}

    MyBatis 也支持很多高级的数据类型,比如结构体,但是当注册 out 参数时你必须告诉它语句类型名称。比如

    #{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}

    尽管所有这些强大的选项很多时候你只简单指定属性名,其他的事情 MyBatis 会自己去推断,最多你需要为可能为空的列名指定 jdbcType

    #{firstName}
    #{middleInitial,jdbcType=VARCHAR}
    #{lastName}
    字符串替换

    默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值(比如?)。这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY,你可以这样来使用:

    ORDER BY ${columnName}

    这里 MyBatis 不会修改或转义字符串。

    NOTE 以这种方式接受从用户输出的内容并提供给语句中不变的字符串是不安全的,会导致潜在的 SQL 注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。

    Result Maps

    <select id="selectUsers" resultType="map">
      select id, username, hashedPassword
      from some_table
      where id = #{id}
    </select>

    默认是resultSet属性

    JavaBean:

        package com.someapp.model;
        public class User {
          private int id;
          private String username;
          private String hashedPassword;
    
          public int getId() {
            return id;
          }
          public void setId(int id) {
            this.id = id;
          }
          public String getUsername() {
            return username;
          }
          public void setUsername(String username) {
            this.username = username;
          }
          public String getHashedPassword() {
            return hashedPassword;
          }
          public void setHashedPassword(String hashedPassword) {
            this.hashedPassword = hashedPassword;
          }
        }

    类有 3 个属性:id,username 和 hashedPassword。

    这样的一个 JavaBean 可以被映射到结果集

    <!-- In mybatis-config.xml file -->
    <typeAlias type="com.someapp.model.User" alias="User"/>
    
    <!-- In SQL Mapping XML file -->
    <select id="selectUsers" resultType="User">
      select id, username, hashedPassword
      from some_table
      where id = #{id}
    </select>

    别名

    <select id="selectUsers" resultType="User">
      select
        user_id             as "id",
        user_name           as "userName",
        hashed_password     as "hashedPassword"
      from some_table
      where id = #{id}
    </select>

    解决列名不匹配的另外一种方式

    <resultMap id="userResultMap" type="User">
      <id property="id" column="user_id" />
      <result property="username" column="username"/>
      <result property="password" column="password"/>
    </resultMap>    
    <select id="selectUsers" resultMap="userResultMap">
      select user_id, user_name, hashed_password
      from some_table
      where id = #{id}
    </select>

     

    高级结果映射
    <!-- Very Complex Statement -->
    <select id="selectBlogDetails" resultMap="detailedBlogResultMap">
      select
           B.id as blog_id,
           B.title as blog_title,
           B.author_id as blog_author_id,
           A.id as author_id,
           A.username as author_username,
           A.password as author_password,
           A.email as author_email,
           A.bio as author_bio,
           A.favourite_section as author_favourite_section,
           P.id as post_id,
           P.blog_id as post_blog_id,
           P.author_id as post_author_id,
           P.created_on as post_created_on,
           P.section as post_section,
           P.subject as post_subject,
           P.draft as draft,
           P.body as post_body,
           C.id as comment_id,
           C.post_id as comment_post_id,
           C.name as comment_name,
           C.comment as comment_text,
           T.id as tag_id,
           T.name as tag_name
      from Blog B
           left outer join Author A on B.author_id = A.id
           left outer join Post P on B.id = P.blog_id
           left outer join Comment C on P.id = C.post_id
           left outer join Post_Tag PT on PT.post_id = P.id
           left outer join Tag T on PT.tag_id = T.id
      where B.id = #{id}
    </select>

    下面是一个完整的复杂结果映射例子 (假设作者, 博客, 博文, 评论和标签都是类型的别名)

    <!-- Very Complex Result Map -->
    <resultMap id="detailedBlogResultMap" type="Blog">
      <constructor>
        <idArg column="blog_id" javaType="int"/>
      </constructor>
      <result property="title" column="blog_title"/>
      <association property="author" javaType="Author">
        <id property="id" column="author_id"/>
        <result property="username" column="author_username"/>
        <result property="password" column="author_password"/>
        <result property="email" column="author_email"/>
        <result property="bio" column="author_bio"/>
        <result property="favouriteSection" column="author_favourite_section"/>
      </association>
      <collection property="posts" ofType="Post">
        <id property="id" column="post_id"/>
        <result property="subject" column="post_subject"/>
        <association property="author" javaType="Author"/>
        <collection property="comments" ofType="Comment">
          <id property="id" column="comment_id"/>
        </collection>
        <collection property="tags" ofType="Tag" >
          <id property="id" column="tag_id"/>
        </collection>
        <discriminator javaType="int" column="draft">
          <case value="1" resultType="DraftPost"/>
        </discriminator>
      </collection>
    </resultMap>

    resultMap 元素有很多子元素和一个值得讨论的结构。 下面是 resultMap 元素的概念视图

    resultMap
    • constructor类在实例化时,用来注入结果到构造方法中

    • idArg- ID 参数;标记结果作为 ID 可以帮助提高整体效能

    • arg - 注入到构造方法的一个普通结果

    • id 一个 ID 结果;标记结果作为 ID 可以帮助提高整体效能

    • result – 注入到字段或 JavaBean 属性的普通结果

    • association 一个复杂的类型关联;许多结果将包成这种类型;嵌入结果映射 – 结果映射自身的关联,或者参考一个

    • collection复杂类型的集;嵌入结果映射 – 结果映射自身的集,或者参考一个

    • discriminator使用结果值来决定使用哪个结果映射

    • case 基于某些值的结果映射;嵌入结果映射 – 这种情形结果也映射它本身,因此可以包含很多相 同的元素,或者它可以参照一个外部的结果映射。

    AttributeDescription
    id A unique identifier in this namespace that can be used to reference this result map.
    type A fully qualified Java class name, or a type alias (see the table above for the list of built-in type aliases).
    autoMapping If present, MyBatis will enable or disable the automapping for this ResultMap. This attribute overrides the global autoMappingBehavior. Default: unset.

     

    id & result
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>

    这些是结果映射最基本内容。id 和 result 都映射一个单独列的值到简单数据类型(字符 串,整型,双精度浮点数,日期等)的单独属性或字段。

    每个都有一些属性:

    属性描述
    property 映射到列结果的字段或属性。如果匹配的是存在的,和给定名称相同 的 JavaBeans 的属性,那么就会使用。否则 MyBatis 将会寻找给定名称 property 的字段。这两种情形你可以使用通常点式的复杂属性导航。比如,你 可以这样映射一些东西: "username" ,或者映射到一些复杂的东西: "address.street.number" 。
    column 从数据库中得到的列名,或者是列名的重命名标签。这也是通常和会 传递给 resultSet.getString(columnName)方法参数中相同的字符串。
    javaType 一个 Java 类的完全限定名,或一个类型别名(参考上面内建类型别名 的列表) 。如果你映射到一个 JavaBean,MyBatis 通常可以断定类型。 然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证所需的行为。
    jdbcType 在这个表格之后的所支持的 JDBC 类型列表中的类型。JDBC 类型是仅 仅需要对插入,更新和删除操作可能为空的列进行处理。这是 JDBC jdbcType 的需要,而不是 MyBatis 的。如果你直接使用 JDBC 编程,你需要指定 这个类型-但仅仅对可能为空的值。
    typeHandler 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默 认的类型处理器。这个属性值是类的完全限定名或者是一个类型处理 器的实现,或者是类型别名。
    支持的 JDBC 类型

    为了未来的参考,MyBatis 通过包含的 jdbcType 枚举型,支持下面的 JDBC 类型。

    BITFLOATCHARTIMESTAMPOTHERUNDEFINED
    TINYINT REAL VARCHAR BINARY BLOG NVARCHAR
    SMALLINT DOUBLE LONGVARCHAR VARBINARY CLOB NCHAR
    INTEGER NUMERIC DATE LONGVARBINARY BOOLEAN NCLOB
    BIGINT DECIMAL TIME NULL CURSOR ARRAY
    构造方法
    <constructor>
       <idArg column="id" javaType="int"/>
       <arg column="username" javaType="String"/>
    </constructor>

    对于大多数数据传输对象(Data Transfer Object,DTO)类型,属性可以起作用,而且像 你绝大多数的领域模型, 指令也许是你想使用一成不变的类的地方。 通常包含引用或查询数 据的表很少或基本不变的话对一成不变的类来说是合适的。 构造方法注入允许你在初始化时 为类设置属性的值,而不用暴露出公有方法。MyBatis 也支持私有属性和私有 JavaBeans 属 性来达到这个目的,但是一些人更青睐构造方法注入。构造方法元素支持这个。

    看看下面这个构造方法:

        public class User {
           //...
           public User(int id, String username) {
             //...
          }
        //...
        }

    为了向这个构造方法中注入结果,MyBatis 需要通过它的参数的类型来标识构造方法。 Java 没有自查(反射)参数名的方法。所以当创建一个构造方法元素时,保证参数是按顺序 排列的,而且数据类型也是确定的。

    <constructor>
       <idArg column="id" javaType="int"/>
       <arg column="username" javaType="String"/>
    </constructor>

    剩余的属性和规则和固定的 id 和 result 元素是相同的。

    属性描述
    column 来自数据库的类名,或重命名的列标签。这和通常传递给 resultSet.getString(columnName)方法的字符串是相同的。
    javaType 一个 Java 类的完全限定名,或一个类型别名(参考上面内建类型别名的列表)。 如果你映射到一个 JavaBean,MyBatis 通常可以断定类型。然而,如 果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证所需的 行为。
    jdbcType 在这个表格之前的所支持的 JDBC 类型列表中的类型。JDBC 类型是仅仅 需要对插入, 更新和删除操作可能为空的列进行处理。这是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 编程,你需要指定这个类型-但 仅仅对可能为空的值。
    typeHandler 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的 类型处理器。 这个属性值是类的完全限定名或者是一个类型处理器的实现, 或者是类型别名。
    select The ID of another mapped statement that will load the complex type required by this property mapping. The values retrieved from columns specified in the column attribute will be passed to the target select statement as parameters. See the Association element for more.
    resultMap This is the ID of a ResultMap that can map the nested results of this argument into an appropriate object graph. This is an alternative to using a call to another select statement. It allows you to join multiple tables together into a single ResultSet. Such a ResultSet will contain duplicated, repeating groups of data that needs to be decomposed and mapped properly to a nested object graph. To facilitate this, MyBatis lets you "chain" result maps together, to deal with the nested results. See the Association element below for more.
    关联
    <association property="author" column="blog_author_id" javaType="Author">
      <id property="id" column="author_id"/>
      <result property="username" column="author_username"/>
    </association>

    关联元素处理"有一个"类型的关系。比如,在我们的示例中,一个博客有一个用户。 关联映射就工作于这种结果之上。你指定了目标属性,来获取值的列,属性的 java 类型(很 多情况下 MyBatis 可以自己算出来) ,如果需要的话还有 jdbc 类型,如果你想覆盖或获取的 结果值还需要类型控制器。

    关联中不同的是你需要告诉 MyBatis 如何加载关联。MyBatis 在这方面会有两种不同的 方式:

    • 嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型。

    • 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集。首先,然让我们来查看这个元素的属性。所有的你都会看到,它和普通的只由 select 和

    resultMap 属性的结果映射不同。

    属性描述
    property 映射到列结果的字段或属性。如果匹配的是存在的,和给定名称相同的 property JavaBeans 的属性, 那么就会使用。 否则 MyBatis 将会寻找给定名称的字段。 这两种情形你可以使用通常点式的复杂属性导航。比如,你可以这样映射 一 些 东 西 :" username ", 或 者 映 射 到 一 些 复 杂 的 东 西 : "address.street.number" 。
    javaType 一个 Java 类的完全限定名,或一个类型别名(参考上面内建类型别名的列 表) 。如果你映射到一个 JavaBean,MyBatis 通常可以断定类型。然而,如 javaType 果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证所需的 行为。
    jdbcType 在这个表格之前的所支持的 JDBC 类型列表中的类型。JDBC 类型是仅仅 需要对插入, 更新和删除操作可能为空的列进行处理。这是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 编程,你需要指定这个类型-但 仅仅对可能为空的值。
    typeHandler 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的 typeHandler 类型处理器。 这个属性值是类的完全限定名或者是一个类型处理器的实现, 或者是类型别名。
    关联的嵌套查询
    属性描述
    column 来自数据库的类名,或重命名的列标签。这和通常传递给 resultSet.getString(columnName)方法的字符串是相同的。 column 注 意 : 要 处 理 复 合 主 键 , 你 可 以 指 定 多 个 列 名 通 过 column= " {prop1=col1,prop2=col2} " 这种语法来传递给嵌套查询语 句。这会引起 prop1 和 prop2 以参数对象形式来设置给目标嵌套查询语句。
    select 另外一个映射语句的 ID,可以加载这个属性映射需要的复杂类型。获取的 在列属性中指定的列的值将被传递给目标 select 语句作为参数。表格后面 有一个详细的示例。 select 注 意 : 要 处 理 复 合 主 键 , 你 可 以 指 定 多 个 列 名 通 过 column= " {prop1=col1,prop2=col2} " 这种语法来传递给嵌套查询语 句。这会引起 prop1 和 prop2 以参数对象形式来设置给目标嵌套查询语句。
    fetchType Optional. Valid values are lazy and eager. If present, it supersedes the global configuration parameter lazyLoadingEnabled for this mapping.

    示例:

    <resultMap id="blogResult" type="Blog">
      <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
    </resultMap>
    
    <select id="selectBlog" resultMap="blogResult">
      SELECT * FROM BLOG WHERE ID = #{id}
    </select>
    
    <select id="selectAuthor" resultType="Author">
      SELECT * FROM AUTHOR WHERE ID = #{id}
    </select>

    我们有两个查询语句:一个来加载博客,另外一个来加载作者,而且博客的结果映射描 述了"selectAuthor"语句应该被用来加载它的 author 属性。

    其他所有的属性将会被自动加载,假设它们的列和属性名相匹配。

    这种方式很简单, 但是对于大型数据集合和列表将不会表现很好。 问题就是我们熟知的 "N+1 查询问题"。概括地讲,N+1 查询问题可以是这样引起的:

    • 你执行了一个单独的 SQL 语句来获取结果列表(就是"+1")。

    • 对返回的每条记录,你执行了一个查询语句来为每个加载细节(就是"N")。

    这个问题会导致成百上千的 SQL 语句被执行。这通常不是期望的。

    MyBatis 能延迟加载这样的查询就是一个好处,因此你可以分散这些语句同时运行的消 耗。然而,如果你加载一个列表,之后迅速迭代来访问嵌套的数据,你会调用所有的延迟加 载,这样的行为可能是很糟糕的。

    所以还有另外一种方法。

    关联的嵌套结果
    属性描述
    resultMap 这是结果映射的 ID,可以映射关联的嵌套结果到一个合适的对象图中。这 是一种替代方法来调用另外一个查询语句。这允许你联合多个表来合成到 resultMap 一个单独的结果集。这样的结果集可能包含重复,数据的重复组需要被分 解,合理映射到一个嵌套的对象图。为了使它变得容易,MyBatis 让你"链 接"结果映射,来处理嵌套结果。一个例子会很容易来仿照,这个表格后 面也有一个示例。
    columnPrefix When joining multiple tables, you would have to use column alias to avoid duplicated column names in the ResultSet. Specifying columnPrefix allows you to map such columns to an external resultMap. Please see the example explained later in this section.
    notNullColumn By default a child object is created only if at least one of the columns mapped to the child's properties is non null. With this attribute you can change this behaviour by specifiying which columns must have a value so MyBatis will create a child object only if any of those columns is not null. Multiple column names can be specified using a comma as a separator. Default value: unset.
    autoMapping If present, MyBatis will enable or disable auto-mapping when mapping the result to this property. This attribute overrides the global autoMappingBehavior. Note that it has no effect on an external resultMap, so it is pointless to use it with select or resultMap attribute. Default value: unset.

    在上面你已经看到了一个非常复杂的嵌套关联的示例。 下面这个是一个非常简单的示例 来说明它如何工作。代替了执行一个分离的语句,我们联合博客表和作者表在一起,就像:

    <select id="selectBlog" resultMap="blogResult">
      select
        B.id            as blog_id,
        B.title         as blog_title,
        B.author_id     as blog_author_id,
        A.id            as author_id,
        A.username      as author_username,
        A.password      as author_password,
        A.email         as author_email,
        A.bio           as author_bio
      from Blog B left outer join Author A on B.author_id = A.id
      where B.id = #{id}
    </select>

    注意这个联合查询, 以及采取保护来确保所有结果被唯一而且清晰的名字来重命名。 这使得映射非常简单。现在我们可以映射这个结果:

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="blog_id" />
      <result property="title" column="blog_title"/>
      <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
    </resultMap>
    
    <resultMap id="authorResult" type="Author">
      <id property="id" column="author_id"/>
      <result property="username" column="author_username"/>
      <result property="password" column="author_password"/>
      <result property="email" column="author_email"/>
      <result property="bio" column="author_bio"/>
    </resultMap>

    在上面的示例中你可以看到博客的作者关联代表着"authorResult"结果映射来加载作 者实例。

    非常重要: 在嵌套据诶过映射中 id 元素扮演了非常重要的角色。应应该通常指定一个 或多个属性,它们可以用来唯一标识结果。实际上就是如果你离开她了,但是有一个严重的 性能问题时 MyBatis 仍然可以工作。选择的属性越少越好,它们可以唯一地标识结果。主键 就是一个显而易见的选择(尽管是联合主键)。

    现在,上面的示例用了外部的结果映射元素来映射关联。这使得 Author 结果映射可以 重用。然而,如果你不需要重用它的话,或者你仅仅引用你所有的结果映射合到一个单独描 述的结果映射中。你可以嵌套结果映射。这里给出使用这种方式的相同示例:

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="blog_id" />
      <result property="title" column="blog_title"/>
      <association property="author" javaType="Author">
        <id property="id" column="author_id"/>
        <result property="username" column="author_username"/>
        <result property="password" column="author_password"/>
        <result property="email" column="author_email"/>
        <result property="bio" column="author_bio"/>
      </association>
    </resultMap>

    如果博客有一个共同作者呢?select语句如下:

    <select id="selectBlog" resultMap="blogResult">
      select
        B.id            as blog_id,
        B.title         as blog_title,
        A.id            as author_id,
        A.username      as author_username,
        A.password      as author_password,
        A.email         as author_email,
        A.bio           as author_bio,
        CA.id           as co_author_id,
        CA.username     as co_author_username,
        CA.password     as co_author_password,
        CA.email        as co_author_email,
        CA.bio          as co_author_bio
      from Blog B
      left outer join Author A on B.author_id = A.id
      left outer join Author CA on B.co_author_id = CA.id
      where B.id = #{id}
    </select>

    回想一下,Author的resultMap定义如下:

    <resultMap id="authorResult" type="Author">
      <id property="id" column="author_id"/>
      <result property="username" column="author_username"/>
      <result property="password" column="author_password"/>
      <result property="email" column="author_email"/>
      <result property="bio" column="author_bio"/>
    </resultMap>

    由于结果中的列名与resultMap中定义的列不同,因此您需要指定columnPrefix来重新使用resultMap,以便映射合Author的结果。

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="blog_id" />
      <result property="title" column="blog_title"/>
      <association property="author"
        resultMap="authorResult" />
      <association property="coAuthor"
        resultMap="authorResult"
        columnPrefix="co_" />
    </resultMap>

    上面你已经看到了如何处理"有一个"类型关联。但是"有很多个"是怎样的?下面这 个部分就是来讨论这个主题的。

    集合

    <collection property="posts" ofType="domain.blog.Post">
      <id property="id" column="post_id"/>
      <result property="subject" column="post_subject"/>
      <result property="body" column="post_body"/>
    </collection>

    集合元素的作用几乎和关联是相同的。实际上,它们也很相似,文档的异同是多余的。 所以我们更多关注于它们的不同。

    我们来继续上面的示例,一个博客只有一个作者。但是博客有很多文章。在博客类中, 这可以由下面这样的写法来表示:

        private List posts;

    要映射嵌套结果集合到 List 中,我们使用集合元素。就像关联元素一样,我们可以从 连接中使用嵌套查询,或者嵌套结果。

    集合的嵌套查询

    首先,让我们看看使用嵌套查询来为博客加载文章。

    <resultMap id="blogResult" type="Blog">
      <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
    </resultMap>
    
    <select id="selectBlog" resultMap="blogResult">
      SELECT * FROM BLOG WHERE ID = #{id}
    </select>
    
    <select id="selectPostsForBlog" resultType="Blog">
      SELECT * FROM POST WHERE BLOG_ID = #{id}
    </select>

    这里你应该注意很多东西,但大部分代码和上面的关联元素是非常相似的。首先,你应 该注意我们使用的是集合元素。然后要注意那个新的"ofType"属性。这个属性用来区分 JavaBean(或字段)属性类型和集合包含的类型来说是很重要的。所以你可以读出下面这个 映射:

    <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>

    读作: "在 Post 类型的 ArrayList 中的 posts 的集合。"

    javaType 属性是不需要的,因为 MyBatis 在很多情况下会为你算出来。所以你可以缩短 写法:

    <collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
    集合的嵌套结果

    至此,你可以猜测集合的嵌套结果是如何来工作的,因为它和关联完全相同,除了它应 用了一个"ofType"属性

    First, let's look at the SQL:

    <select id="selectBlog" resultMap="blogResult">
      select
      B.id as blog_id,
      B.title as blog_title,
      B.author_id as blog_author_id,
      P.id as post_id,
      P.subject as post_subject,
      P.body as post_body,
      from Blog B
      left outer join Post P on B.id = P.blog_id
      where B.id = #{id}
    </select>

    我们又一次联合了博客表和文章表,而且关注于保证特性,结果列标签的简单映射。现 在用文章映射集合映射博客,可以简单写为:

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="blog_id" />
      <result property="title" column="blog_title"/>
      <collection property="posts" ofType="Post">
        <id property="id" column="post_id"/>
        <result property="subject" column="post_subject"/>
        <result property="body" column="post_body"/>
      </collection>
    </resultMap>

    同样,要记得 id 元素的重要性,如果你不记得了,请阅读上面的关联部分。

    同样, 如果你引用更长的形式允许你的结果映射的更多重用, 你可以使用下面这个替代 的映射:

    <resultMap id="blogResult" type="Blog">
      <id property="id" column="blog_id" />
      <result property="title" column="blog_title"/>
      <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
    </resultMap>
    
    <resultMap id="blogPostResult" type="Post">
      <id property="id" column="id"/>
      <result property="subject" column="subject"/>
      <result property="body" column="body"/>
    </resultMap>

    注意 这个对你所映射的内容没有深度,广度或关联和集合相联合的限制。当映射它们 时你应该在大脑中保留它们的表现。 你的应用在找到最佳方法前要一直进行的单元测试和性 能测试。好在 myBatis 让你后来可以改变想法,而不对你的代码造成很小(或任何)影响。

    高级关联和集合映射是一个深度的主题。文档只能给你介绍到这了。加上一点联系,你 会很快清楚它们的用法。

    鉴别器
    <discriminator javaType="int" column="draft">
      <case value="1" resultType="DraftPost"/>
    </discriminator>

    有时一个单独的数据库查询也许返回很多不同 (但是希望有些关联) 数据类型的结果集。 鉴别器元素就是被设计来处理这个情况的, 还有包括类的继承层次结构。 鉴别器非常容易理 解,因为它的表现很像 Java 语言中的 switch 语句。

    定义鉴别器指定了 column 和 javaType 属性。 列是 MyBatis 查找比较值的地方。 JavaType 是需要被用来保证等价测试的合适类型(尽管字符串在很多情形下都会有用)。比如:

    <resultMap id="vehicleResult" type="Vehicle">
      <id property="id" column="id" />
      <result property="vin" column="vin"/>
      <result property="year" column="year"/>
      <result property="make" column="make"/>
      <result property="model" column="model"/>
      <result property="color" column="color"/>
      <discriminator javaType="int" column="vehicle_type">
        <case value="1" resultMap="carResult"/>
        <case value="2" resultMap="truckResult"/>
        <case value="3" resultMap="vanResult"/>
        <case value="4" resultMap="suvResult"/>
      </discriminator>
    </resultMap>

    在这个示例中, MyBatis 会从结果集中得到每条记录, 然后比较它的 vehicle 类型的值。 如果它匹配任何一个鉴别器的实例,那么就使用这个实例指定的结果映射。换句话说,这样 做完全是剩余的结果映射被忽略(除非它被扩展,这在第二个示例中讨论) 。如果没有任何 一个实例相匹配,那么 MyBatis 仅仅使用鉴别器块外定义的结果映射。所以,如果 carResult 按如下声明:

    <resultMap id="carResult" type="Car">
      <result property="doorCount" column="door_count" />
    </resultMap>

    那么只有 doorCount 属性会被加载。这步完成后完整地允许鉴别器实例的独立组,尽管 和父结果映射可能没有什么关系。这种情况下,我们当然知道 cars 和 vehicles 之间有关系, 如 Car 是一个 Vehicle 实例。因此,我们想要剩余的属性也被加载。我们设置的结果映射的 简单改变如下。

    <resultMap id="carResult" type="Car" extends="vehicleResult">
      <result property="doorCount" column="door_count" />
    </resultMap>

    现在 vehicleResult 和 carResult 的属性都会被加载了。

    尽管曾经有些人会发现这个外部映射定义会多少有一些令人厌烦之处。 因此还有另外一 种语法来做简洁的映射风格。比如:

    <resultMap id="vehicleResult" type="Vehicle">
      <id property="id" column="id" />
      <result property="vin" column="vin"/>
      <result property="year" column="year"/>
      <result property="make" column="make"/>
      <result property="model" column="model"/>
      <result property="color" column="color"/>
      <discriminator javaType="int" column="vehicle_type">
        <case value="1" resultType="carResult">
          <result property="doorCount" column="door_count" />
        </case>
        <case value="2" resultType="truckResult">
          <result property="boxSize" column="box_size" />
          <result property="extendedCab" column="extended_cab" />
        </case>
        <case value="3" resultType="vanResult">
          <result property="powerSlidingDoor" column="power_sliding_door" />
        </case>
        <case value="4" resultType="suvResult">
          <result property="allWheelDrive" column="all_wheel_drive" />
        </case>
      </discriminator>
    </resultMap>

    要记得 这些都是结果映射, 如果你不指定任何结果, 那么 MyBatis 将会为你自动匹配列 和属性。所以这些例子中的大部分是很冗长的,而其实是不需要的。也就是说,很多数据库 是很复杂的,我们不太可能对所有示例都能依靠它。

    MyBatis 动态SQL

    动态 SQL

    MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

    通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语言来改进这种情形,这种语言可以被用在任意的 SQL 映射语句中。

    动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多的元素需要来了解。MyBatis 3 大大提升了它们,现在用不到原先一半的元素就可以了。MyBatis 采用功能强大的基于 OGNL 的表达式来消除其他元素。

    • if

    • choose (when, otherwise)

    • trim (where, set)

    • foreach

    if

    动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。比如:

    <select id="findActiveBlogWithTitleLike"
         resultType="Blog">
      SELECT * FROM BLOG 
      WHERE state = ‘ACTIVE’ 
      <if test="title != null">
        AND title like #{title}
      </if>
    </select>

    这条语句提供了一个可选的文本查找类型的功能。如果没有传入"title",那么所有处于"ACTIVE"状态的BLOG都会返回;反之若传入了"title",那么就会把模糊查找"title"内容的BLOG结果返回(就这个例子而言,细心的读者会发现其中的参数值是可以包含一些掩码或通配符的)。

    如果想可选地通过"title"和"author"两个条件搜索该怎么办呢?首先,改变语句的名称让它更具实际意义;然后只要加入另一个条件即可。

    <select id="findActiveBlogLike"
         resultType="Blog">
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’ 
      <if test="title != null">
        AND title like #{title}
      </if>
      <if test="author != null and author.name != null">
        AND author_name like #{author.name}
      </if>
    </select>

    choose, when, otherwise

    有些时候,我们不想用到所有的条件语句,而只想从中择其一二。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

    还是上面的例子,但是这次变为提供了"title"就按"title"查找,提供了"author"就按"author"查找,若两者都没有提供,就返回所有符合条件的BLOG(实际情况可能是由管理员按一定策略选出BLOG列表,而不是返回大量无意义的随机结果)。

    <select id="findActiveBlogLike"
         resultType="Blog">
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’
      <choose>
        <when test="title != null">
          AND title like #{title}
        </when>
        <when test="author != null and author.name != null">
          AND author_name like #{author.name}
        </when>
        <otherwise>
          AND featured = 1
        </otherwise>
      </choose>
    </select>

    trim, where, set

    前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题。现在考虑回到"if"示例,这次我们将"ACTIVE = 1"也设置成动态的条件,看看会发生什么。

    <select id="findActiveBlogLike"
         resultType="Blog">
      SELECT * FROM BLOG 
      WHERE 
      <if test="state != null">
        state = #{state}
      </if> 
      <if test="title != null">
        AND title like #{title}
      </if>
      <if test="author != null and author.name != null">
        AND author_name like #{author.name}
      </if>
    </select>

    如果这些条件没有一个能匹配上将会怎样?最终这条 SQL 会变成这样:

    SELECT * FROM BLOG
    WHERE

    这会导致查询失败。如果仅仅第二个条件匹配又会怎样?这条 SQL 最终会是这样:

    SELECT * FROM BLOG
    WHERE
    AND title like 'someTitle'

    这个查询也会失败。这个问题不能简单的用条件句式来解决,如果你也曾经被迫这样写过,那么你很可能从此以后都不想再这样去写了。

    MyBatis 有一个简单的处理,这在90%的情况下都会有用。而在不能使用的地方,你可以自定义处理方式来令其正常工作。一处简单的修改就能得到想要的效果:

    <select id="findActiveBlogLike"
         resultType="Blog">
      SELECT * FROM BLOG 
      <where> 
        <if test="state != null">
             state = #{state}
        </if> 
        <if test="title != null">
            AND title like #{title}
        </if>
        <if test="author != null and author.name != null">
            AND author_name like #{author.name}
        </if>
      </where>
    </select>

    where 元素知道只有在一个以上的if条件有值的情况下才去插入"WHERE"子句。而且,若最后的内容是"AND"或"OR"开头的,where 元素也知道如何将他们去除。

    如果 where 元素没有按正常套路出牌,我们还是可以通过自定义 trim 元素来定制我们想要的功能。比如,和 where 元素等价的自定义 trim 元素为:

    <trim prefix="WHERE" prefixOverrides="AND |OR ">
      ... 
    </trim>

    prefixOverrides 属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它带来的结果就是所有在 prefixOverrides 属性中指定的内容将被移除,并且插入 prefix 属性中指定的内容。

    类似的用于动态更新语句的解决方案叫做 set。set 元素可以被用于动态包含需要更新的列,而舍去其他的。比如:

    <update id="updateAuthorIfNecessary">
      update Author
        <set>
          <if test="username != null">username=#{username},</if>
          <if test="password != null">password=#{password},</if>
          <if test="email != null">email=#{email},</if>
          <if test="bio != null">bio=#{bio}</if>
        </set>
      where id=#{id}
    </update>

    这里,set 元素会动态前置 SET 关键字,同时也会消除无关的逗号,因为用了条件语句之后很可能就会在生成的赋值语句的后面留下这些逗号。

    若你对等价的自定义 trim 元素的样子感兴趣,那这就应该是它的真面目:

    <trim prefix="SET" suffixOverrides=",">
      ...
    </trim>

    注意这里我们忽略的是后缀中的值,而又一次附加了前缀中的值。

    foreach

    动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:

    <select id="selectPostIn" resultType="domain.blog.Post">
      SELECT *
      FROM POST P
      WHERE ID in
      <foreach item="item" index="index" collection="list"
          open="(" separator="," close=")">
            #{item}
      </foreach>
    </select>

    foreach 元素的功能是非常强大的,它允许你指定一个集合,声明可以用在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。

    注意 你可以将一个 List 实例或者数组作为参数对象传给 MyBatis,当你这么做的时候,MyBatis 会自动将它包装在一个 Map 中并以名称为键。List 实例将会以"list"作为键,而数组实例的键将是"array"。

    到此我们已经完成了涉及 XML 配置文件和 XML 映射文件的讨论。下一部分将详细探讨 Java API,这样才能从已创建的映射中获取最大利益。

    bind

    bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如:

    <select id="selectBlogsLike" resultType="Blog">
      <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
      SELECT * FROM BLOG
      WHERE title LIKE #{pattern}
    </select>

    Multi-db vendor support

    一个配置了"_databaseId"变量的 databaseIdProvider 对于动态代码来说是可用的,这样就可以根据不同的数据库厂商构建特定的语句。比如下面的例子:

    <insert id="insert">
      <selectKey keyProperty="id" resultType="int" order="BEFORE">
        <if test="_databaseId == 'oracle'">
          select seq_users.nextval from dual
        </if>
        <if test="_databaseId == 'db2'">
          select nextval for seq_users from sysibm.sysdummy1"
        </if>
      </selectKey>
      insert into users values (#{id}, #{name})
    </insert>

    动态 SQL 中可插拔的脚本语言

    MyBatis 从 3.2 开始支持可插拔的脚本语言,因此你可以在插入一种语言的驱动(language driver)之后来写基于这种语言的动态 SQL 查询。

    可以通过实现下面接口的方式来插入一种语言:

        public interface LanguageDriver {
          ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
          SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType);
          SqlSource createSqlSource(Configuration configuration, String script, Class parameterType);
        }

    一旦有了自定义的语言驱动,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:

    <typeAliases>
      <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
    </typeAliases>
    <settings>
      <setting name="defaultScriptingLanguage" value="myLanguage"/>
    </settings>

    除了设置默认语言,你也可以针对特殊的语句指定特定语言,这可以通过如下的 lang 属性来完成:

    <select id="selectBlog" lang="myLanguage">
      SELECT * FROM BLOG
    </select>

    或者在你正在使用的映射中加上注解 @Lang 来完成:

        public interface Mapper {
          @Lang(MyLanguageDriver.class)
          @Select("SELECT * FROM BLOG")
          List selectBlog();
        }

    注意 可以将 Apache Velocity 作为动态语言来使用,更多细节请参考 MyBatis-Velocity 项目。

    你前面看到的所有 xml 标签都是默认 MyBatis 语言提供的,它是由别名为 xml 语言驱动器 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver 驱动的。

    MyBatis Java API

    Java API

    既然你已经知道如何配置 MyBatis 和创建映射文件,你就已经准备好来提升技能了。 MyBatis 的 Java API 就是你收获你所做的努力的地方。正如你即将看到的,和 JDBC 相比, MyBatis 很大程度简化了你的代码而且保持简洁,很容易理解和维护。MyBatis 3 已经引入 了很多重要的改进来使得 SQL 映射更加优秀。

    应用目录结构

    在我们深入 Java API 之前,理解关于目录结构的最佳实践是很重要的。MyBatis 非常灵 活, 你可以用你自己的文件来做几乎所有的事情。 但是对于任一框架, 都有一些最佳的方式。

    让我们看一下典型应用的目录结构:

    /my_application
      /bin
      /devlib
      /lib                <-- MyBatis *.jar文件在这里。
      /src
        /org/myapp/
          /action
          /data           <-- MyBatis配置文件在这里, 包括映射器类, XML配置, XML映射文件。
            /mybatis-config.xml
            /BlogMapper.java
            /BlogMapper.xml
          /model
          /service
          /view
        /properties       <-- 在你XML中配置的属性 文件在这里。
      /test
        /org/myapp/
          /action
          /data
          /model
          /service
          /view
        /properties
      /web
        /WEB-INF
          /web.xml

    Remember, these are preferences, not requirements, but others will thank you for using a common directory structure.

    这部分内容剩余的示例将假设你使用了这种目录结构。

    SqlSessions

    使用 MyBatis 的主要 Java 接口就是 SqlSession。尽管你可以使用这个接口执行命令,获 取映射器和管理事务。我们会讨论 SqlSession 本身更多,但是首先我们还是要了解如果获取 一个 SqlSession 实例。SqlSessions 是由 SqlSessionFactory 实例创建的。SqlSessionFactory 对 象 包 含 创 建 SqlSession 实 例 的 所 有 方 法 。 而 SqlSessionFactory 本 身 是 由 SqlSessionFactoryBuilder 创建的,它可以从 XML 配置,注解或手动配置 Java 来创建 SqlSessionFactory。

    NOTE When using MyBatis with a dependency injection framework like Spring or Guice, SqlSessions are created and injected by the DI framework so you don't need to use the SqlSessionFactoryBuilder or SqlSessionFactory and can go directly to the SqlSession section. Please refer to the MyBatis-Spring or MyBatis-Guice manuals for further info.

    SqlSessionFactoryBuilder

    SqlSessionFactoryBuilder 有五个 build()方法,每一种都允许你从不同的资源中创建一个 SqlSession 实例。

        SqlSessionFactory build(InputStream inputStream)
        SqlSessionFactory build(InputStream inputStream, String environment)
        SqlSessionFactory build(InputStream inputStream, Properties properties)
        SqlSessionFactory build(InputStream inputStream, String env, Properties props)
        SqlSessionFactory build(Configuration config)

    第一种方法是最常用的,它使用了一个参照了 XML 文档或上面讨论过的更特定的 mybatis-config.xml 文件的 Reader 实例。 可选的参数是 environment 和 properties。 Environment 决定加载哪种环境,包括数据源和事务管理器。比如:

    <environments default="development">
      <environment id="development">
        <transactionManager type="JDBC">
            ...
        <dataSource type="POOLED">
            ...
      </environment>
      <environment id="production">
        <transactionManager type="MANAGED">
            ...
        <dataSource type="JNDI">
            ...
      </environment>
    </environments>

    如果你调用了 一个使用 environment 参数 方 式的 build 方法, 那么 MyBatis 将会使用 configuration 对象来配置这个 environment。 当然, 如果你指定了一个不合法的 environment, 你会得到错误提示。 如果你调用了其中之一没有 environment 参数的 build 方法, 那么就使用 默认的 environment(在上面的示例中就会指定为 default="development")。

    如果你调用了使用 properties 实例的方法,那么 MyBatis 就会加载那些 properties(属性 配置文件) ,并你在你配置中可使用它们。那些属性可以用${propName}语法形式多次用在 配置文件中。

    回想一下,属性可以从 mybatis-config.xml 中被引用,或者直接指定它。因此理解优先 级是很重要的。我们在文档前面已经提及它了,但是这里要再次重申:

    如果一个属性存在于这些位置,那么 MyBatis 将会按找下面的顺序来加载它们:

    • 在 properties 元素体中指定的属性首先被读取,

    • 从 properties 元素的类路径 resource 或 url 指定的属性第二个被读取, 可以覆盖已经 指定的重复属性,

    • 作为方法参 数传递 的属性最 后被读 取,可以 覆盖已 经从 properties 元 素体和 resource/url 属性中加载的任意重复属性。

    因此,最高优先级的属性是通过方法参数传递的,之后是 resource/url 属性指定的,最 后是在 properties 元素体中指定的属性。

    总结一下,前四个方法很大程度上是相同的,但是由于可以覆盖,就允许你可选地指定 environment 和/或 properties。 这里给出一个从 mybatis-config.xml 文件创建 SqlSessionFactory 的示例:

        String **resource** = "org/mybatis/builder/mybatis-config.xml";
        InputStream **inputStream** = Resources.getResourceAsStream(resource);
        SqlSessionFactoryBuilder **builder** = new SqlSessionFactoryBuilder();
        SqlSessionFactory **factory** = builder.build(inputStream);

    注意这里我们使用了 Resources 工具类,这个类在 org.mybatis.io 包中。Resources 类正 如其名,会帮助你从类路径下,文件系统或一个 web URL 加载资源文件。看一下这个类的 源代码或者通过你的 IDE 来查看,就会看到一整套有用的方法。这里给出一个简表:

        URL getResourceURL(String resource)
        URL getResourceURL(ClassLoader loader, String resource)
        InputStream getResourceAsStream(String resource)
        InputStream getResourceAsStream(ClassLoader loader, String resource)
        Properties getResourceAsProperties(String resource)
        Properties getResourceAsProperties(ClassLoader loader, String resource)
        Reader getResourceAsReader(String resource)
        Reader getResourceAsReader(ClassLoader loader, String resource)
        File getResourceAsFile(String resource)
        File getResourceAsFile(ClassLoader loader, String resource)
        InputStream getUrlAsStream(String urlString)
        Reader getUrlAsReader(String urlString)
        Properties getUrlAsProperties(String urlString)
        Class classForName(String className)

    最后一个 build 方法使用了一个 Configuration 实例。configuration 类包含你可能需要了 解 SqlSessionFactory 实例的所有内容。Configuration 类对于配置的自查很有用,包含查找和 操作 SQL 映射(不推荐使用,因为应用正接收请求) 。configuration 类有所有配置的开关, 这些你已经了解了,只在 Java API 中露出来。这里有一个简单的示例,如何手动配置 configuration 实例,然后将它传递给 build()方法来创建 SqlSessionFactory。

        DataSource dataSource = BaseDataTest.createBlogDataSource();
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
    
        Environment environment = new Environment("development", transactionFactory, dataSource);
    
        Configuration configuration = new Configuration(environment);
        configuration.setLazyLoadingEnabled(true);
        configuration.setEnhancementEnabled(true);
        configuration.getTypeAliasRegistry().registerAlias(Blog.class);
        configuration.getTypeAliasRegistry().registerAlias(Post.class);
        configuration.getTypeAliasRegistry().registerAlias(Author.class);
        configuration.addMapper(BoundBlogMapper.class);
        configuration.addMapper(BoundAuthorMapper.class);
    
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(configuration);

    现在你有一个 SqlSessionFactory,可以用来创建 SqlSession 实例。

    SqlSessionFactory

    SqlSessionFactory 有六个方法可以用来创建 SqlSession 实例。通常来说,如何决定是你 选择下面这些方法时:

    • Transaction (事务): 你想为 session 使用事务或者使用自动提交(通常意味着很多 数据库和/或 JDBC 驱动没有事务)?

    • Connection (连接): 你想 MyBatis 获得来自配置的数据源的连接还是提供你自己

    • Execution (执行): 你想 MyBatis 复用预处理语句和/或批量更新语句(包括插入和 删除)?

    重载的 openSession()方法签名设置允许你选择这些可选中的任何一个组合。

        SqlSession openSession()
        SqlSession openSession(boolean autoCommit)
        SqlSession openSession(Connection connection)
        SqlSession openSession(TransactionIsolationLevel level)
        SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
        SqlSession openSession(ExecutorType execType)
        SqlSession openSession(ExecutorType execType, boolean autoCommit)
        SqlSession openSession(ExecutorType execType, Connection connection)
        Configuration getConfiguration();

    默认的 openSession()方法没有参数,它会创建有如下特性的 SqlSession:

    • 会开启一个事务(也就是不自动提交)

    • 连接对象会从由活动环境配置的数据源实例中得到。

    • 事务隔离级别将会使用驱动或数据源的默认设置。

    • 预处理语句不会被复用,也不会批量处理更新。

    这些方法大都可以自我解释的。 开启自动提交, "true" 传递 给可选的 autoCommit 参数。 提供自定义的连接,传递一个 Connection 实例给 connection 参数。注意没有覆盖同时设置 Connection 和 autoCommit 两者的方法,因为 MyBatis 会使用当前 connection 对象提供的设 置。 MyBatis 为事务隔离级别调用使用一个 Java 枚举包装器, 称为 TransactionIsolationLevel, 否则它们按预期的方式来工作,并有 JDBC 支持的 5 级 ( NONE,READ_UNCOMMITTED,READ_COMMITTED,REPEA TABLE_READ,SERIALIZA BLE)

    还有一个可能对你来说是新见到的参数,就是 ExecutorType。这个枚举类型定义了 3 个 值:

    • ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情。它为每个语句的执行创建一个新的预处理语句。

    • ExecutorType.REUSE: 这个执行器类型会复用预处理语句。

    • ExecutorType.BATCH: 这个执行器会批量执行所有更新语句,如果 SELECT 在它们中间执行还会标定它们是 必须的,来保证一个简单并易于理解的行为。

    注意 在 SqlSessionFactory 中还有一个方法我们没有提及,就是 getConfiguration()。这 个方法会返回一个 Configuration 实例,在运行时你可以使用它来自检 MyBatis 的配置。

    注意 如果你已经使用之前版本 MyBatis,你要回忆那些 session,transaction 和 batch 都是分离的。现在和以往不同了,这些都包含在 session 的范围内了。你需要处理分开处理 事务或批量操作来得到它们的效果。

    SqlSession

    如上面所提到的,SqlSession 实例在 MyBatis 中是非常强大的一个类。在这里你会发现 所有执行语句的方法,提交或回滚事务,还有获取映射器实例。

    在 SqlSession 类中有超过 20 个方法,所以将它们分开成易于理解的组合。

    语句执行方法

    这些方法被用来执行定义在 SQL 映射的 XML 文件中的 SELECT,INSERT,UPDA E T 和 DELETE 语句。它们都会自行解释,每一句都使用语句的 ID 属性和参数对象,参数可以 是原生类型(自动装箱或包装类) ,JavaBean,POJO 或 Map。

         T selectOne(String statement, Object parameter)
         List selectList(String statement, Object parameter)
         Map selectMap(String statement, Object parameter, String mapKey)
        int insert(String statement, Object parameter)
        int update(String statement, Object parameter)
        int delete(String statement, Object parameter)

    selectOne 和 selectList 的不同仅仅是 selectOne 必须返回一个对象。 如果多余一个, 或者 没有返回 (或返回了 null) 那么就会抛出异常。 , 如果你不知道需要多少对象, 使用 selectList。

    如果你想检查一个对象是否存在,那么最好返回统计数(0 或 1) 。因为并不是所有语句都需 要参数,这些方法都是有不同重载版本的,它们可以不需要参数对象。

         T selectOne(String statement)
         List selectList(String statement)
         Map selectMap(String statement, String mapKey)
        int insert(String statement)
        int update(String statement)
        int delete(String statement)

    最后,还有查询方法的三个高级版本,它们允许你限制返回行数的范围,或者提供自定 义结果控制逻辑,这通常用于大量的数据集合。

    <E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
    <K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
    void select (String statement, Object parameter, ResultHandler<T> handler)
    void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)

    RowBounds 参数会告诉 MyBatis 略过指定数量的记录,还有限制返回结果的数量。 RowBounds 类有一个构造方法来接收 offset 和 limit,否则是不可改变的。

        int offset = 100;
        int limit = 25;
        RowBounds rowBounds = new RowBounds(offset, limit);

    不同的驱动会实现这方面的不同级别的效率。对于最佳的表现,使用结果集类型的 SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE(或句话说:不是 FORWARD_ONLY)。

    ResultHandler 参数允许你按你喜欢的方式处理每一行。你可以将它添加到 List 中,创 建 Map, 或抛出每个结果而不是只保留总计。 Set 你可以使用 ResultHandler 做很多漂亮的事, 那就是 MyBatis 内部创建结果集列表。

    它的接口很简单。

        package org.apache.ibatis.session;
        public interface ResultHandler {
          void handleResult(ResultContext context);
        }

    ResultContext 参数给你访问结果对象本身的方法, 大量结果对象被创建, 你可以使用布 尔返回值的 stop()方法来停止 MyBatis 加载更多的结果。

    事务控制方法

    控制事务范围有四个方法。 当然, 如果你已经选择了自动提交或你正在使用外部事务管 理器,这就没有任何效果了。然而,如果你正在使用 JDBC 事务管理员,由 Connection 实 例来控制,那么这四个方法就会派上用场:

        void commit()
        void commit(boolean force)
        void rollback()
        void rollback(boolean force)

    默认情况下 MyBatis 不会自动提交事务, 除非它侦测到有插入, 更新或删除操作改变了 数据库。如果你已经做出了一些改变而没有使用这些方法,那么你可以传递 true 到 commit 和 rollback 方法来保证它会被提交(注意,你不能在自动提交模式下强制 session,或者使用 了外部事务管理器时) 。很多时候你不用调用 rollback(),因为如果你没有调用 commit 时 MyBatis 会替你完成。然而,如果你需要更多对多提交和回滚都可能的 session 的细粒度控 制,你可以使用回滚选择来使它成为可能。

    NOTE MyBatis-Spring and MyBatis-Guice provide declarative transaction handling. So if you are using MyBatis with Spring or Guice please refer to their specific manuals.

    清理 Session 级的缓存

    void clearCache()

    SqlSession 实例有一个本地缓存在执行 update,commit,rollback 和 close 时被清理。要 明确地关闭它(获取打算做更多的工作) ,你可以调用 clearCache()。

    确保 SqlSession 被关闭

    void close()

    你必须保证的最重要的事情是你要关闭所打开的任何 session。保证做到这点的最佳方 式是下面的工作模式:

        SqlSession session = sqlSessionFactory.openSession();
        try {
            // following 3 lines pseudocod for "doing some work"
            session.insert(...);
            session.update(...);
            session.delete(...);
            session.commit();
        } finally {
            session.close();
        }

    Or, If you are using jdk 1.7+ and MyBatis 3.2+, you can use the try-with-resources statement:

        try (SqlSession session = sqlSessionFactory.openSession()) {
            // following 3 lines pseudocode for "doing some work"
            session.insert(...);
            session.update(...);
            session.delete(...);
            session.commit();
        }

    注意 就像 SqlSessionFactory,你可以通过调用 getConfiguration()方法获得 SqlSession 使用的 Configuration 实例

        Configuration getConfiguration()

    使用映射器

         T getMapper(Class type)

    上述的各个 insert,update,delete 和 select 方法都很强大,但也有些繁琐,没有类型安 全,对于你的 IDE 也没有帮助,还有可能的单元测试。在上面的入门章节中我们已经看到 了一个使用映射器的示例。

    因此, 一个更通用的方式来执行映射语句是使用映射器类。 一个映射器类就是一个简单 的接口,其中的方法定义匹配于 SqlSession 方法。下面的示例展示了一些方法签名和它们是 如何映射到 SqlSession 的。

        public interface AuthorMapper {
          // (Author) selectOne("selectAuthor",5);
          Author selectAuthor(int id);
          // (List) selectList("selectAuthors")
          List selectAuthors();
          // (Map) selectMap("selectAuthors", "id")
          @MapKey("id")
          Map selectAuthors();
          // insert("insertAuthor", author)
          int insertAuthor(Author author);
          // updateAuthor("updateAuthor", author)
          int updateAuthor(Author author);
          // delete("deleteAuthor",5)
          int deleteAuthor(int id);
        }

    总之, 每个映射器方法签名应该匹配相关联的 SqlSession 方法, 而没有字符串参数 ID。 相反,方法名必须匹配映射语句的 ID。

    此外,返回类型必须匹配期望的结果类型。所有常用的类型都是支持的,包括:原生类 型,Map,POJO 和 JavaBean。

    映射器接口不需要去实现任何接口或扩展任何类。 只要方法前面可以被用来唯一标识对 应的映射语句就可以了。

    映射器接口可以扩展其他接口。当使用 XML 来构建映射器接口时要保证在合适的命名 空间中有语句。 而且, 唯一的限制就是你不能在两个继承关系的接口中有相同的方法签名 (这 也是不好的想法)。

    你可以传递多个参数给一个映射器方法。 如果你这样做了, 默认情况下它们将会以它们 在参数列表中的位置来命名,比如:#{param1},#{param2}等。如果你想改变参数的名称(只在多参数 情况下) ,那么你可以在参数上使用@Param("paramName")注解。

    你也可以给方法传递一个 RowBounds 实例来限制查询结果。

    映射器注解

    因为最初设计时,MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,而且 映射语句也是定义在 XML 中的。而到了 MyBatis 3,有新的可用的选择了。MyBatis 3 构建 在基于全面而且强大的 Java 配置 API 之上。这个配置 API 是基于 XML 的 MyBatis 配置的 基础,也是新的基于注解配置的基础。注解提供了一种简单的方式来实现简单映射语句,而 不会引入大量的开销。

    注意 不幸的是,Java 注解限制了它们的表现和灵活。尽管很多时间都花调查,设计和 实验上,最强大的 MyBatis 映射不能用注解来构建,那并不可笑。C#属性(做示例)就没 有这些限制,因此 MyBatis.NET 将会比 XML 有更丰富的选择。也就是说,基于 Java 注解 的配置离不开它的特性。

    注解有下面这些:

    注解目标相对应的 XML描述
    @CacheNamespace `` 为给定的命名空间 (比如类) 配置缓存。 属性:implemetation,eviction, flushInterval,size 和 readWrite。
    @CacheNamespaceRef `` 参照另外一个命名空间的缓存来使用。 属性:value,应该是一个名空间的字 符串值(也就是类的完全限定名) 。
    @ConstructorArgs Method `` 收集一组结果传递给一个劫夺对象的 构造方法。属性:value,是形式参数 的数组。
    @Arg 方法   单 独 的 构 造 方 法 参 数 , 是 ConstructorArgs 集合的一部分。属性: id,column,javaType,typeHandler。 id 属性是布尔值, 来标识用于比较的属 性,和XML 元素相似。
    @TypeDiscriminator 方法 `` 一组实例值被用来决定结果映射的表 现。 属性: column, javaType, jdbcType, typeHandler,cases。cases 属性就是实 例的数组。
    @Case 方法 `` 单独实例的值和它对应的映射。属性: value,type,results。Results 属性是结 果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。
    @Results 方法 `` 结果映射的列表, 包含了一个特别结果 列如何被映射到属性或字段的详情。 属 性:value,是 Result 注解的数组。
    @Result 方法   在列和属性或字段之间的单独结果映 射。属 性:id,column, property, javaType ,jdbcType ,type Handler, one,many。id 属性是一个布尔值,表 示了应该被用于比较(和在 XML 映射 中的相似)的属性。one 属性是单 独 的 联 系, 和 相 似 , 而 many 属 性 是 对 集 合 而 言 的 , 和 相似。 它们这样命名是为了 避免名称冲突。
    @One 方法 ` | 复杂类型的单独属性值映射。属性: select,已映射语句(也就是映射器方 法)的完全限定名,它可以加载合适类 型的实例。注意:联合映射在注解 API 中是不支持的。这是因为 Java 注解的 限制,不允许循环引用。fetchType, which supersedes the global configuration parameterlazyLoadingEnabled` for this mapping.  
    @Many 方法 ` | A mapping to a collection property of a complex type. Attributes:select, which is the fully qualified name of a mapped statement (i.e. mapper method) that can load a collection of instances of the appropriate types,fetchType, which supersedes the global configuration parameterlazyLoadingEnabled` for this mapping. NOTE You will notice that join mapping is not supported via the Annotations API. This is due to the limitation in Java Annotations that does not allow for circular references.  
    @MapKey 方法   复 杂 类 型 的 集合 属 性 映射 。 属 性 : select,是映射语句(也就是映射器方 法)的完全限定名,它可以加载合适类 型的一组实例。注意:联合映射在 Java 注解中是不支持的。这是因为 Java 注 解的限制,不允许循环引用。
    @Options 方法 映射语句的属性 这个注解提供访问交换和配置选项的 宽广范围, 它们通常在映射语句上作为 属性出现。 而不是将每条语句注解变复 杂,Options 注解提供连贯清晰的方式 来访问它们。属性:useCache=true , flushCache=false , resultSetType=FORWARD_ONLY , statementType=PREPARED , fetchSize=-1 , , timeout=-1 useGeneratedKeys=false , keyProperty="id"。 理解 Java 注解是很 重要的,因为没有办法来指定"null" 作为值。因此,一旦你使用了 Options 注解,语句就受所有默认值的支配。要 注意什么样的默认值来避免不期望的 行为。
    * @Insert,@Update, @Delete, @Select 方法 ,,, 这些注解中的每一个代表了执行的真 实 SQL。 它们每一个都使用字符串数组 (或单独的字符串)。如果传递的是字 符串数组, 它们由每个分隔它们的单独 空间串联起来。这就当用 Java 代码构 建 SQL 时避免了“丢失空间”的问题。 然而,如果你喜欢,也欢迎你串联单独 的字符串。属性:value,这是字符串 数组用来组成单独的 SQL 语句。
    @InsertProvider,@UpdateProvider,@DeleteProvider,@SelectProvider 方法 ,,, 这些可选的 SQL 注解允许你指定一个 类名和一个方法在执行时来返回运行 允许创建动态 的 SQL。 基于执行的映射语句, MyBatis 会实例化这个类,然后执行由 provider 指定的方法. 这个方法可以选择性的接 受参数对象作为它的唯一参数, 但是必 须只指定该参数或者没有参数。属性: type,method。type 属性是类的完全限 定名。method 是该类中的那个方法名。 注意: 这节之后是对 SelectBuilder 类的 讨论,它可以帮助你以干净,容于阅读 的方式来构建动态 SQL。
    @Param Parameter N/A 如果你的映射器的方法需要多个参数, 这个注解可以被应用于映射器的方法 参数来给每个参数一个名字。否则,多 参数将会以它们的顺序位置来被命名 (不包括任何 RowBounds 参数) 比如。 #{param1} , #{param2} 等 , 这 是 默 认 的 。 使 用 @Param("person"),参数应该被命名为 #{person}。
    @SelectKey Method | This annotation duplicates the functionality for methods annotated with@Insert,@InsertProvider,@Updateor@UpdateProvider. It is ignored for other methods. If you specify a@SelectKeyannotation, then MyBatis will ignore any generated key properties set via the@Optionsannotation, or configuration properties. Attributes: statement an array of strings which is the SQL statement to execute,keyPropertywhich is the property of the parameter object that will be updated with the new value, before which must be eithertrueorfalseto denote if the SQL statement should be executed before or after the insert,resultTypewhich is the Java type of thekeyProperty, andstatementType=PREPARED.  
    @ResultMap Method N/A This annotation is used to provide the id of a ` element in an XML mapper to a@Selector@SelectProviderannotation. This allows annotated selects to reuse resultmaps that are defined in XML. This annotation will override any@Resultsor@ConstructorArgs` annotation if both are specified on an annotated select.
    @ResultType Method N/A This annotation is used when using a result handler. In that case, the return type is void so MyBatis must have a way to determine the type of object to construct for each row. If there is an XML result map, use the @ResultMap annotation. If the result type is specified in XML on the ` Night Mode

    映射申明样例

    这个例子展示了如何使用 @SelectKey 注解来在插入前读取数据库序列的值:

    @Insert("insert into table3 (id, name) values(#{nameId}, #{name})")
    @SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class)
    int insertTable3(Name name);

    这个例子展示了如何使用 @SelectKey 注解来在插入后读取数据库识别列的值:

    @Insert("insert into table2 (name) values(#{name})")
    @SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
    int insertTable2(Name name);

    MyBatis SQL语句构建器

    SQL语句构建器

    问题

    Java程序员面对的最痛苦的事情之一就是在Java代码中嵌入SQL语句。这么来做通常是由于SQL语句需要动态来生成-否则可以将它们放到外部文件或者存储过程中。正如你已经看到的那样,MyBatis在它的XML映射特性中有一个强大的动态SQL生成方案。但有时在Java代码内部创建SQL语句也是必要的。此时,MyBatis有另外一个特性可以帮到你,在减少典型的加号,引号,新行,格式化问题和嵌入条件来处理多余的逗号或 AND 连接词之前。事实上,在Java代码中来动态生成SQL代码就是一场噩梦。例如:

        String sql = "SELECT P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME, "
        "P.LAST_NAME,P.CREATED_ON, P.UPDATED_ON " +
        "FROM PERSON P, ACCOUNT A " +
        "INNER JOIN DEPARTMENT D on D.ID = P.DEPARTMENT_ID " +
        "INNER JOIN COMPANY C on D.COMPANY_ID = C.ID " +
        "WHERE (P.ID = A.ID AND P.FIRST_NAME like ?) " +
        "OR (P.LAST_NAME like ?) " +
        "GROUP BY P.ID " +
        "HAVING (P.LAST_NAME like ?) " +
        "OR (P.FIRST_NAME like ?) " +
        "ORDER BY P.ID, P.FULL_NAME";

    The Solution

    MyBatis 3提供了方便的工具类来帮助解决该问题。使用SQL类,简单地创建一个实例来调用方法生成SQL语句。上面示例中的问题就像重写SQL类那样:

        private String selectPersonSql() {
          return new SQL() {{
            SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
            SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
            FROM("PERSON P");
            FROM("ACCOUNT A");
            INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
            INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
            WHERE("P.ID = A.ID");
            WHERE("P.FIRST_NAME like ?");
            OR();
            WHERE("P.LAST_NAME like ?");
            GROUP_BY("P.ID");
            HAVING("P.LAST_NAME like ?");
            OR();
            HAVING("P.FIRST_NAME like ?");
            ORDER_BY("P.ID");
            ORDER_BY("P.FULL_NAME");
          }}.toString();
        }

    该例中有什么特殊之处?当你仔细看时,那不用担心偶然间重复出现的"AND"关键字,或者在"WHERE"和"AND"之间的选择,抑或什么都不选。该SQL类非常注意"WHERE"应该出现在何处,哪里又应该使用"AND",还有所有的字符串链接。

    SQL类

    这里给出一些示例:

        // Anonymous inner class
        public String deletePersonSql() {
          return new SQL() {{
            DELETE_FROM("PERSON");
            WHERE("ID = ${id}");
          }}.toString();
        }
    
        // Builder / Fluent style
        public String insertPersonSql() {
          String sql = new SQL()
            .INSERT_INTO("PERSON")
            .VALUES("ID, FIRST_NAME", "${id}, ${firstName}")
            .VALUES("LAST_NAME", "${lastName}")
            .toString();
          return sql;
        }
    
        // With conditionals (note the final parameters, required for the anonymous inner class to access them)
        public String selectPersonLike(final String id, final String firstName, final String lastName) {
          return new SQL() {{
            SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
            FROM("PERSON P");
            if (id != null) {
              WHERE("P.ID like ${id}");
            }
            if (firstName != null) {
              WHERE("P.FIRST_NAME like ${firstName}");
            }
            if (lastName != null) {
              WHERE("P.LAST_NAME like ${lastName}");
            }
            ORDER_BY("P.LAST_NAME");
          }}.toString();
        }
    
        public String deletePersonSql() {
          return new SQL() {{
            DELETE_FROM("PERSON");
            WHERE("ID = ${id}");
          }}.toString();
        }
    
        public String insertPersonSql() {
          return new SQL() {{
            INSERT_INTO("PERSON");
            VALUES("ID, FIRST_NAME", "${id}, ${firstName}");
            VALUES("LAST_NAME", "${lastName}");
          }}.toString();
        }
    
        public String updatePersonSql() {
          return new SQL() {{
            UPDATE("PERSON");
            SET("FIRST_NAME = ${firstName}");
            WHERE("ID = ${id}");
          }}.toString();
        }
    方法描述
    SELECT(String) 开始或插入到 SELECT子句。 可以被多次调用,参数也会添加到 SELECT子句。 参数通常使用逗号分隔的列名和别名列表,但也可以是数据库驱动程序接受的任意类型。
    SELECT_DISTINCT(String) 开始或插入到 SELECT子句, 也可以插入 DISTINCT关键字到生成的查询语句中。 可以被多次调用,参数也会添加到 SELECT子句。 参数通常使用逗号分隔的列名和别名列表,但也可以是数据库驱动程序接受的任意类型。
    FROM(String) 开始或插入到 FROM子句。 可以被多次调用,参数也会添加到 FROM子句。 参数通常是表名或别名,也可以是数据库驱动程序接受的任意类型。
    JOIN(String), INNER_JOIN(String), LEFT_OUTER_JOIN(String), RIGHT_OUTER_JOIN(String) 基于调用的方法,添加新的合适类型的 JOIN子句。 参数可以包含由列命和join on条件组合成标准的join。
    WHERE(String) 插入新的 WHERE子句条件, 由AND链接。可以多次被调用,每次都由AND来链接新条件。使用 OR() 来分隔OR
    OR() 使用OR来分隔当前的 WHERE子句条件。 可以被多次调用,但在一行中多次调用或生成不稳定的SQL
    AND() 使用AND来分隔当前的 WHERE子句条件。 可以被多次调用,但在一行中多次调用或生成不稳定的SQL。因为 WHEREHAVING 二者都会自动链接 AND, 这是非常罕见的方法,只是为了完整性才被使用。
    GROUP_BY(String) 插入新的 GROUP BY子句元素,由逗号连接。 可以被多次调用,每次都由逗号连接新的条件。
    HAVING(String) 插入新的 HAVING子句条件。 由AND连接。可以被多次调用,每次都由AND来连接新的条件。使用 OR() 来分隔OR.
    ORDER_BY(String) 插入新的 ORDER BY子句元素, 由逗号连接。可以多次被调用,每次由逗号连接新的条件。
    DELETE_FROM(String) 开始一个delete语句并指定需要从哪个表删除的表名。通常它后面都会跟着WHERE语句!
    INSERT_INTO(String) 开始一个insert语句并指定需要插入数据的表名。后面都会跟着一个或者多个VALUES()。
    SET(String) 针对update语句,插入到"set"列表中
    UPDATE(String) 开始一个update语句并指定需要更新的表明。后面都会跟着一个或者多个SET(),通常也会有一个WHERE()。
    VALUES(String, String) 插入到insert语句中。第一个参数是要插入的列名,第二个参数则是该列的值。

    SqlBuilder 和 SelectBuilder (已经废弃)

    在3.2版本之前,我们使用了一点不同的做法,通过实现ThreadLocal变量来掩盖一些导致Java DSL麻烦的语言限制。但这种方式已经废弃了,现代的框架都欢迎人们使用构建器类型和匿名内部类的想法。因此,SelectBuilder 和 SqlBuilder 类都被废弃了。

    下面的方法仅仅适用于废弃的SqlBuilder 和 SelectBuilder 类。

    方法描述
    BEGIN() / RESET() 这些方法清空SelectBuilder类的ThreadLocal状态,并且准备一个新的构建语句。开始新的语句时, BEGIN()读取得最好。 由于一些原因(在某些条件下,也许是逻辑需要一个完全不同的语句),在执行中清理语句 RESET()读取得最好。
    SQL() 返回生成的 SQL() 并重置 SelectBuilder 状态 (好像 BEGIN()RESET() 被调用了). 因此,该方法只能被调用一次!

    SelectBuilder 和 SqlBuilder 类并不神奇,但是知道它们如何工作也是很重要的。 SelectBuilder 使用 SqlBuilder 使用了静态导入和ThreadLocal变量的组合来开启整洁语法,可以很容易地和条件交错。使用它们,静态导入类的方法即可,就像这样(一个或其它,并非两者):

        import static org.apache.ibatis.jdbc.SelectBuilder.*;
        import static org.apache.ibatis.jdbc.SqlBuilder.*;

    这就允许像下面这样来创建方法:

        /* DEPRECATED */
        public String selectBlogsSql() {
          BEGIN(); // Clears ThreadLocal variable
          SELECT("*");
          FROM("BLOG");
          return SQL();
        }
        /* DEPRECATED */
        private String selectPersonSql() {
          BEGIN(); // Clears ThreadLocal variable
          SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
          SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
          FROM("PERSON P");
          FROM("ACCOUNT A");
          INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
          INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
          WHERE("P.ID = A.ID");
          WHERE("P.FIRST_NAME like ?");
          OR();
          WHERE("P.LAST_NAME like ?");
          GROUP_BY("P.ID");
          HAVING("P.LAST_NAME like ?");
          OR();
          HAVING("P.FIRST_NAME like ?");
          ORDER_BY("P.ID");
          ORDER_BY("P.FULL_NAME");
          return SQL();
        }

    MyBatis 日志

    Logging

    Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:

    • SLF4J

    • Apache Commons Logging

    • Log4j 2

    • Log4j

    • JDK logging

    具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。 如果一个都未找到,日志功能就会被禁用。

    不少应用服务器的classpath中已经包含Commons Logging,如Tomcat和WebShpere, 所以MyBatis会把它作为具体的日志实现。记住这点非常重要。这将意味着,在诸如 WebSphere的环境中——WebSphere提供了Commons Logging的私有实现,你的Log4J配置将被忽略。 这种做法不免让人悲催,MyBatis怎么能忽略你的配置呢?事实上,因Commons Logging已经存 在了,按照优先级顺序,Log4J自然就被忽略了!不过,如果你的应用部署在一个包含Commons Logging的环境, 而你又想用其他的日志框架,你可以根据需要调用如下的某一方法:

    org.apache.ibatis.logging.LogFactory.useSlf4jLogging();
    org.apache.ibatis.logging.LogFactory.useLog4JLogging();
    org.apache.ibatis.logging.LogFactory.useJdkLogging();
    org.apache.ibatis.logging.LogFactory.useCommonsLogging();
    org.apache.ibatis.logging.LogFactory.useStdOutLogging();

    如果的确需要调用以上的某个方法,请在其他所有MyBatis方法之前调用它。另外,只有在相应日志实现中存在 的前提下,调用对应的方法才是有意义的,否则MyBatis一概忽略。如你环境中并不存在Log4J,你却调用了 相应的方法,MyBatis就会忽略这一调用,代之默认的查找顺序查找日志实现。

    关于SLF4J、Apache Commons Logging、Apache Log4J和JDK Logging的API介绍已经超出本文档的范围。 不过,下面的例子可以作为一个快速入门。关于这些日志框架的更多信息,可以参考以下链接:

    Logging Configuration

    MyBatis可以对包、类、命名空间和全限定的语句记录日志。

    具体怎么做,视使用的日志框架而定,这里以Log4J为例。配置日志功能非常简单:添加几个配置文件, 如log4j.properties,再添加个jar包,如log4j.jar。下面是具体的例子,共两个步骤:

    步骤1: 添加 Log4J 的 jar 包

    因为采用Log4J,要确保在应用中对应的jar包是可用的。要满足这一点,只要将jar包添加到应用的classpath中即可。 Log4J的jar包可以从上面的链接中下载。

    具体而言,对于web或企业应用,需要将log4j.jar 添加到WEB-INF/lib 目录; 对于独立应用, 可以将它添加到jvm的 -classpath启动参数中。

    步骤2:配置Log4J

    配置Log4J比较简单, 比如需要记录这个mapper接口的日志:

        package org.mybatis.example;
        public interface BlogMapper {
          @Select("SELECT * FROM blog WHERE id = #{id}")
          Blog selectBlog(int id);
        }

    只要在应用的classpath中创建一个名称为log4j.properties的文件, 文件的具体内容如下:

        # Global logging configuration
        log4j.rootLogger=ERROR, stdout
        # MyBatis logging configuration...
        log4j.logger.org.mybatis.example.BlogMapper=TRACE
        # Console output...
        log4j.appender.stdout=org.apache.log4j.ConsoleAppender
        log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
        log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

    添加以上配置后,Log4J就会把 org.mybatis.example.BlogMapper 的详细执行日志记录下来,对于应用中的其它类则仅仅记录错误信息。

    也可以将日志从整个mapper接口级别调整到到语句级别,从而实现更细粒度的控制。如下配置只记录 selectBlog 语句的日志:

    log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE

    与此相对,可以对一组mapper接口记录日志,只要对mapper接口所在的包开启日志功能即可:

        log4j.logger.org.mybatis.example=TRACE

    某些查询可能会返回大量的数据,只想记录其执行的SQL语句该怎么办?为此,Mybatis中SQL语 句的日志级别被设为DEBUG(JDK Logging中为FINE),结果日志的级别为TRACE(JDK Logging中为FINER)。所以,只要将日志级别调整为DEBUG即可达到目的:

        log4j.logger.org.mybatis.example=DEBUG

    要记录日志的是类似下面的mapper文件而不是mapper接口又该怎么呢?

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.mybatis.example.BlogMapper">
      <select id="selectBlog" resultType="Blog">
        select * from Blog where id = #{id}
      </select>
    </mapper>

    对这个文件记录日志,只要对命名空间增加日志记录功能即可:

        log4j.logger.org.mybatis.example.BlogMapper=TRACE

    进一步,要记录具体语句的日志可以这样做:

    log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE

    看到了吧,两种配置没差别!

    配置文件log4j.properties的余下内容是针对日志格式的,这一内容已经超出本 文档范围。关于Log4J的更多内容,可以参考Log4J的网站。不过,可以简单试一下看看,不同的配置 会产生什么不一样的效果。

  • 相关阅读:
    C++11: reference_wrapper
    mac 查看目前哪些进程占用哪些端口
    Intellij IDEA 10.5 语言设置
    linux中的strip命令简介------给文件脱衣服
    HashMap的key可以是可变的对象吗???
    java BIO/NIO/AIO 学习
    java 反射
    Java线程同步
    maven modules
    设计模式在cocos2d-x中的使用--简单工厂模式(Simple Factory)
  • 原文地址:https://www.cnblogs.com/1605-3QYL/p/12569106.html
Copyright © 2011-2022 走看看