关于mybatis:MyBatis文档-最近更新-07-十月-2020-版本-356

129次阅读

共计 101426 个字符,预计需要花费 254 分钟才能阅读完成。

摘自:https://mybatis.org/mybatis-3…

简介

什么是 MyBatis?

MyBatis 是一款优良的长久层框架,它反对自定义 SQL、存储过程以及高级映射。MyBatis 罢黜了简直所有的 JDBC 代码以及设置参数和获取后果集的工作。MyBatis 能够通过简略的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,一般老式 Java 对象)为数据库中的记录。

帮忙改良文档 …

如果你发现文档有任何的脱漏,或短少某一个性能点的阐明,最好的解决办法是先本人学习,而后为脱漏的部份补上相应的文档。

该文档 xdoc 格局的源码文件可通过我的项目的 Git 代码库来获取。复刻该源码库,作出更新,并提交 Pull Request 吧。

还有其余像你一样的人都须要浏览这份文档,而你,就是这份文档最好的作者。

文档的翻译版本

您能够浏览 MyBatis 文档的其余语言版本:

  • English
  • Español
  • 日本語
  • 한국어
  • 简体中文

想用你的母语来理解 MyBatis 吗?那就将文档翻译成你的母语并提供给咱们吧!


入门

装置

要应用 MyBatis,只需将 mybatis-x.x.x.jar 文件置于类门路(classpath)中即可。

如果应用 Maven 来构建我的项目,则需将上面的依赖代码置于 pom.xml 文件中:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

从 XML 中构建 SqlSessionFactory

每个基于 MyBatis 的利用都是以一个 SqlSessionFactory 的实例为外围的。SqlSessionFactory 的实例能够通过 SqlSessionFactoryBuilder 取得。而 SqlSessionFactoryBuilder 则能够从 XML 配置文件或一个事后配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,倡议应用类门路下的资源文件进行配置。但也能够应用任意的输出流(InputStream)实例,比方用文件门路字符串或 file:// URL 结构的输出流。MyBatis 蕴含一个名叫 Resources 的工具类,它蕴含一些实用办法,使得从类门路或其它地位加载资源文件更加容易。

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

XML 配置文件中蕴含了对 MyBatis 零碎的外围设置,包含获取数据库连贯实例的数据源(DataSource)以及决定事务作用域和管制形式的事务管理器(TransactionManager)。前面会再探讨 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="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

当然,还有很多能够在 XML 文件中配置的选项,下面的示例仅列举了最要害的局部。留神 XML 头部的申明,它用来验证 XML 文档的正确性。environment 元素体中蕴含了事务管理和连接池的配置。mappers 元素则蕴含了一组映射器(mapper),这些映射器的 XML 映射文件蕴含了 SQL 代码和映射定义信息。

不应用 XML 构建 SqlSessionFactory

如果你更违心间接从 Java 代码而不是 XML 文件中创立配置,或者想要创立你本人的配置建造器,MyBatis 也提供了残缺的配置类,提供了所有与 XML 文件等价的配置项。

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

留神该例中,configuration 增加了一个映射器类(mapper class)。映射器类是 Java 类,它们蕴含 SQL 映射注解从而防止依赖 XML 文件。不过,因为 Java 注解的一些限度以及某些 MyBatis 映射的复杂性,要应用大多数高级映射(比方:嵌套联结映射),依然须要应用 XML 配置。有鉴于此,如果存在一个同名 XML 配置文件,MyBatis 会主动查找并加载它(在这个例子中,基于类门路和 BlogMapper.class 的类名,会加载 BlogMapper.xml)。具体细节稍后探讨。

从 SqlSessionFactory 中获取 SqlSession

既然有了 SqlSessionFactory,顾名思义,咱们能够从中取得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有办法。你能够通过 SqlSession 实例来间接执行已映射的 SQL 语句。例如:

try (SqlSession session = sqlSessionFactory.openSession()) {Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}

诚然,这种形式可能失常工作,对应用旧版本 MyBatis 的用户来说也比拟相熟。但当初有了一种更简洁的形式——应用和指定语句的参数和返回值相匹配的接口(比方 BlogMapper.class),当初你的代码不仅更清晰,更加类型平安,还不必放心可能出错的字符串字面值以及强制类型转换。

例如:

try (SqlSession session = sqlSessionFactory.openSession()) {BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

当初咱们来探索一下这段代码到底做了些什么。

探索已映射的 SQL 语句

当初你可能很想晓得 SqlSession 和 Mapper 到底具体执行了些什么操作,但 SQL 语句映射是个相当宽泛的话题,可能会占去文档的大部分篇幅。但为了让你可能理解个大略,这里会给出几个例子。

在下面提到的例子中,一个语句既能够通过 XML 定义,也能够通过注解定义。咱们先看看 XML 定义语句的形式,事实上 MyBatis 提供的所有个性都能够利用基于 XML 的映射语言来实现,这使得 MyBatis 在过来的数年间得以风行。如果你用过旧版本的 MyBatis,你应该对这个概念比拟相熟。但相比于之前的版本,新版本改良了许多 XML 的配置,前面咱们会提到这些改良。这里给出一个基于 XML 映射语句的示例,它应该能够满足上个示例中 SqlSession 的调用。

<?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>

为了这个简略的例子,咱们仿佛写了不少配置,但其实并不多。在一个 XML 映射文件中,能够定义无数个映射语句,这样一来,XML 头部和文档类型申明局部就显得微不足道了。文档的其它局部很直白,容易了解。它在命名空间“org.mybatis.example.BlogMapper”中定义了一个名为“selectBlog”的映射语句,这样你就能够用全限定名“org.mybatis.example.BlogMapper.selectBlog”来调用映射语句了,就像下面例子中那样:

Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

你可能会留神到,这种形式和用全限定名调用 Java 对象的办法相似。这样,该命名就能够间接映射到在命名空间中同名的映射器类,并将已映射的 select 语句匹配到对应名称、参数和返回类型的办法。因而你就能够像下面那样,不费吹灰之力地在对应的映射器接口调用办法,就像上面这样:

BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);

第二种办法有很多劣势,首先它不依赖于字符串字面值,会更平安一点;其次,如果你的 IDE 有代码补全性能,那么代码补全能够帮你疾速抉择到映射好的 SQL 语句。


提醒 对命名空间的一点补充

在之前版本的 MyBatis 中,命名空间(Namespaces)的作用并不大,是可选的。但当初,随着命名空间越发重要,你必须指定命名空间。

命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你下面见到的接口绑定。就算你感觉临时用不到接口绑定,你也应该遵循这里的规定,以防哪天你扭转了主见。久远来看,只有将命名空间置于适合的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更不便地应用 MyBatis。

命名解析:为了缩小输入量,MyBatis 对所有具备名称的配置元素(包含语句,后果映射,缓存等)应用了如下的命名解析规定。

  • 全限定名(比方“com.mypackage.MyMapper.selectAllThings)将被间接用于查找及应用。
  • 短名称(比方“selectAllThings”)如果全局惟一也能够作为一个独自的援用。如果不惟一,有两个或两个以上的雷同名称(比方“com.foo.selectAllThings”和“com.bar.selectAllThings”),那么应用时就会产生“短名称不惟一”的谬误,这种状况下就必须应用全限定名。

对于像 BlogMapper 这样的映射器类来说,还有另一种办法来实现语句映射。它们映射的语句能够不必 XML 来配置,而能够应用 Java 注解来配置。比方,下面的 XML 示例能够被替换成如下的配置:

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

应用注解来映射简略语句会使代码显得更加简洁,但对于略微简单一点的语句,Java 注解不仅力不从心,还会让你本就简单的 SQL 语句更加凌乱不堪。因而,如果你须要做一些很简单的操作,最好用 XML 来映射语句。

抉择何种形式来配置映射,以及认为是否应该要对立映射语句定义的模式,齐全取决于你和你的团队。换句话说,永远不要拘泥于一种形式,你能够很轻松的在基于注解和 XML 的语句映射形式间自在移植和切换。

作用域(Scope)和生命周期

了解咱们之前探讨过的不同作用域和生命周期类别是至关重要的,因为谬误的应用会导致十分重大的并发问题。


提醒 对象生命周期和依赖注入框架

依赖注入框架能够创立线程平安的、基于事务的 SqlSession 和映射器,并将它们间接注入到你的 bean 中,因而能够间接疏忽它们的生命周期。如果对如何通过依赖注入框架应用 MyBatis 感兴趣,能够钻研一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。


SqlSessionFactoryBuilder

这个类能够被实例化、应用和抛弃,一旦创立了 SqlSessionFactory,就不再须要它了。因而 SqlSessionFactoryBuilder 实例的最佳作用域是办法作用域(也就是部分办法变量)。你能够重用 SqlSessionFactoryBuilder 来创立多个 SqlSessionFactory 实例,但最好还是不要始终保留着它,以保障所有的 XML 解析资源能够被开释给更重要的事件。

SqlSessionFactory

SqlSessionFactory 一旦被创立就应该在利用的运行期间始终存在,没有任何理由抛弃它或从新创立另一个实例。应用 SqlSessionFactory 的最佳实际是在利用运行期间不要反复创立屡次,屡次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因而 SqlSessionFactory 的最佳作用域是利用作用域。有很多办法能够做到,最简略的就是应用单例模式或者动态单例模式。

SqlSession

每个线程都应该有它本人的 SqlSession 实例。SqlSession 的实例不是线程平安的,因而是不能被共享的,所以它的最佳的作用域是申请或办法作用域。相对不能将 SqlSession 实例的援用放在一个类的动态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的援用放在任何类型的托管作用域中,比方 Servlet 框架中的 HttpSession。如果你当初正在应用一种 Web 框架,思考将 SqlSession 放在一个和 HTTP 申请类似的作用域中。换句话说,每次收到 HTTP 申请,就能够关上一个 SqlSession,返回一个响应后,就敞开它。这个敞开操作很重要,为了确保每次都能执行敞开操作,你应该把这个敞开操作放到 finally 块中。上面的示例就是一个确保 SqlSession 敞开的规范模式:

try (SqlSession session = sqlSessionFactory.openSession()) {// 你的应用逻辑代码}

在所有代码中都遵循这种应用模式,能够保障所有数据库资源都能被正确地敞开。

映射器实例

映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中取得的。尽管从技术层面上来讲,任何映射器实例的最大作用域与申请它们的 SqlSession 雷同。但办法作用域才是映射器实例的最合适的作用域。也就是说,映射器实例应该在调用它们的办法中被获取,应用结束之后即可抛弃。映射器实例并不需要被显式地敞开。只管在整个申请作用域保留映射器实例不会有什么问题,然而你很快会发现,在这个作用域上治理太多像 SqlSession 的资源会让你忙不过来。因而,最好将映射器放在办法作用域内。就像上面的例子一样:

try (SqlSession session = sqlSessionFactory.openSession()) {BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 你的应用逻辑代码
}

配置

MyBatis 的配置文件蕴含了会深深影响 MyBatis 行为的设置和属性信息。配置文档的顶层构造如下:

  • configuration(配置)

    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)

      • environment(环境变量)

        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)

属性(properties)

这些属性能够在内部进行配置,并能够进行动静替换。你既能够在典型的 Java 属性文件中配置这些属性,也能够在 properties 元素的子元素中设置。例如:

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

设置好的属性能够在整个配置文件中用来替换须要动静配置的属性值。比方:

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

这个例子中的 username 和 password 将会由 properties 元素中设置的相应值来替换。driver 和 url 属性将会由 config.properties 文件中对应的值来替换。这样就为配置提供了诸多灵便抉择。

也能够在 SqlSessionFactoryBuilder.build() 办法中传入属性值。例如:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);

// ... 或者 ...

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);

如果一个属性在不只一个中央进行了配置,那么,MyBatis 将依照上面的程序来加载:

  • 首先读取在 properties 元素体内指定的属性。
  • 而后依据 properties 元素中的 resource 属性读取类门路下属性文件,或依据 url 属性指定的门路读取属性文件,并笼罩之前读取过的同名属性。
  • 最初读取作为办法参数传递的属性,并笼罩之前读取过的同名属性。

因而,通过办法参数传递的属性具备最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

从 MyBatis 3.4.2 开始,你能够为占位符指定一个默认值。例如:

<dataSource type="POOLED">
  <!-- ... -->
  <property name="username" value="${username:ut_user}"/> <!-- 如果属性 'username' 没有被配置,'username' 属性的值将为 'ut_user' -->
</dataSource>

这个个性默认是敞开的。要启用这个个性,须要增加一个特定的属性来开启这个个性。例如:

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- 启用默认值个性 -->
</properties>

提醒 如果你在属性名中应用了 ":" 字符(如:db:username),或者在 SQL 映射中应用了 OGNL 表达式的三元运算符(如:${tableName != null ? tableName : 'global_constants'}),就须要设置特定的属性来批改分隔属性名和默认值的字符。例如:

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> <!-- 批改默认值的分隔符 -->
</properties>
<dataSource type="POOLED">
  <!-- ... -->
  <property name="username" value="${db:username?:ut_user}"/>
</dataSource>

设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会扭转 MyBatis 的运行时行为。下表形容了设置中各项设置的含意、默认值等。

设置名 形容 有效值 默认值
cacheEnabled 全局性地开启或敞开所有映射器配置文件中已配置的任何缓存。 true \ false true
lazyLoadingEnabled 提早加载的全局开关。当开启时,所有关联对象都会提早加载。特定关联关系中可通过设置 fetchType 属性来笼罩该项的开关状态。 true \ false false
aggressiveLazyLoading 开启时,任一办法的调用都会加载该对象的所有提早加载属性。否则,每个提早加载属性会按需加载(参考 lazyLoadTriggerMethods)。 true \ false false(在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled 是否容许单个语句返回多后果集(须要数据库驱动反对)。 true \ false true
useColumnLabel 应用列标签代替列名。理论体现依赖于数据库驱动,具体可参考数据库驱动的相干文档,或通过比照测试来察看。 true \ false true
useGeneratedKeys 容许 JDBC 反对主动生成主键,须要数据库驱动反对。如果设置为 true,将强制应用主动生成主键。只管一些数据库驱动不反对此个性,但仍可失常工作(如 Derby)。 true \ false False
autoMappingBehavior 指定 MyBatis 应如何主动映射列到字段或属性。NONE 示意敞开主动映射;PARTIAL 只会主动映射没有定义嵌套后果映射的字段。FULL 会主动映射任何简单的后果集(无论是否嵌套)。 NONE, PARTIAL, FULL PARTIAL
autoMappingUnknownColumnBehavior 指定发现主动映射指标未知列(或未知属性类型)的行为。NONE: 不做任何反馈WARNING: 输入正告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARNFAILING: 映射失败 (抛出 SqlSessionException) NONE, WARNING, FAILING NONE
defaultExecutorType 配置默认的执行器。SIMPLE 就是一般的执行器;REUSE 执行器会重用预处理语句(PreparedStatement);BATCH 执行器不仅重用语句还会执行批量更新。 SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout 设置超时工夫,它决定数据库驱动期待数据库响应的秒数。 任意正整数 未设置 (null)
defaultFetchSize 为驱动的后果集获取数量(fetchSize)设置一个倡议值。此参数只能够在查问设置中被笼罩。 任意正整数 未设置 (null)
defaultResultSetType 指定语句默认的滚动策略。(新增于 3.5.2) FORWARD_ONLY \ SCROLL_SENSITIVE \ SCROLL_INSENSITIVE \ DEFAULT(等同于未设置) 未设置 (null)
safeRowBoundsEnabled 是否容许在嵌套语句中应用分页(RowBounds)。如果容许应用则设置为 false。 true \ false False
safeResultHandlerEnabled 是否容许在嵌套语句中应用后果处理器(ResultHandler)。如果容许应用则设置为 false。 true \ false True
mapUnderscoreToCamelCase 是否开启驼峰命名主动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true \ false False
localCacheScope MyBatis 利用本地缓存机制(Local Cache)避免循环援用和减速反复的嵌套查问。默认值为 SESSION,会缓存一个会话中执行的所有查问。若设置值为 STATEMENT,本地缓存将仅用于执行语句,对雷同 SqlSession 的不同查问将不会进行缓存。 SESSION \ STATEMENT SESSION
jdbcTypeForNull 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。某些数据库驱动须要指定列的 JDBC 类型,少数状况间接用个别类型即可,比方 NULL、VARCHAR 或 OTHER。 JdbcType 常量,罕用值:NULL、VARCHAR 或 OTHER。 OTHER
lazyLoadTriggerMethods 指定对象的哪些办法触发一次提早加载。 用逗号分隔的办法列表。 equals,clone,hashCode,toString
defaultScriptingLanguage 指定动静 SQL 生成应用的默认脚本语言。 一个类型别名或全限定类名。 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler 指定 Enum 应用的默认 TypeHandler。(新增于 3.4.5) 一个类型别名或全限定类名。 org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls 指定当后果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)办法,这在依赖于 Map.keySet() 或 null 值进行初始化时比拟有用。留神根本类型(int、boolean 等)是不能设置成 null 的。 true \ false false
returnInstanceForEmptyRow 当返回行的所有列都是空时,MyBatis 默认返回 null。当开启这个设置时,MyBatis 会返回一个空实例。请留神,它也实用于嵌套的后果集(如汇合或关联)。(新增于 3.4.2) true \ false false
logPrefix 指定 MyBatis 减少到日志名称的前缀。 任何字符串 未设置
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将主动查找。 SLF4J \ LOG4J \ LOG4J2 \ JDK_LOGGING \ COMMONS_LOGGING \ STDOUT_LOGGING \ NO_LOGGING 未设置
proxyFactory 指定 Mybatis 创立可提早加载对象所用到的代理工具。 CGLIB \ JAVASSIST JAVASSIST(MyBatis 3.3 以上)
vfsImpl 指定 VFS 的实现 自定义 VFS 的实现的类全限定名,以逗号分隔。 未设置
useActualParamName 容许应用办法签名中的名称作为语句参数名称。为了应用该个性,你的我的项目必须采纳 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) true \ false true
configurationFactory 指定一个提供 Configuration 实例的类。这个被返回的 Configuration 实例用来加载被反序列化对象的提早加载属性值。这个类必须蕴含一个签名为static Configuration getConfiguration() 的办法。(新增于 3.2.3) 一个类型别名或齐全限定类名。 未设置
shrinkWhitespacesInSql 从 SQL 中删除多余的空格字符。请留神,这也会影响 SQL 中的文字字符串。(新增于 3.5.5) true \ false false
defaultSqlProviderType Specifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the type(or value) attribute on sql provider annotation(e.g. @SelectProvider), when these attribute was omitted. A type alias or fully qualified class name Not set

一个配置残缺的 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="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <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)

类型别名可为 Java 类型设置一个缩写名字。它仅用于 XML 配置,意在升高冗余的全限定类名书写。例如:

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

当这样配置时,Blog 能够用在任何应用 domain.blog.Blog 的中央。

也能够指定一个包名,MyBatis 会在包名上面搜寻须要的 Java Bean,比方:

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

每一个在包 domain.blog 中的 Java Bean,在没有注解的状况下,会应用 Bean 的首字母小写的非限定类名来作为它的别名。比方 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见上面的例子:

@Alias("author")
public class Author {...}

上面是一些为常见的 Java 类型内建的类型别名。它们都是不辨别大小写的,留神,为了应答原始类型的命名反复,采取了非凡的命名格调。

别名 映射的类型
_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)

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从后果集中取出一个值时,都会用类型处理器将获取到的值以适合的形式转换成 Java 类型。下表形容了一些默认的类型处理器。

提醒 从 3.4.5 开始,MyBatis 默认反对 JSR-310(日期和工夫 API)。

类型处理器 Java 类型 JDBC 类型
BooleanTypeHandler java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
ByteTypeHandler java.lang.Byte, byte 数据库兼容的 NUMERICBYTE
ShortTypeHandler java.lang.Short, short 数据库兼容的 NUMERICSMALLINT
IntegerTypeHandler java.lang.Integer, int 数据库兼容的 NUMERICINTEGER
LongTypeHandler java.lang.Long, long 数据库兼容的 NUMERICBIGINT
FloatTypeHandler java.lang.Float, float 数据库兼容的 NUMERICFLOAT
DoubleTypeHandler java.lang.Double, double 数据库兼容的 NUMERICDOUBLE
BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERICDECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR
ClobReaderTypeHandler java.io.Reader
ClobTypeHandler java.lang.String CLOB, LONGVARCHAR
NStringTypeHandler java.lang.String NVARCHAR, NCHAR
NClobTypeHandler java.lang.String NCLOB
BlobInputStreamTypeHandler java.io.InputStream
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 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandler java.lang.String SQLXML
InstantTypeHandler java.time.Instant TIMESTAMP
LocalDateTimeTypeHandler java.time.LocalDateTime TIMESTAMP
LocalDateTypeHandler java.time.LocalDate DATE
LocalTimeTypeHandler java.time.LocalTime TIME
OffsetDateTimeTypeHandler java.time.OffsetDateTime TIMESTAMP
OffsetTimeTypeHandler java.time.OffsetTime TIME
ZonedDateTimeTypeHandler java.time.ZonedDateTime TIMESTAMP
YearTypeHandler java.time.Year INTEGER
MonthTypeHandler java.time.Month INTEGER
YearMonthTypeHandler java.time.YearMonth VARCHARLONGVARCHAR
JapaneseDateTypeHandler java.time.chrono.JapaneseDate DATE

你能够重写已有的类型处理器或创立你本人的类型处理器来解决不反对的或非规范的类型。具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口,或继承一个很便当的类 org.apache.ibatis.type.BaseTypeHandler,并且能够(可选地)将它映射到一个 JDBC 类型。比方:

// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  @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="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

应用上述的类型处理器将会笼罩已有的解决 Java String 类型的属性以及 VARCHAR 类型的参数和后果的类型处理器。要留神 MyBatis 不会通过检测数据库元信息来决定应用哪种类型,所以你必须在参数和后果映射中指明字段是 VARCHAR 类型,以使其可能绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才分明数据类型。

通过类型处理器的泛型,MyBatis 能够得悉该类型处理器解决的 Java 类型,不过这种行为能够通过两种办法扭转:

  • 在类型处理器的配置元素(typeHandler 元素)上减少一个 javaType 属性(比方:javaType="String");
  • 在类型处理器的类上减少一个 @MappedTypes 注解指定与其关联的 Java 类型列表。如果在 javaType 属性中也同时指定,则注解上的配置将被疏忽。

能够通过两种形式来指定关联的 JDBC 类型:

  • 在类型处理器的配置元素上减少一个 jdbcType 属性(比方:jdbcType="VARCHAR");
  • 在类型处理器的类上减少一个 @MappedJdbcTypes 注解指定与其关联的 JDBC 类型列表。如果在 jdbcType 属性中也同时指定,则注解上的配置将被疏忽。

当在 ResultMap 中决定应用哪种类型处理器时,此时 Java 类型是已知的(从后果类型中取得),然而 JDBC 类型是未知的。因而 Mybatis 应用 javaType=[Java 类型], jdbcType=null 的组合来抉择一个类型处理器。这意味着应用 @MappedJdbcTypes 注解能够 限度 类型处理器的作用范畴,并且能够确保,除非显式地设置,否则类型处理器在 ResultMap 中将不会失效。如果心愿能在 ResultMap 中隐式地应用类型处理器,那么设置 @MappedJdbcTypes 注解的 includeNullJdbcType=true 即可。然而从 Mybatis 3.4.0 开始,如果某个 Java 类型 只有一个 注册的类型处理器,即便没有设置 includeNullJdbcType=true,那么这个类型处理器也会是 ResultMap 应用 Java 类型时的默认处理器。

最初,能够让 MyBatis 帮你查找类型处理器:

<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>

留神在应用主动发现性能的时候,只能通过注解形式来指定 JDBC 的类型。

你能够创立可能解决多个类的泛型类型处理器。为了应用泛型类型处理器,须要减少一个承受该类的 class 作为参数的结构器,这样 MyBatis 会在结构一个类型处理器实例的时候传入一个具体的类。

//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {

  private Class<E> type;

  public GenericTypeHandler(Class<E> type) {if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
    this.type = type;
  }
  ...

EnumTypeHandlerEnumOrdinalTypeHandler 都是泛型类型处理器,咱们将会在接下来的局部具体探讨。

解决枚举类型

若想映射枚举类型 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>

但要是你想在一个中央将 Enum 映射成字符串,在另外一个中央映射成整形值呢?

主动映射器(auto-mapper)会主动地选用 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

对象工厂(objectFactory)

每次 MyBatis 创立后果对象的新实例时,它都会应用一个对象工厂(ObjectFactory)实例来实现实例化工作。默认的对象工厂须要做的仅仅是实例化指标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。如果想笼罩对象工厂的默认行为,能够通过创立本人的对象工厂来实现。比方:

// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {public Object create(Class type) {return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {return super.create(type, constructorArgTypes, constructorArgs);
  }
  public void setProperties(Properties properties) {super.setProperties(properties);
  }
  public <T> boolean isCollection(Class<T> type) {return Collection.class.isAssignableFrom(type);
  }}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

ObjectFactory 接口很简略,它蕴含两个创立实例用的办法,一个是解决默认无参构造方法的,另外一个是解决带参数的构造方法的。另外,setProperties 办法能够被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后,objectFactory 元素体中定义的属性会被传递给 setProperties 办法。

插件(plugins)

MyBatis 容许你在映射语句执行过程中的某一点进行拦挡调用。默认状况下,MyBatis 容许应用插件来拦挡的办法调用包含:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

这些类中办法的细节能够通过查看每个办法的签名来发现,或者间接查看 MyBatis 发行包中的源代码。如果你想做的不仅仅是监控办法的调用,那么你最好相当理解要重写的办法的行为。因为在试图批改或重写已有办法的行为时,很可能会毁坏 MyBatis 的外围模块。这些都是更底层的类和办法,所以应用插件的时候要特地当心。

通过 MyBatis 提供的弱小机制,应用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦挡的办法签名即可。

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {private Properties properties = new Properties();
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre processing if need
    Object returnObject = invocation.proceed();
    // implement post processing if need
    return returnObject;
  }
  public void setProperties(Properties properties) {this.properties = properties;}
}
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

下面的插件将会拦挡在 Executor 实例中所有的“update”办法调用,这里的 Executor 是负责执行底层映射语句的外部对象。

提醒 笼罩配置类

除了用插件来批改 MyBatis 外围行为以外,还能够通过齐全笼罩配置类来达到目标。只需继承配置类后笼罩其中的某个办法,再把它传递到 SqlSessionFactoryBuilder.build(myConfig) 办法即可。再次重申,这可能会极大影响 MyBatis 的行为,务请慎之又慎。

环境配置(environments)

MyBatis 能够配置成适应多种环境,这种机制有助于将 SQL 映射利用于多种数据库之中,现实情况下有多种理由须要这么做。例如,开发、测试和生产环境须要有不同的配置;或者想在具备雷同 Schema 的多个生产数据库中应用雷同的 SQL 映射。还有许多相似的应用场景。

不过要记住:只管能够配置多个环境,但每个 SqlSessionFactory 实例只能抉择一种环境。

所以,如果你想连贯两个数据库,就须要创立两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就须要三个实例,依此类推,记起来很简略:

  • 每个数据库对应一个 SqlSessionFactory 实例

为了指定创立哪种环境,只有将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。能够承受环境配置的两个办法签名是:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

如果疏忽了环境参数,那么将会加载默认环境,如下所示:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);

environments 元素定义了如何配置环境。

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

留神一些关键点:

  • 默认应用的环境 ID(比方:default=”development”)。
  • 每个 environment 元素定义的环境 ID(比方:id=”development”)。
  • 事务管理器的配置(比方:type=”JDBC”)。
  • 数据源的配置(比方:type=”POOLED”)。

默认环境和环境 ID 顾名思义。环境能够随便命名,但务必保障默认的环境 ID 要匹配其中一个环境 ID。

事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type=”[JDBC|MANAGED]”):

  • JDBC – 这个配置间接应用了 JDBC 的提交和回滚设施,它依赖从数据源取得的连贯来治理事务作用域。
  • MANAGED – 这个配置简直没做什么。它从不提交或回滚一个连贯,而是让容器来治理事务的整个生命周期(比方 JEE 应用服务器的上下文)。默认状况下它会敞开连贯。然而一些容器并不心愿连贯被敞开,因而须要将 closeConnection 属性设置为 false 来阻止默认的敞开行为。例如:

    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>

提醒 如果你正在应用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会应用自带的管理器来笼罩后面的配置。

这两种事务管理器类型都不须要设置任何属性。它们其实是类型别名,换句话说,你能够用 TransactionFactory 接口实现类的全限定名或类型别名代替它们。

public interface TransactionFactory {default void setProperties(Properties props) { // 从 3.5.2 开始,该办法为默认办法
    // 空实现
  }
  Transaction newTransaction(Connection conn);
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

在事务管理器实例化后,所有在 XML 中配置的属性将会被传递给 setProperties() 办法。你的实现还须要创立一个 Transaction 接口的实现类,这个接口也很简略:

public interface Transaction {Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;}

应用这两个接口,你能够齐全自定义 MyBatis 对事务的解决。

数据源(dataSource)

dataSource 元素应用规范的 JDBC 数据源接口来配置 JDBC 连贯对象的资源。

  • 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。尽管数据源配置是可选的,但如果要启用提早加载个性,就必须配置数据源。

有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]”):

UNPOOLED– 这个数据源的实现会每次申请时关上和敞开连贯。尽管有点慢,但对那些数据库连贯可用性要求不高的简略应用程序来说,是一个很好的抉择。性能体现则依赖于应用的数据库,对某些数据库来说,应用连接池并不重要,这个配置就很适宜这种情景。UNPOOLED 类型的数据源仅仅须要配置以下 5 种属性:

  • driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能蕴含的数据源类)。
  • url – 这是数据库的 JDBC URL 地址。
  • username – 登录数据库的用户名。
  • password – 登录数据库的明码。
  • defaultTransactionIsolationLevel – 默认的连贯事务隔离级别。
  • defaultNetworkTimeout – 期待数据库操作实现的默认网络超时工夫(单位:毫秒)。查看 java.sql.Connection#setNetworkTimeout() 的 API 文档以获取更多信息。

作为可选项,你也能够传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:

  • driver.encoding=UTF8

这将通过 DriverManager.getConnection(url, driverProperties) 办法传递值为 UTF8encoding 属性给数据库驱动。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连贯对象组织起来,防止了创立新的连贯实例时所必须的初始化和认证工夫。这种解决形式很风行,能使并发 Web 利用疾速响应申请。

除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:

  • poolMaximumActiveConnections – 在任意工夫可存在的流动(正在应用)连贯数量,默认值:10
  • poolMaximumIdleConnections – 任意工夫可能存在的闲暇连接数。
  • poolMaximumCheckoutTime – 在被强制返回之前,池中连贯被检出(checked out)工夫,默认值:20000 毫秒(即 20 秒)
  • poolTimeToWait – 这是一个底层设置,如果获取连贯破费了相当长的工夫,连接池会打印状态日志并从新尝试获取一个连贯(防止在误配置的状况下始终失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
  • poolMaximumLocalBadConnectionTolerance – 这是一个对于坏连贯容忍度的底层设置,作用于每一个尝试从缓存池获取连贯的线程。如果这个线程获取到的是一个坏的连贯,那么这个数据源容许这个线程尝试从新获取一个新的连贯,然而这个从新尝试的次数不应该超过 poolMaximumIdleConnectionspoolMaximumLocalBadConnectionTolerance 之和。默认值:3(新增于 3.4.5)
  • poolPingQuery – 发送到数据库的侦测查问,用来测验连贯是否失常工作并筹备承受申请。默认是“NO PING QUERY SET”,这会导致少数数据库驱动出错时返回失当的谬误音讯。
  • poolPingEnabled – 是否启用侦测查问。若开启,须要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度十分快的 SQL 语句),默认值:false。
  • poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。能够被设置为和数据库连贯超时工夫一样,来防止不必要的侦测,默认值:0(即所有连贯每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时实用)。

JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中应用,容器能够集中或在内部配置数据源,而后搁置一个 JNDI 上下文的数据源援用。这种数据源配置只须要两个属性:

  • initial_context – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果疏忽,那么将会间接从 InitialContext 中寻找 data_source 属性。
  • data_source – 这是援用数据源实例地位的上下文门路。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则间接在 InitialContext 中查找。

和其余数据源配置相似,能够通过增加前缀“env.”间接把属性传递给 InitialContext。比方:

  • env.encoding=UTF8

这就会在 InitialContext 实例化时往它的构造方法传递值为 UTF8encoding 属性。

你能够通过实现接口 org.apache.ibatis.datasource.DataSourceFactory 来应用第三方数据源实现:

public interface DataSourceFactory {void setProperties(Properties props);
  DataSource getDataSource();}

org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父类来构建新的数据源适配器,比方上面这段插入 C3P0 数据源所必须的代码:

import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {public C3P0DataSourceFactory() {this.dataSource = new ComboPooledDataSource();
  }
}

为了令其工作,记得在配置文件中为每个心愿 MyBatis 调用的 setter 办法减少对应的属性。上面是一个能够连贯至 PostgreSQL 数据库的例子:

<dataSource type="org.myproject.C3P0DataSourceFactory">
  <property name="driver" value="org.postgresql.Driver"/>
  <property name="url" value="jdbc:postgresql:mydb"/>
  <property name="username" value="postgres"/>
  <property name="password" value="root"/>
</dataSource>

数据库厂商标识(databaseIdProvider)

MyBatis 能够依据不同的数据库厂商执行不同的语句,这种多厂商的反对是基于映射语句中的 databaseId 属性。MyBatis 会加载带有匹配以后数据库 databaseId 属性和所有不带 databaseId 属性的语句。如果同时找到带有 databaseId 和不带 databaseId 的雷同语句,则后者会被舍弃。为反对多厂商个性,只有像上面这样在 mybatis-config.xml 文件中退出 databaseIdProvider 即可:

<databaseIdProvider type="DB_VENDOR" />

databaseIdProvider 对应的 DB_VENDOR 实现会将 databaseId 设置为 DatabaseMetaData#getDatabaseProductName() 返回的字符串。因为通常状况下这些字符串都十分长,而且雷同产品的不同版本会返回不同的值,你可能想通过设置属性别名来使其变短:

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

在提供了属性别名时,databaseIdProvider 的 DB_VENDOR 实现会将 databaseId 设置为数据库产品名与属性中的名称第一个相匹配的值,如果没有匹配的属性,将会设置为“null”。在这个例子中,如果 getDatabaseProductName() 返回“Oracle (DataDirect)”,databaseId 将被设置为“oracle”。

你能够通过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注册来构建本人的 DatabaseIdProvider:

public interface DatabaseIdProvider {default void setProperties(Properties p) { // 从 3.5.2 开始,该办法为默认办法
    // 空实现
  }
  String getDatabaseId(DataSource dataSource) throws SQLException;
}

映射器(mappers)

既然 MyBatis 的行为曾经由上述元素配置完了,咱们当初就要来定义 SQL 映射语句了。但首先,咱们须要通知 MyBatis 到哪里去找到这些语句。在主动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的方法是间接通知 MyBatis 到哪里去找映射文件。你能够应用绝对于类门路的资源援用,或齐全限定资源定位符(包含 file:/// 模式的 URL),或类名和包名等。例如:

<!-- 应用绝对于类门路的资源援用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 应用齐全限定资源定位符(URL)-->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 应用映射器接口实现类的齐全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全副注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

这些配置会通知 MyBatis 去哪里找映射文件,剩下的细节就应该是每个 SQL 映射文件了,也就是接下来咱们要探讨的。


XML 映射器

MyBatis 的真正弱小在于它的语句映射,这是它的魔力所在。因为它的异样弱小,映射器的 XML 文件就显得绝对简略。如果拿它跟具备雷同性能的 JDBC 代码进行比照,你会立刻发现省掉了将近 95% 的代码。MyBatis 致力于缩小应用老本,让用户能更专一于 SQL 代码。

SQL 映射文件只有很少的几个顶级元素(依照应被定义的程序列出):

  • cache – 该命名空间的缓存配置。
  • cache-ref – 援用其它命名空间的缓存配置。
  • resultMap – 形容如何从数据库后果集中加载对象,是最简单也是最弱小的元素。
  • parameterMap – 老式格调的参数映射。此元素已被废除,并可能在未来被移除!请应用行内参数映射。文档中不会介绍此元素。
  • sql – 可被其它语句援用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查问语句。

下一部分将从语句自身开始来形容每个元素的细节。

select

查问语句是 MyBatis 中最罕用的元素之一——光能把数据存到数据库中价值并不大,还要能从新取出来才有用,少数利用也都是查问比批改要频繁。MyBatis 的根本准则之一是:在每个插入、更新或删除操作之间,通常会执行多个查问操作。因而,MyBatis 在查问和后果映射做了相当多的改良。一个简略查问的 select 元素是非常简单的。比方:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

这个语句名为 selectPerson,承受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是后果行中的对应值。

留神参数符号:

#{id}

这就通知 MyBatis 创立一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:

// 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

当然,应用 JDBC 就意味着应用更多的代码,以便提取后果并将它们映射到对象实例中,而这就是 MyBatis 的拿手好戏。参数和后果映射的具体细节会别离在前面独自的大节中阐明。

select 元素容许你配置很多属性来配置每条语句的行为细节。

<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
属性 形容
id 在命名空间中惟一的标识符,能够被用来援用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 能够通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap 用于援用内部 parameterMap 的属性,目前已被废除。请应用行内参数映射和 parameterType 属性。
resultType 冀望从这条语句中返回后果的类全限定名或别名。留神,如果返回的是汇合,那应该设置为汇合蕴含的类型,而不是汇合自身的类型。resultType 和 resultMap 之间只能同时应用一个。
resultMap 对外部 resultMap 的命名援用。后果映射是 MyBatis 最弱小的个性,如果你对其了解透彻,许多简单的映射问题都能迎刃而解。resultType 和 resultMap 之间只能同时应用一个。
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 或 DEFAULT(等价于 unset)中的一个,默认值为 unset(依赖数据库驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配以后 databaseId 的语句;如果带和不带的语句都有,则不带的会被疏忽。
resultOrdered 这个设置仅针对嵌套后果 select 语句:如果为 true,将会假如蕴含了嵌套后果集或是分组,当返回一个主后果行时,就不会产生对后面后果集的援用。这就使得在获取嵌套后果集的时候不至于内存不够用。默认值:false
resultSets 这个设置仅实用于多后果集的状况。它将列出语句执行后返回的后果集并赋予每个后果集一个名称,多个名称之间以逗号分隔。

insert, update 和 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">
属性 形容
id 在命名空间中惟一的标识符,能够被用来援用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 能够通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap 用于援用内部 parameterMap 的属性,目前已被废除。请应用行内参数映射和 parameterType 属性。
flushCache 将其设置为 true 后,只有语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)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 的语句;如果带和不带的语句都有,则不带的会被疏忽。

上面是 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>

如前所述,插入语句的配置规定更加丰盛,在插入语句外面有一些额定的属性和子元素用来解决主键的生成,并且提供了多种生成形式。

首先,如果你的数据库反对主动生成主键的字段(比方 MySQL 和 SQL Server),那么你能够设置 useGeneratedKeys=”true”,而后再把 keyProperty 设置为指标属性就 OK 了。例如,如果下面的 Author 表曾经在 id 列上应用了主动生成,那么语句能够批改为:

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
</insert>

如果你的数据库还反对多行插入, 你也能够传入一个 Author 数组或汇合,并返回主动生成的主键。

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

对于不反对主动生成主键列的数据库和可能不反对主动生成主键的 JDBC 驱动,MyBatis 有另外一种办法来生成主键。

这里有一个简略(也很傻)的示例,它能够生成一个随机 ID(不倡议理论应用,这里只是为了展现 MyBatis 解决问题的灵活性和宽容度):

<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 元素中的语句,并设置 Author 的 id,而后才会调用插入语句。这样就实现了数据库主动生成主键相似的行为,同时放弃了 Java 代码的简洁。

selectKey 元素形容如下:

<selectKey
  keyProperty="id"
  resultType="int"
  order="BEFORE"
  statementType="PREPARED">
属性 形容
keyProperty selectKey 语句后果应该被设置到的指标属性。如果生成列不止一个,能够用逗号分隔多个属性名称。
keyColumn 返回后果集中生成列属性的列名。如果生成列不止一个,能够用逗号分隔多个属性名称。
resultType 后果的类型。通常 MyBatis 能够推断进去,然而为了更加精确,写上也不会有什么问题。MyBatis 容许将任何简略类型用作主键的类型,包含字符串。如果生成列不止一个,则能够应用蕴含冀望属性的 Object 或 Map。
order 能够设置为 BEFOREAFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,而后是 selectKey 中的语句 – 这和 Oracle 数据库的行为类似,在插入语句外部可能有嵌入索引调用。
statementType 和后面一样,MyBatis 反对 STATEMENTPREPAREDCALLABLE 类型的映射语句,别离代表 Statement, PreparedStatementCallableStatement 类型。

sql

这个元素能够用来定义可重用的 SQL 代码片段,以便在其它语句中应用。参数能够动态地(在加载的时候)确定下来,并且能够在不同的 include 元素中定义不同的参数值。比方:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

这个 SQL 片段能够在其它语句中应用,例如:

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

也能够在 include 元素的 refid 属性或外部语句中应用属性值,例如:

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

参数

之前见到的所有语句都应用了简略的参数模式。但实际上,参数是 MyBatis 十分弱小的元素。对于大多数简略的应用场景,你都不须要应用简单的参数,比方:

<select id="selectUsers" resultType="User">
  select id, username, password
  from users
  where id = #{id}
</select>

下面的这个示例阐明了一个非常简单的命名参数映射。鉴于参数类型(parameterType)会被主动设置为 int,这个参数能够随便命名。原始类型或简略数据类型(比方 IntegerString)因为没有其它属性,会用它们的值来作为参数。然而,如果传入一个简单的对象,行为就会有点不一样了。比方:

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

如果 User 类型的参数对象传递到了语句中,会查找 id、username 和 password 属性,而后将它们的值传入预处理语句的参数中。

对传递语句参数来说,这种形式真是干脆利落。不过参数映射的性能远不止于此。

首先,和 MyBatis 的其它局部一样,参数也能够指定一个非凡的数据类型。

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

和 MyBatis 的其它局部一样,简直总是能够依据参数对象的类型确定 javaType,除非该对象是一个 HashMap。这个时候,你须要显式指定 javaType 来确保正确的类型处理器(TypeHandler)被应用。

提醒 JDBC 要求,如果一个列容许应用 null 值,并且会应用值为 null 的参数,就必须要指定 JDBC 类型(jdbcType)。浏览 PreparedStatement.setNull() 的 JavaDoc 来获取更多信息。

要更进一步地自定义类型解决形式,能够指定一个非凡的类型处理器类(或别名),比方:

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

参数的配置如同越来越繁琐了,但实际上,很少须要如此繁琐的配置。

对于数值类型,还能够设置 numericScale 指定小数点后保留的位数。

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

最初,mode 属性容许你指定 INOUTINOUT 参数。如果参数的 modeOUTINOUT,将会批改参数对象的属性值,以便作为输入参数返回。如果 modeOUT(或 INOUT),而且 jdbcTypeCURSOR(也就是 Oracle 的 REFCURSOR),你必须指定一个 resultMap 援用来将后果集 ResultMap 映射到参数的类型上。要留神这里的 javaType 属性是可选的,如果留空并且 jdbcType 是 CURSOR,它会被主动地被设为 ResultMap

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

MyBatis 也反对很多高级的数据类型,比方构造体(structs),然而当应用 out 参数时,你必须显式设置类型的名称。比方(再次提醒,在理论中要像这样不能换行):

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

只管下面这些选项很弱小,但大多时候,你只须简略指定属性名,顶多要为可能为空的列指定 jdbcType,其余的事件交给 MyBatis 本人去推断就行了。

#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}

字符串替换

默认状况下,应用 #{} 参数语法时,MyBatis 会创立 PreparedStatement 参数占位符,并通过占位符平安地设置参数(就像应用 ? 一样)。这样做更平安,更迅速,通常也是首选做法,不过有时你就是想间接在 SQL 语句中直接插入一个不本义的字符串。比方 ORDER BY 子句,这时候你能够:

ORDER BY ${columnName}

这样,MyBatis 就不会批改或本义该字符串了。

当 SQL 语句中的元数据(如表名或列名)是动静生成的时候,字符串替换将会十分有用。举个例子,如果你想 select 一个表任意一列的数据时,不须要这样写:

@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);

@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);

@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);

// 其它的 "findByXxx" 办法

而是能够只写这样一个办法:

@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);

其中 ${column} 会被间接替换,而 #{value} 会应用 ? 预处理。这样,就能实现同样的工作:

User userOfId1 = userMapper.findByColumn("id", 1L);
User userOfNameKid = userMapper.findByColumn("name", "kid");
User userOfEmail = userMapper.findByColumn("email", "noone@nowhere.com");

这种形式也同样实用于替换表名的状况。

提醒 用这种形式承受用户的输出,并用作语句参数是不平安的,会导致潜在的 SQL 注入攻打。因而,要么不容许用户输出这些字段,要么自行本义并测验这些参数。

后果映射

resultMap 元素是 MyBatis 中最重要最弱小的元素。它能够让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情景下容许你进行一些 JDBC 不反对的操作。实际上,在为一些比方连贯的简单语句编写映射代码的时候,一份 resultMap 可能代替实现等同性能的数千行代码。ResultMap 的设计思维是,对简略的语句做到零配置,对于简单一点的语句,只须要形容语句之间的关系就行了。

之前你曾经见过简略映射语句的示例,它们没有显式指定 resultMap。比方:

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

上述语句只是简略地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。尽管在大部分状况下都够用,然而 HashMap 并不是一个很好的畛域模型。你的程序更可能会应用 JavaBean 或 POJO(Plain Old Java Objects,一般老式 Java 对象)作为畛域模型。MyBatis 对两者都提供了反对。看看上面这个 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;}
}

基于 JavaBean 的标准,下面这个类有 3 个属性:id,username 和 hashedPassword。这些属性会对应到 select 语句中的列名。

这样的一个 JavaBean 能够被映射到 ResultSet,就像映射到 HashMap 一样简略。

<select id="selectUsers" resultType="com.someapp.model.User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

类型别名是你的好帮手。应用它们,你就能够不必输出类的全限定名了。比方:

<!-- mybatis-config.xml 中 -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

在这些状况下,MyBatis 会在幕后主动创立一个 ResultMap,再依据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,能够在 SELECT 语句中设置列别名(这是一个根本的 SQL 个性)来实现匹配。比方:

<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,这就是 ResultMap 的优良之处——你齐全能够不必显式地配置它们。尽管下面的例子不必显式配置 ResultMap。但为了解说,咱们来看看如果在刚刚的示例中,显式应用内部的 resultMap 会怎么,这也是解决列名不匹配的另外一种形式。

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

而后在援用它的语句中设置 resultMap 属性就行了(留神咱们去掉了 resultType 属性)。比方:

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

如果这个世界总是这么简略就好了。

高级后果映射

MyBatis 创立时的一个思维是:数据库不可能永远是你所想或所需的那个样子。咱们心愿每个数据库都具备良好的第三范式或 BCNF 范式,惋惜它们并不都是那样。如果能有一种数据库映射模式,完满适配所有的应用程序,那就太好了,但惋惜也没有。而 ResultMap 就是 MyBatis 对这个问题的答案。

比方,咱们如何映射上面这个语句?

<!-- 非常复杂的语句 -->
<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>

你可能想把它映射到一个智能的对象模型,这个对象示意了一篇博客,它由某位作者所写,有很多的博文,每篇博文有零或多条的评论和标签。咱们先来看看上面这个残缺的例子,它是一个非常复杂的后果映射(假如作者,博客,博文,评论和标签都是类型别名)。不必缓和,咱们会一步一步地来阐明。尽管它看起来令人望而却步,但其实非常简单。

<!-- 非常复杂的后果映射 -->
<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

    – 一个简单类型的关联;许多后果将包装成这种类型

    • 嵌套后果映射 – 关联能够是 resultMap 元素,或是对其它后果映射的援用
  • collection

    – 一个简单类型的汇合

    • 嵌套后果映射 – 汇合能够是 resultMap 元素,或是对其它后果映射的援用
  • discriminator

    – 应用后果值来决定应用哪个

    resultMap
    • case

      – 基于某些值的后果映射

      • 嵌套后果映射 – case 也是一个后果映射,因而具备雷同的构造和元素;或者援用其它的后果映射
属性 形容
id 以后命名空间中的一个惟一标识,用于标识一个后果映射。
type 类的齐全限定名, 或者一个类型别名(对于内置的类型别名,能够参考下面的表格)。
autoMapping 如果设置这个属性,MyBatis 将会为本后果映射开启或者敞开主动映射。这个属性会笼罩全局的属性 autoMappingBehavior。默认值:未设置(unset)。

最佳实际 最好逐渐建设后果映射。单元测试能够在这个过程中起到很大帮忙。如果你尝试一次性创立像下面示例那么微小的后果映射,不仅容易出错,难度也会直线回升。所以,从最简略的状态开始,逐渐迭代。而且别忘了单元测试!有时候,框架的行为像是一个黑盒子(无论是否开源)。因而,为了确保实现的行为与你的冀望相一致,最好编写单元测试。并且单元测试在提交 bug 时也能起到很大的作用。

下一部分将具体阐明每个元素。

id & result

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

这些元素是后果映射的根底。idresult 元素都将一个列的值映射到一个简略数据类型(String, int, double, Date 等)的属性或字段。

这两者之间的惟一不同是,id 元素对应的属性会被标记为对象的标识符,在比拟对象实例时应用。这样能够进步整体的性能,尤其是进行缓存和嵌套后果映射(也就是连贯映射)的时候。

两个元素都有一些属性:

属性 形容
property 映射到列后果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先应用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。无论是哪一种情景,你都能够应用常见的点式分隔模式进行简单属性导航。比方,你能够这样映射一些简略的货色:“username”,或者映射到一些简单的货色上:“address.street.number”。
column 数据库中的列名,或者是列的别名。个别状况下,这和传递给 resultSet.getString(columnName) 办法的参数一样。
javaType 一个 Java 类的全限定名,或一个类型别名(对于内置的类型别名,能够参考下面的表格)。如果你映射到一个 JavaBean,MyBatis 通常能够推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保障行为与冀望的相一致。
jdbcType JDBC 类型,所反对的 JDBC 类型参见这个表格之后的“反对的 JDBC 类型”。只须要在可能执行插入、更新和删除的且容许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你间接面向 JDBC 编程,你须要对能够为空值的列指定这个类型。
typeHandler 咱们在后面探讨过默认的类型处理器。应用这个属性,你能够笼罩默认的类型处理器。这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。

反对的 JDBC 类型

为了当前可能的应用场景,MyBatis 通过内置的 jdbcType 枚举类型反对上面的 JDBC 类型。

BIT FLOAT CHAR TIMESTAMP OTHER UNDEFINED
TINYINT REAL VARCHAR BINARY BLOB NVARCHAR
SMALLINT DOUBLE LONGVARCHAR VARBINARY CLOB NCHAR
INTEGER NUMERIC DATE LONGVARBINARY BOOLEAN NCLOB
BIGINT DECIMAL TIME NULL CURSOR ARRAY

构造方法

通过批改对象属性的形式,能够满足大多数的数据传输对象(Data Transfer Object, DTO)以及绝大部分畛域模型的要求。但有些状况下你想应用不可变类。一般来说,很少扭转或根本不变的蕴含援用或数据的表,很适宜应用不可变类。构造方法注入容许你在初始化时为类设置属性的值,而不必暴露出私有办法。MyBatis 也反对公有属性和公有 JavaBean 属性来实现注入,但有一些人更青眼于通过构造方法进行注入。constructor 元素就是为此而生的。

看看上面这个构造方法:

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

为了将后果注入构造方法,MyBatis 须要通过某种形式定位相应的构造方法。在上面的例子中,MyBatis 搜寻一个申明了三个形参的构造方法,参数类型以 java.lang.Integer, java.lang.Stringint 的程序给出。

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

当你在解决一个带有多个形参的构造方法时,很容易搞乱 arg 元素的程序。从版本 3.4.3 开始,能够在指定参数名称的前提下,以任意程序编写 arg 元素。为了通过名称来援用构造方法参数,你能够增加 @Param 注解,或者应用 ‘-parameters’ 编译选项并启用 useActualParamName 选项(默认开启)来编译我的项目。上面是一个等价的例子,只管函数签名中第二和第三个形参的程序与 constructor 元素中参数申明的程序不匹配。

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

如果存在名称和类型雷同的属性,那么能够省略 javaType

残余的属性和规定和一般的 id 和 result 元素是一样的。

属性 形容
column 数据库中的列名,或者是列的别名。个别状况下,这和传递给 resultSet.getString(columnName) 办法的参数一样。
javaType 一个 Java 类的齐全限定名,或一个类型别名(对于内置的类型别名,能够参考下面的表格)。如果你映射到一个 JavaBean,MyBatis 通常能够推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保障行为与冀望的相一致。
jdbcType JDBC 类型,所反对的 JDBC 类型参见这个表格之前的“反对的 JDBC 类型”。只须要在可能执行插入、更新和删除的且容许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你间接面向 JDBC 编程,你须要对可能存在空值的列指定这个类型。
typeHandler 咱们在后面探讨过默认的类型处理器。应用这个属性,你能够笼罩默认的类型处理器。这个属性值是一个类型处理器实现类的齐全限定名,或者是类型别名。
select 用于加载简单类型属性的映射语句的 ID,它会从 column 属性中指定的列检索数据,作为参数传递给此 select 语句。具体请参考关联元素。
resultMap 后果映射的 ID,能够将嵌套的后果集映射到一个适合的对象树中。它能够作为应用额定 select 语句的代替计划。它能够将多表连贯操作的后果映射成一个繁多的 ResultSet。这样的 ResultSet 将会将蕴含反复或局部数据反复的后果集。为了将后果集正确地映射到嵌套的对象树中,MyBatis 容许你“串联”后果映射,以便解决嵌套后果集的问题。想理解更多内容,请参考上面的关联元素。
name 构造方法形参的名字。从 3.4.3 版本开始,通过指定具体的参数名,你能够以任意程序写入 arg 元素。参看下面的解释。

关联

<association property="author" column="blog_author_id" javaType="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
</association>

关联(association)元素解决“有一个”类型的关系。比方,在咱们的示例中,一个博客有一个用户。关联后果映射和其它类型的映射工作形式差不多。你须要指定指标属性名以及属性的javaType(很多时候 MyBatis 能够本人推断进去),在必要的状况下你还能够设置 JDBC 类型,如果你想笼罩获取后果值的过程,还能够设置类型处理器。

关联的不同之处是,你须要通知 MyBatis 如何加载关联。MyBatis 有两种不同的形式加载关联:

  • 嵌套 Select 查问:通过执行另外一个 SQL 映射语句来加载冀望的简单类型。
  • 嵌套后果映射:应用嵌套的后果映射来解决连贯后果的反复子集。

首先,先让咱们来看看这个元素的属性。你将会发现,和一般的后果映射相比,它只在 select 和 resultMap 属性上有所不同。

属性 形容
property 映射到列后果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被应用。否则 MyBatis 将会寻找给定名称的字段。无论是哪一种情景,你都能够应用通常的点式分隔模式进行简单属性导航。比方,你能够这样映射一些简略的货色:“username”,或者映射到一些简单的货色上:“address.street.number”。
javaType 一个 Java 类的齐全限定名,或一个类型别名(对于内置的类型别名,能够参考下面的表格)。如果你映射到一个 JavaBean,MyBatis 通常能够推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保障行为与冀望的相一致。
jdbcType JDBC 类型,所反对的 JDBC 类型参见这个表格之前的“反对的 JDBC 类型”。只须要在可能执行插入、更新和删除的且容许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你间接面向 JDBC 编程,你须要对可能存在空值的列指定这个类型。
typeHandler 咱们在后面探讨过默认的类型处理器。应用这个属性,你能够笼罩默认的类型处理器。这个属性值是一个类型处理器实现类的齐全限定名,或者是类型别名。

关联的嵌套 Select 查问

属性 形容
column 数据库中的列名,或者是列的别名。个别状况下,这和传递给 resultSet.getString(columnName) 办法的参数一样。留神:在应用复合主键的时候,你能够应用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查问语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
select 用于加载简单类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给指标 select 语句。具体请参考上面的例子。留神:在应用复合主键的时候,你能够应用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查问语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
fetchType 可选的。有效值为 lazyeager。指定属性后,将在映射中疏忽全局配置参数 lazyLoadingEnabled,应用属性的值。

示例:

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

就是这么简略。咱们有两个 select 查问语句:一个用来加载博客(Blog),另外一个用来加载作者(Author),而且博客的后果映射形容了应该应用 selectAuthor 语句加载它的 author 属性。

其它所有的属性将会被主动加载,只有它们的列名和属性名相匹配。

这种形式尽管很简略,但在大型数据集或大型数据表上体现不佳。这个问题被称为“N+1 查问问题”。概括地讲,N+1 查问问题是这样子的:

  • 你执行了一个独自的 SQL 语句来获取后果的一个列表(就是“+1”)。
  • 对列表返回的每条记录,你执行一个 select 查问语句来为每条记录加载详细信息(就是“N”)。

这个问题会导致成千盈百的 SQL 语句被执行。有时候,咱们不心愿产生这样的结果。

好消息是,MyBatis 可能对这样的查问进行提早加载,因而能够将大量语句同时运行的开销分散开来。然而,如果你加载记录列表之后立即就遍历列表以获取嵌套的数据,就会触发所有的提早加载查问,性能可能会变得很蹩脚。

所以还有另外一种办法。

关联的嵌套后果映射

属性 形容
resultMap 后果映射的 ID,能够将此关联的嵌套后果集映射到一个适合的对象树中。它能够作为应用额定 select 语句的代替计划。它能够将多表连贯操作的后果映射成一个繁多的 ResultSet。这样的 ResultSet 有局部数据是反复的。为了将后果集正确地映射到嵌套的对象树中, MyBatis 容许你“串联”后果映射,以便解决嵌套后果集的问题。应用嵌套后果映射的一个例子在表格当前。
columnPrefix 当连贯多个表时,你可能会不得不应用列别名来防止在 ResultSet 中产生反复的列名。指定 columnPrefix 列名前缀容许你将带有这些前缀的列映射到一个内部的后果映射中。具体阐明请参考前面的例子。
notNullColumn 默认状况下,在至多一个被映射到属性的列不为空时,子对象才会被创立。你能够在这个属性上指定非空的列来扭转默认行为,指定后,Mybatis 将只在这些列非空时才创立一个子对象。能够应用逗号分隔来指定多个列。默认值:未设置(unset)。
autoMapping 如果设置这个属性,MyBatis 将会为本后果映射开启或者敞开主动映射。这个属性会笼罩全局的属性 autoMappingBehavior。留神,本属性对外部的后果映射有效,所以不能搭配 selectresultMap 元素应用。默认值:未设置(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>

在下面的例子中,你能够看到,博客(Blog)作者(author)的关联元素委托名为“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>

那如果博客(blog)有一个独特作者(co-author)该怎么办?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 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>

因为后果中的列名与后果映射中的列名不同。你须要指定 columnPrefix 以便重复使用该后果映射来映射 co-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>

关联的多后果集(ResultSet)

属性 形容
column 当应用多个后果集时,该属性指定后果集中用于与 foreignColumn 匹配的列(多个列名以逗号隔开),以辨认关系中的父类型与子类型。
foreignColumn 指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配。
resultSet 指定用于加载简单类型的后果集名字。

从版本 3.2.3 开始,MyBatis 提供了另一种解决 N+1 查问问题的办法。

某些数据库容许存储过程返回多个后果集,或一次性执行多个语句,每个语句返回一个后果集。咱们能够利用这个个性,在不应用连贯的状况下,只拜访数据库一次就能取得相干数据。

在例子中,存储过程执行上面的查问并返回两个后果集。第一个后果集会返回博客(Blog)的后果,第二个则返回作者(Author)的后果。

SELECT * FROM BLOG WHERE ID = #{id}

SELECT * FROM AUTHOR WHERE ID = #{id}

在映射语句中,必须通过 resultSets 属性为每个后果集指定一个名字,多个名字应用逗号隔开。

<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
  {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>

当初咱们能够指定应用“authors”后果集的数据来填充“author”关联:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="id" />
  <result property="title" column="title"/>
  <association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
    <result property="email" column="email"/>
    <result property="bio" column="bio"/>
  </association>
</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>

汇合元素和关联元素简直是一样的,它们类似的水平之高,以致于没有必要再介绍汇合元素的类似局部。所以让咱们来关注它们的不同之处吧。

咱们来持续下面的示例,一个博客(Blog)只有一个作者(Author)。但一个博客有很多文章(Post)。在博客类中,这能够用上面的写法来示意:

private List<Post> posts;

要像下面这样,映射嵌套后果汇合到一个 List 中,能够应用汇合元素。和关联元素一样,咱们能够应用嵌套 Select 查问,或基于连贯的嵌套后果映射汇合。

汇合的嵌套 Select 查问

首先,让咱们看看如何应用嵌套 Select 查问来为博客加载文章。

<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="Post">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

你可能会立即留神到几个不同,但大部分都和咱们下面学习过的关联元素十分类似。首先,你会留神到咱们应用的是汇合元素。接下来你会留神到有一个新的“ofType”属性。这个属性十分重要,它用来将 JavaBean(或字段)属性的类型和汇合存储的类型辨别开来。所以你能够依照上面这样来浏览映射:

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

读作:“posts 是一个存储 Post 的 ArrayList 汇合”

在个别状况下,MyBatis 能够推断 javaType 属性,因而并不需要填写。所以很多时候你能够简略成:

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

汇合的嵌套后果映射

当初你可能曾经猜到了汇合的嵌套后果映射是怎么工作的——除了新增的“ofType”属性,它和关联的完全相同。

首先, 让咱们看看对应的 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>

汇合的多后果集(ResultSet)

像关联元素那样,咱们能够通过执行存储过程实现,它会执行两个查问并返回两个后果集,一个是博客的后果集,另一个是文章的后果集:

SELECT * FROM BLOG WHERE ID = #{id}

SELECT * FROM POST WHERE BLOG_ID = #{id}

在映射语句中,必须通过 resultSets 属性为每个后果集指定一个名字,多个名字应用逗号隔开。

<select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult">
  {call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}
</select>

咱们指定“posts”汇合将会应用存储在“posts”后果集中的数据进行填充:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="id" />
  <result property="title" column="title"/>
  <collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="blog_id">
    <id property="id" column="id"/>
    <result property="subject" column="subject"/>
    <result property="body" column="body"/>
  </collection>
</resultMap>

留神 对关联或汇合的映射,并没有深度、广度或组合上的要求。但在映射时要注意性能问题。在摸索最佳实际的过程中,利用的单元测试和性能测试会是你的好帮手。而 MyBatis 的益处在于,能够在不对你的代码引入重大变更(如果有)的状况下,容许你之后扭转你的想法。

高级关联和汇合映射是一个深度话题。文档的介绍只能到此为止。配合少许的实际,你会很快理解全副的用法。

鉴别器

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

有时候,一个数据库查问可能会返回多个不同的后果集(但总体上还是有肯定的分割的)。鉴别器(discriminator)元素就是被设计来应答这种状况的,另外也能解决其它状况,例如类的继承层次结构。鉴别器的概念很好了解——它很像 Java 语言中的 switch 语句。

一个鉴别器的定义须要指定 column 和 javaType 属性。column 指定了 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 type 值。如果它匹配任意一个鉴别器的 case,就会应用这个 case 指定的后果映射。这个过程是互斥的,也就是说,残余的后果映射将被疏忽(除非它是扩大的,咱们将在稍后探讨它)。如果不能匹配任何一个 case,MyBatis 就只会应用鉴别器块外定义的后果映射。所以,如果 carResult 的申明如下:

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

那么只有 doorCount 属性会被加载。这是为了即便鉴别器的 case 之间都能分为齐全独立的一组,只管和父后果映射可能没有什么关系。在下面的例子中,咱们当然晓得 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>

提醒 请留神,这些都是后果映射,如果你齐全不设置任何的 result 元素,MyBatis 将为你主动匹配列和属性。所以下面的例子大多都要比理论的更简单。这也表明,大多数数据库的复杂度都比拟高,咱们不太可能始终依赖于这种机制。

主动映射

正如你在后面一节看到的,在简略的场景下,MyBatis 能够为你主动映射查问后果。但如果遇到简单的场景,你须要构建一个后果映射。然而在本节中,你将看到,你能够混合应用这两种策略。让咱们深刻理解一下主动映射是怎么工作的。

当主动映射查问后果时,MyBatis 会获取后果中返回的列名并在 Java 类中查找雷同名字的属性(疏忽大小写)。这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性。

通常数据库列应用大写字母组成的单词命名,单词间用下划线分隔;而 Java 属性个别遵循驼峰命名法约定。为了在这两种命名形式之间启用主动映射,须要将 mapUnderscoreToCamelCase 设置为 true。

甚至在提供了后果映射后,主动映射也能工作。在这种状况下,对于每一个后果映射,在 ResultSet 呈现的列,如果没有设置手动映射,将被主动映射。在主动映射处理完毕后,再解决手动映射。在上面的例子中,iduserName 列将被主动映射,hashed_password 列将依据配置进行映射。

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

有三种主动映射等级:

  • NONE – 禁用主动映射。仅对手动映射的属性进行映射。
  • PARTIAL – 对除在外部定义了嵌套后果映射(也就是连贯的属性)以外的属性进行映射
  • FULL – 主动映射所有属性。

默认值是 PARTIAL,这是有起因的。当对连贯查问的后果应用 FULL 时,连贯查问会在同一行中获取多个不同实体的数据,因而可能导致非预期的映射。上面的例子将展现这种危险:

<select id="selectBlog" resultMap="blogResult">
  select
    B.id,
    B.title,
    A.username,
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
  <association property="author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
  <result property="username" column="author_username"/>
</resultMap>

在该后果映射中,BlogAuthor 均将被主动映射。然而留神 Author 有一个 id 属性,在 ResultSet 中也有一个名为 id 的列,所以 Author 的 id 将填入 Blog 的 id,这可不是你冀望的行为。所以,要审慎应用 FULL

无论设置的主动映射等级是哪种,你都能够通过在后果映射上设置 autoMapping 属性来为指定的后果映射设置启用 / 禁用主动映射。

<resultMap id="userResultMap" type="User" autoMapping="false">
  <result property="password" column="hashed_password"/>
</resultMap>

缓存

MyBatis 内置了一个弱小的事务性查问缓存机制,它能够十分不便地配置和定制。为了使它更加弱小而且易于配置,咱们对 MyBatis 3 中的缓存实现进行了许多改良。

默认状况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。要启用全局的二级缓存,只须要在你的 SQL 映射文件中增加一行:

<cache/>

基本上就是这样。这个简略语句的成果如下:

  • 映射语句文件中的所有 select 语句的后果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会应用最近起码应用算法(LRU, Least Recently Used)算法来革除不须要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新距离)。
  • 缓存会保留列表或对象(无论查询方法返回哪种)的 1024 个援用。
  • 缓存会被视为读 / 写缓存,这意味着获取到的对象并不是共享的,能够平安地被调用者批改,而不烦扰其余调用者或线程所做的潜在批改。

提醒 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合应用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你须要应用 @CacheNamespaceRef 注解指定缓存作用域。

这些属性能够通过 cache 元素的属性来批改。比方:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这个更高级的配置创立了一个 FIFO 缓存,每隔 60 秒刷新,最多能够存储后果对象或列表的 512 个援用,而且返回的对象被认为是只读的,因而对它们进行批改可能会在不同线程中的调用者产生抵触。

可用的革除策略有:

  • LRU – 最近起码应用:移除最长工夫不被应用的对象。
  • FIFO – 先进先出:按对象进入缓存的程序来移除它们。
  • SOFT – 软援用:基于垃圾回收器状态和软援用规定移除对象。
  • WEAK – 弱援用:更踊跃地基于垃圾收集器状态和弱援用规定移除对象。

默认的革除策略是 LRU。

flushInterval(刷新距离)属性能够被设置为任意的正整数,设置的值应该是一个以毫秒为单位的正当工夫量。默认状况是不设置,也就是没有刷新距离,缓存仅仅会在调用语句时刷新。

size(援用数目)属性能够被设置为任意正整数,要留神欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性能够被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的雷同实例。因而这些对象不能被批改。这就提供了可观的性能晋升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。速度上会慢一些,然而更平安,因而默认值是 false。

提醒 二级缓存是事务性的。这意味着,当 SqlSession 实现并提交时,或是实现并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会取得更新。

应用自定义缓存

除了上述自定义缓存的形式,你也能够通过实现你本人的缓存,或为其余第三方缓存计划创立适配器,来齐全笼罩缓存行为。

<cache type="com.domain.something.MyCustomCache"/>

这个示例展现了如何应用一个自定义的缓存实现。type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个承受 String 参数作为 id 的结构器。这个接口是 MyBatis 框架中许多简单的接口之一,然而行为却非常简单。

public interface Cache {String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();}

为了对你的缓存进行配置,只须要简略地在你的缓存实现中增加私有的 JavaBean 属性,而后通过 cache 元素传递属性值,例如,上面的例子将在你的缓存实现上调用一个名为 setCacheFile(String file) 的办法:

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

你能够应用所有简略类型作为 JavaBean 属性的类型,MyBatis 会进行转换。你也能够应用占位符(如 ${cache.file}),以便替换成在配置文件属性中定义的值。

从版本 3.4.2 开始,MyBatis 曾经反对在所有属性设置结束之后,调用一个初始化办法。如果想要应用这个个性,请在你的自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject 接口。

public interface InitializingObject {void initialize() throws Exception;
}

提醒 上一节中对缓存的配置(如革除策略、可读或可读写等),不能利用于自定义缓存。

请留神,缓存的配置和缓存实例会被绑定到 SQL 映射文件的命名空间中。因而,同一命名空间中的所有语句和缓存将通过命名空间绑定在一起。每条语句能够自定义与缓存交互的形式,或将它们齐全排除于缓存之外,这能够通过在每条语句上应用两个简略属性来达成。默认状况下,语句会这样来配置:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

鉴于这是默认行为,显然你永远不应该以这样的形式显式配置一条语句。但如果你想扭转默认的行为,只须要设置 flushCache 和 useCache 属性。比方,某些状况下你可能心愿特定 select 语句的后果排除于缓存之外,或心愿一条 select 语句清空缓存。相似地,你可能心愿某些 update 语句执行时不要刷新缓存。

cache-ref

回忆一下上一节的内容,对某一命名空间的语句,只会应用该命名空间的缓存进行缓存或刷新。但你可能会想要在多个命名空间中共享雷同的缓存配置和实例。要实现这种需要,你能够应用 cache-ref 元素来援用另一个缓存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

动静 SQL

动静 SQL 是 MyBatis 的弱小个性之一。如果你应用过 JDBC 或其它相似的框架,你应该能了解依据不同条件拼接 SQL 语句有多苦楚,例如拼接时要确保不能遗记增加必要的空格,还要留神去掉列表最初一个列名的逗号。利用动静 SQL,能够彻底解脱这种苦楚。

应用动静 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的弱小的动静 SQL 语言,MyBatis 显著地晋升了这一个性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动静 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,须要花工夫理解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素品种,当初要学习的元素品种比原来的一半还要少。

  • 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”的参数值须要蕴含查找掩码或通配符字符)。

如果心愿通过“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”查找的情景。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 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”示例,这次咱们将“state =‘ACTIVE’”设置成动静条件,看看会产生什么。

<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
WHEREs

这会导致查问失败。如果匹配的只是第二个条件又会怎么?这条 SQL 会是这

SELECT * FROM BLOG
WHERE
AND title like‘someTitle’

这个查问也会失败。这个问题不能简略地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。

MyBatis 有一个简略且适宜大多数场景的解决办法。而在其余场景中,能够对其进行自定义以合乎需要。而这,只须要一处简略的改变:

<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 元素只会在子元素返回任何内容的状况下才插入“WHERE”子句。而且,若子句的结尾为“AND”或“OR”,where 元素也会将它们去除。

如果 where 元素与你冀望的不太一样,你也能够通过自定义 trim 元素来定制 where 元素的性能。比方,和 where 元素等价的自定义 trim 元素为:

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

prefixOverrides 属性会疏忽通过管道符分隔的文本序列(留神此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

用于动静更新语句的相似解决方案叫做 setset 元素能够用于动静蕴含须要更新的列,疏忽其它不更新的列。比方:

<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 关键字,并会删掉额定的逗号(这些逗号是在应用条件语句给列赋值时引入的)。

来看看与 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 元素的性能十分弱小,它容许你指定一个汇合,申明能够在元素体内应用的汇合项(item)和索引(index)变量。它也容许你指定结尾与结尾的字符串以及汇合项迭代之间的分隔符。这个元素也不会谬误地增加多余的分隔符,看它多智能!

提醒 你能够将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为汇合参数传递给 foreach。当应用可迭代对象或者数组时,index 是以后迭代的序号,item 的值是本次迭代获取到的元素。当应用 Map 对象(或者 Map.Entry 对象的汇合)时,index 是键,item 是值。

至此,咱们曾经实现了与 XML 配置及映射文件相干的探讨。下一章将具体探讨 Java API,以便你能充分利用曾经创立的映射配置。

script

要在带注解的映射器接口类中应用动静 SQL,能够应用 script 元素。比方:

    @Update({"<script>",
      "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}",
      "</script>"})
    void updateAuthorValues(Author author);

bind

bind 元素容许你在 OGNL 表达式以外创立一个变量,并将其绑定到以后的上下文。比方:

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

多数据库反对

如果配置了 databaseIdProvider,你就能够在动静代码中应用名为“_databaseId”的变量来为不同的数据库构建特定的语句。比方上面的例子:

<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 版本开始反对插入脚本语言,这容许你插入一种语言驱动,并基于这种语言来编写动静 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>

或者,在你的 mapper 接口上增加 @Lang 注解:

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

提醒 能够应用 Apache Velocity 作为动静语言,更多细节请参考 MyBatis-Velocity 我的项目。

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


Java API

既然你曾经晓得如何配置 MyBatis 以及如何创立映射,是时候来尝点苦头了。MyBatis 的 Java API 就是这个苦头。稍后你将看到,和 JDBC 相比,MyBatis 大幅简化你的代码并力求放弃其简洁、容易了解和保护。为了使得 SQL 映射更加优良,MyBatis 3 引入了许多重要的改良。

目录构造

在咱们深刻 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

当然,这是举荐的目录构造,并非强制要求,但应用一个通用的目录构造将更有利于大家沟通。

本章接下来的示例将假设你遵循这种目录构造。

SqlSession

应用 MyBatis 的次要 Java 接口就是 SqlSession。你能够通过这个接口来执行命令,获取映射器示例和治理事务。在介绍 SqlSession 接口之前,咱们先来理解如何获取一个 SqlSession 实例。SqlSessions 是由 SqlSessionFactory 实例创立的。SqlSessionFactory 对象蕴含创立 SqlSession 实例的各种办法。而 SqlSessionFactory 自身是由 SqlSessionFactoryBuilder 创立的,它能够从 XML、注解或 Java 配置代码来创立 SqlSessionFactory。

提醒 当 Mybatis 与一些依赖注入框架(如 Spring 或者 Guice)搭配应用时,SqlSession 将被依赖注入框架创立并注入,所以你不须要应用 SqlSessionFactoryBuilder 或者 SqlSessionFactory,能够间接浏览 SqlSession 这一节。请参考 Mybatis-Spring 或者 Mybatis-Guice 手册以理解更多信息。

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder 有五个 build() 办法,每一种都容许你从不同的资源中创立一个 SqlSessionFactory 实例。

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 文件)的 InputStream 实例。可选的参数是 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 将应用该环境对应的配置。当然,如果你指定了一个有效的环境,会收到谬误。如果你调用了不带 environment 参数的 build 办法,那么就会应用默认的环境配置(在下面的示例中,通过 default=”development” 指定了默认环境)。

如果你调用了承受 properties 实例的办法,那么 MyBatis 就会加载这些属性,并在配置中提供应用。绝大多数场合下,能够用 ${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.apache.ibatis.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);

当初你就取得一个能够用来创立 SqlSession 实例的 SqlSessionFactory 了。

SqlSessionFactory

SqlSessionFactory 有六个办法创立 SqlSession 实例。通常来说,当你抉择其中一个办法时,你须要思考以下几点:

  • 事务处理:你心愿在 session 作用域中应用事务作用域,还是应用主动提交(auto-commit)?(对很多数据库和 / 或 JDBC 驱动来说,等同于敞开事务反对)
  • 数据库连贯:你心愿 MyBatis 帮你从已配置的数据源获取连贯,还是应用本人提供的连贯?
  • 语句执行:你心愿 MyBatis 复用 PreparedStatement 和 / 或批量更新语句(包含插入语句和删除语句)吗?

基于以上需要,有下列已重载的多个 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:

  • 事务作用域将会开启(也就是不主动提交)。
  • 将由以后环境配置的 DataSource 实例中获取 Connection 对象。
  • 事务隔离级别将会应用驱动或数据源的默认设置。
  • 预处理语句不会被复用,也不会批量解决更新。

置信你曾经能从办法签名中晓得这些办法的区别。向 autoCommit 可选参数传递 true 值即可开启主动提交性能。若要应用本人的 Connection 实例,传递一个 Connection 实例给 connection 参数即可。留神,咱们没有提供同时设置 ConnectionautoCommit 的办法,这是因为 MyBatis 会根据传入的 Connection 来决定是否启用 autoCommit。对于事务隔离级别,MyBatis 应用了一个 Java 枚举包装器来示意,称为 TransactionIsolationLevel,事务隔离级别反对 JDBC 的五个隔离级别(NONEREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE),并且与预期的行为统一。

你可能对 ExecutorType 参数感到生疏。这个枚举类型定义了三个值:

  • ExecutorType.SIMPLE:该类型的执行器没有特地的行为。它为每个语句的执行创立一个新的预处理语句。
  • ExecutorType.REUSE:该类型的执行器会复用预处理语句。
  • ExecutorType.BATCH:该类型的执行器会批量执行所有更新语句,如果 SELECT 在多个更新两头执行,将在必要时将多条更新语句分隔开来,以不便了解。

提醒 在 SqlSessionFactory 中还有一个办法咱们没有提及,就是 getConfiguration()。这个办法会返回一个 Configuration 实例,你能够在运行时应用它来查看 MyBatis 的配置。

提醒 如果你应用过 MyBatis 的旧版本,可能还记得 session、事务和批量操作是互相独立的。在新版本中则不是这样。上述三者都蕴含在 session 作用域内。你不用别离处理事务或批量操作就能失去想要的全副成果。

SqlSession

正如之前所提到的,SqlSession 在 MyBatis 中是十分弱小的一个类。它蕴含了所有执行语句、提交或回滚事务以及获取映射器实例的办法。

SqlSession 类的办法超过了 20 个,为了不便了解,咱们将它们分成几种组别。

语句执行办法

这些办法被用来执行定义在 SQL 映射 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 语句。你能够通过名字疾速理解它们的作用,每一办法都承受语句的 ID 以及参数对象,参数能够是原始类型(反对主动装箱或包装类)、JavaBean、POJO 或 Map。

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<T> Cursor<T> selectCursor(String statement, Object parameter)
<K,V> Map<K,V> 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。如果须要查看某个对象是否存在,最好的方法是查问一个 count 值(0 或 1)。selectMap 略微非凡一点,它会将返回对象的其中一个属性作为 key 值,将对象作为 value 值,从而将多个后果集转为 Map 类型值。因为并不是所有语句都须要参数,所以这些办法都具备一个不须要参数的重载模式。

游标(Cursor)与列表(List)返回的后果雷同,不同的是,游标借助迭代器实现了数据的惰性加载。

try (Cursor<MyEntity> entities = session.selectCursor(statement, param)) {for (MyEntity entity:entities) {// 解决单个实体}
}

insert、update 以及 delete 办法返回的值示意受该语句影响的行数。

<T> T selectOne(String statement)
<E> List<E> selectList(String statement)
<T> Cursor<T> selectCursor(String statement)
<K,V> Map<K,V> selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)

最初,还有 select 办法的三个高级版本,它们容许你限度返回行数的范畴,或是提供自定义后果解决逻辑,通常在数据集十分宏大的情景下应用。

<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<T> Cursor<T> selectCursor(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);

数据库驱动决定了略过记录时的查问效率。为了获得最佳的性能,倡议将 ResultSet 类型设置为 SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE(换句话说:不要应用 FORWARD_ONLY)。

ResultHandler 参数容许自定义每行后果的处理过程。你能够将它增加到 List 中、创立 Map 和 Set,甚至抛弃每个返回值,只保留计算后的统计后果。你能够应用 ResultHandler 做很多事,这其实就是 MyBatis 构建 后果列表的外部实现方法。

从版本 3.4.6 开始,ResultHandler 会在存储过程的 REFCURSOR 输入参数中传递应用的 CALLABLE 语句。

它的接口很简略:

package org.apache.ibatis.session;
public interface ResultHandler<T> {void handleResult(ResultContext<? extends T> context);
}

ResultContext 参数容许你拜访后果对象和以后已被创立的对象数目,另外还提供了一个返回值为 Boolean 的 stop 办法,你能够应用此 stop 办法来进行 MyBatis 加载更多的后果。

应用 ResultHandler 的时候须要留神以下两个限度:

  • 应用带 ResultHandler 参数的办法时,收到的数据不会被缓存。
  • 当应用高级的后果映射集(resultMap)时,MyBatis 很可能须要数行后果来结构一个对象。如果你应用了 ResultHandler,你可能会接管到关联(association)或者汇合(collection)中尚未被残缺填充的对象。
立刻批量更新办法

当你将 ExecutorType 设置为 ExecutorType.BATCH 时,能够应用这个办法革除(执行)缓存在 JDBC 驱动类中的批量更新语句。

List<BatchResult> flushStatements()
事务管制办法

有四个办法用来管制事务作用域。当然,如果你曾经设置了主动提交或你应用了内部事务管理器,这些办法就没什么作用了。然而,如果你正在应用由 Connection 实例管制的 JDBC 事务管理器,那么这四个办法就会派上用场:

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

默认状况下 MyBatis 不会主动提交事务,除非它侦测到调用了插入、更新或删除办法扭转了数据库。如果你没有应用这些办法提交批改,那么你能够在 commit 和 rollback 办法参数中传入 true 值,来保障事务被失常提交(留神,在主动提交模式或者应用了内部事务管理器的状况下,设置 force 值对 session 有效)。大部分状况下你无需调用 rollback(),因为 MyBatis 会在你没有调用 commit 时替你实现回滚操作。不过,当你要在一个可能屡次提交或回滚的 session 中具体管制事务,回滚操作就派上用场了。

提醒 MyBatis-Spring 和 MyBatis-Guice 提供了申明式事务处理,所以如果你在应用 Mybatis 的同时应用了 Spring 或者 Guice,请参考它们的手册以获取更多的内容。

本地缓存

Mybatis 应用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache)。

每当一个新 session 被创立,MyBatis 就会创立一个与之相关联的本地缓存。任何在 session 执行过的查问后果都会被保留在本地缓存中,所以,当再次执行参数雷同的雷同查问时,就不须要理论查询数据库了。本地缓存将会在做出批改、事务提交或回滚,以及敞开 session 时清空。

默认状况下,本地缓存数据的生命周期等同于整个 session 的周期。因为缓存会被用来解决循环援用问题和放慢反复嵌套查问的速度,所以无奈将其齐全禁用。然而你能够通过设置 localCacheScope=STATEMENT 来只在语句执行时应用缓存。

留神,如果 localCacheScope 被设置为 SESSION,对于某个对象,MyBatis 将返回在本地缓存中惟一对象的援用。对返回的对象(例如 list)做出的任何批改将会影响本地缓存的内容,进而将会影响到在本次 session 中从缓存返回的值。因而,不要对 MyBatis 所返回的对象作出更改,以防后患。

你能够随时调用以下办法来清空本地缓存:

void clearCache()
确保 SqlSession 被敞开
void close()

对于你关上的任何 session,你都要保障它们被妥善敞开,这很重要。保障妥善敞开的最佳代码模式是这样的:

SqlSession session = sqlSessionFactory.openSession();
try (SqlSession session = sqlSessionFactory.openSession()) {
    // 假如上面三行代码是你的业务逻辑
    session.insert(...);
    session.update(...);
    session.delete(...);
    session.commit();}

提醒 和 SqlSessionFactory 一样,你能够调用以后应用的 SqlSession 的 getConfiguration 办法来取得 Configuration 实例。

Configuration getConfiguration()
应用映射器
<T> T getMapper(Class<T> type)

上述的各个 insert、update、delete 和 select 办法都很弱小,但也有些繁琐,它们并不合乎类型平安,对你的 IDE 和单元测试也不是那么敌对。因而,应用映射器类来执行映射语句是更常见的做法。

咱们曾经在之前的入门章节中见到过一个应用映射器的示例。一个映射器类就是一个仅需申明与 SqlSession 办法相匹配办法的接口。上面的示例展现了一些办法签名以及它们是如何映射到 SqlSession 上的。

public interface AuthorMapper {// (Author) selectOne("selectAuthor",5);
  Author selectAuthor(int id);
  // (List<Author>) selectList(“selectAuthors”)
  List<Author> selectAuthors();
  // (Map<Integer,Author>) selectMap("selectAuthors", "id")
  @MapKey("id")
  Map<Integer, Author> 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。

此外,返回类型必须匹配冀望的后果类型,返回单个值时,返回类型应该是返回值的类,返回多个值时,则为数组或汇合类,另外也能够是游标(Cursor)。所有罕用的类型都是反对的,包含:原始类型、Map、POJO 和 JavaBean。

提醒 映射器接口不须要去实现任何接口或继承自任何类。只有办法签名能够被用来惟一辨认对应的映射语句就能够了。

提醒 映射器接口能够继承自其余接口。在应用 XML 来绑定映射器接口时,保障语句处于适合的命名空间中即可。惟一的限度是,不能在两个具备继承关系的接口中领有雷同的办法签名(这是潜在的危险做法,不可取)。

你能够传递多个参数给一个映射器办法。在多个参数的状况下,默认它们将会以 param 加上它们在参数列表中的地位来命名,比方:#{param1}、#{param2}等。如果你想(在有多个参数时)自定义参数的名称,那么你能够在参数上应用 @Param(“paramName”) 注解。

你也能够给办法传递一个 RowBounds 实例来限度查问后果。

映射器注解

设计初期的 MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,映射语句也是定义在 XML 中的。而在 MyBatis 3 中,咱们提供了其它的配置形式。MyBatis 3 构建在全面且弱小的基于 Java 语言的配置 API 之上。它是 XML 和注解配置的根底。注解提供了一种简略且低成本的形式来实现简略的映射语句。

提醒 可怜的是,Java 注解的表达能力和灵活性非常无限。只管咱们花了很多工夫在考察、设计和试验上,但最弱小的 MyBatis 映射并不能用注解来构建——咱们真没开玩笑。而 C# 属性就没有这些限度,因而 MyBatis.NET 的配置会比 XML 有更大的抉择余地。虽说如此,基于 Java 注解的配置还是有它的益处的。

注解如下表所示:

注解 应用对象 XML 等价模式 形容
@CacheNamespace 为给定的命名空间(比方类)配置缓存。属性:implemetationevictionflushIntervalsizereadWriteblockingproperties
@Property N/A 指定参数值或占位符(placeholder)(该占位符能被 mybatis-config.xml 内的配置属性替换)。属性:namevalue。(仅在 MyBatis 3.4.2 以上可用)
@CacheNamespaceRef 援用另外一个命名空间的缓存以供应用。留神,即便共享雷同的全限定类名,在 XML 映射文件中申明的缓存仍被辨认为一个独立的命名空间。属性:valuename。如果你应用了这个注解,你应设置 value 或者 name 属性的其中一个。value 属性用于指定可能示意该命名空间的 Java 类型(命名空间名就是该 Java 类型的全限定类名),name 属性(这个属性仅在 MyBatis 3.4.2 以上可用)则间接指定了命名空间的名字。
@ConstructorArgs 办法 undefined“undefined 收集一组后果以传递给一个后果对象的构造方法。属性:value,它是一个 Arg 数组。
@Arg N/A `` ConstructorArgs 汇合的一部分,代表一个构造方法参数。属性:idcolumnjavaTypejdbcTypetypeHandlerselectresultMap。id 属性和 XML 元素 “ 类似,它是一个布尔值,示意该属性是否用于惟一标识和比拟对象。从版本 3.5.4 开始,该注解变为可反复注解。
@TypeDiscriminator 办法 决定应用何种后果映射的一组取值(case)。属性:columnjavaTypejdbcTypetypeHandlercases。cases 属性是一个 Case 的数组。
@Case N/A 示意某个值的一个取值以及该取值对应的映射。属性:valuetyperesults。results 属性是一个 Results 的数组,因而这个注解实际上和 ResultMap 很类似,由上面的 Results 注解指定。
@Results 办法 一组后果映射,指定了对某个特定后果列,映射到某个属性或字段的形式。属性:valueid。value 属性是一个 Result 注解的数组。而 id 属性则是后果映射的名称。从版本 3.5.4 开始,该注解变为可反复注解。
@Result N/A `` 在列和属性或字段之间的单个后果映射。属性:idcolumnjavaTypejdbcTypetypeHandleronemany。id 属性和 XML 元素 类似,它是一个布尔值,示意该属性是否用于惟一标识和比拟对象。one 属性是一个关联,和 相似,而 many 属性则是汇合关联,和 “ 相似。这样命名是为了防止产生名称抵触。
@One N/A 简单类型的单个属性映射。属性:select,指定可加载适合类型实例的映射语句(也就是映射器办法)全限定名;fetchType,指定在该映射中笼罩全局配置参数 lazyLoadingEnabledresultMap(available since 3.5.5), which is the fully qualified name of a result map that map to a single container object from select result;columnPrefix(available since 3.5.5), which is column prefix for grouping select columns at nested result map. 提醒 注解 API 不反对联结映射。这是因为 Java 注解不容许产生循环援用。
@Many N/A 简单类型的汇合属性映射。属性:select,指定可加载适合类型实例汇合的映射语句(也就是映射器办法)全限定名;fetchType,指定在该映射中笼罩全局配置参数 lazyLoadingEnabled resultMap(available since 3.5.5), which is the fully qualified name of a result map that map to collection object from select result;columnPrefix(available since 3.5.5), which is column prefix for grouping select columns at nested result map. 提醒 注解 API 不反对联结映射。这是因为 Java 注解不容许产生循环援用。
@MapKey 办法 供返回值为 Map 的办法应用的注解。它应用对象的某个属性作为 key,将对象 List 转化为 Map。属性:value,指定作为 Map 的 key 值的对象属性名。
@Options 办法 映射语句的属性 该注解容许你指定大部分开关和配置选项,它们通常在映射语句上作为属性呈现。与在注解上提供大量的属性相比,Options 注解提供了统一、清晰的形式来指定选项。属性:useCache=trueflushCache=FlushCachePolicy.DEFAULTresultSetType=DEFAULTstatementType=PREPAREDfetchSize=-1timeout=-1useGeneratedKeys=falsekeyProperty=""keyColumn=""resultSets="", databaseId=""。留神,Java 注解无奈指定 null 值。因而,一旦你应用了 Options 注解,你的语句就会被上述属性的默认值所影响。要留神防止默认值带来的非预期行为。The databaseId(Available since 3.5.5), in case there is a configured DatabaseIdProvider, the MyBatis use the Options with no databaseId attribute or with a databaseId that matches the current one. If found with and without the databaseId the latter will be discarded. 留神:keyColumn 属性只在某些数据库中无效(如 Oracle、PostgreSQL 等)。要理解更多对于 keyColumnkeyProperty 可选值信息,请查看“insert, update 和 delete”一节。
@Insert`@Update@Delete@Select` 办法 `` 每个注解别离代表将会被执行的 SQL 语句。它们用字符串数组(或单个字符串)作为参数。如果传递的是字符串数组,字符串数组会被连接成单个残缺的字符串,每个字符串之间退出一个空格。这无效地防止了用 Java 代码构建 SQL 语句时产生的“失落空格”问题。当然,你也能够提前手动连贯好字符串。属性:value,指定用来组成单个 SQL 语句的字符串数组。The databaseId(Available since 3.5.5), in case there is a configured DatabaseIdProvider, the MyBatis use a statement with no databaseId attribute or with a databaseId that matches the current one. If found with and without the databaseId the latter will be discarded.
@InsertProvider`@UpdateProvider@DeleteProvider@SelectProvider` 办法 `` 容许构建动静 SQL。这些备选的 SQL 注解容许你指定返回 SQL 语句的类和办法,以供运行时执行。(从 MyBatis 3.4.6 开始,能够应用 CharSequence 代替 String 来作为返回类型)。当执行映射语句时,MyBatis 会实例化注解指定的类,并调用注解指定的办法。你能够通过 ProviderContext 传递映射办法接管到的参数、”Mapper interface type” 和 “Mapper method”(仅在 MyBatis 3.4.5 以上反对)作为参数。(MyBatis 3.4 以上反对传入多个参数)属性:valuetypemethoddatabaseIdvalue and type 属性用于指定类名 (The type attribute is alias for value, you must be specify either one. But both attributes can be omit when specify the defaultSqlProviderType as global configuration)。method 用于指定该类的办法名(从版本 3.5.1 开始,能够省略 method 属性,MyBatis 将会应用 ProviderMethodResolver 接口解析办法的具体实现。如果解析失败,MyBatis 将会应用名为 provideSql 的降级实现)。提醒 接下来的“SQL 语句构建器”一章将会探讨该话题,以帮忙你以更清晰、更便于浏览的形式构建动静 SQL。The databaseId(Available since 3.5.5), in case there is a configured DatabaseIdProvider, the MyBatis will use a provider method with no databaseId attribute or with a databaseId that matches the current one. If found with and without the databaseId the latter will be discarded.
@Param 参数 N/A 如果你的映射办法承受多个参数,就能够应用这个注解自定义每个参数的名字。否则在默认状况下,除 RowBounds 以外的参数会以 “param” 加参数地位被命名。例如 #{param1}, #{param2}。如果应用了 @Param("person"),参数就会被命名为 #{person}
@SelectKey 办法 这个注解的性能与 ` 标签完全一致。该注解只能在 @Insert@InsertProvider@Update@UpdateProvider 标注的办法上应用,否则将会被疏忽。如果标注了 @SelectKey 注解,MyBatis 将会疏忽掉由 @Options 注解所设置的生成主键或设置(configuration)属性。属性:statement 以字符串数组模式指定将会被执行的 SQL 语句,keyProperty 指定作为参数传入的对象对应属性的名称,该属性将会更新成新的值,before 能够指定为 truefalse 以指明 SQL 语句应被在插入语句的之前还是之后执行。resultType 则指定 keyProperty 的 Java 类型。statementType 则用于抉择语句类型,能够抉择 STATEMENTPREPAREDCALLABLE 之一,它们别离对应于 StatementPreparedStatementCallableStatement。默认值是 PREPARED。The databaseId(Available since 3.5.5), in case there is a configured DatabaseIdProvider, the MyBatis will use a statement with no databaseId attribute or with a databaseId that matches the current one. If found with and without the databaseId` the latter will be discarded.
@ResultMap 办法 N/A 这个注解为 @Select 或者 @SelectProvider 注解指定 XML 映射中 ` 元素的 id。这使得注解的 select 能够复用已在 XML 中定义的 ResultMap。如果标注的 select 注解中存在 @Results 或者 @ConstructorArgs` 注解,这两个注解将被此注解笼罩。
@ResultType 办法 N/A 在应用了后果处理器的状况下,须要应用此注解。因为此时的返回类型为 void,所以 Mybatis 须要有一种办法来判断每一行返回的对象类型。如果在 XML 有对应的后果映射,请应用 @ResultMap 注解。如果后果类型在 XML 的 “ 元素中指定了,就不须要应用其它注解了。否则就须要应用此注解。比方,如果一个标注了 @Select 的办法想要应用后果处理器,那么它的返回类型必须是 void,并且必须应用这个注解(或者 @ResultMap)。这个注解仅在办法返回类型是 void 的状况下失效。
@Flush 办法 N/A 如果应用了这个注解,定义在 Mapper 接口中的办法就可能调用 SqlSession#flushStatements() 办法。(Mybatis 3.3 以上可用)
映射注解示例

这个例子展现了如何应用 @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);

这个例子展现了如何应用 @Flush 注解来调用 SqlSession#flushStatements()

@Flush
List<BatchResult> flush();

这些例子展现了如何通过指定 @Result 的 id 属性来命名后果集:

@Results(id = "userResult", value = {@Result(property = "id", column = "uid", id = true),
  @Result(property = "firstName", column = "first_name"),
  @Result(property = "lastName", column = "last_name")
})
@Select("select * from users where id = #{id}")
User getUserById(Integer id);

@Results(id = "companyResults")
@ConstructorArgs({@Arg(column = "cid", javaType = Integer.class, id = true),
  @Arg(column = "name", javaType = String.class)
})
@Select("select * from company where id = #{id}")
Company getCompanyById(Integer id);

这个例子展现了如何应用单个参数的 @SqlProvider 注解:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(String name);

class UserSqlBuilder {public static String buildGetUsersByName(final String name) {return new SQL(){{SELECT("*");
      FROM("users");
      if (name != null) {WHERE("name like #{value} ||'%'");
      }
      ORDER_BY("id");
    }}.toString();}
}

这个例子展现了如何应用多个参数的 @SqlProvider 注解:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(@Param("name") String name, @Param("orderByColumn") String orderByColumn);

class UserSqlBuilder {

  // 如果不应用 @Param,就应该定义与 mapper 办法雷同的参数
  public static String buildGetUsersByName(final String name, final String orderByColumn) {return new SQL(){{SELECT("*");
      FROM("users");
      WHERE("name like #{name} ||'%'");
      ORDER_BY(orderByColumn);
    }}.toString();}

  // 如果应用 @Param,就能够只定义须要应用的参数
  public static String buildGetUsersByName(@Param("orderByColumn") final String orderByColumn) {return new SQL(){{SELECT("*");
      FROM("users");
      WHERE("name like #{name} ||'%'");
      ORDER_BY(orderByColumn);
    }}.toString();}
}

This example shows usage that share an sql provider class to all mapper methods using global configuration(Available since 3.5.6):

Configuration configuration = new Configuration();
configuration.setDefaultSqlProviderType(TemplateFilePathProvider.class); // Specify an sql provider class for sharing on all mapper methods
// ...
// Can omit the type/value attribute on sql provider annotation
// If omit it, the MyBatis apply the class that specified on defaultSqlProviderType.
public interface UserMapper {@SelectProvider // Same with @SelectProvider(TemplateFilePathProvider.class)
  User findUser(int id);

  @InsertProvider // Same with @InsertProvider(TemplateFilePathProvider.class)
  void createUser(User user);

  @UpdateProvider // Same with @UpdateProvider(TemplateFilePathProvider.class)
  void updateUser(User user);

  @DeleteProvider // Same with @DeleteProvider(TemplateFilePathProvider.class)
  void deleteUser(int id);
}

以下例子展现了 ProviderMethodResolver(3.5.1 后可用)的默认实现应用办法:

@SelectProvider(UserSqlProvider.class)
List<User> getUsersByName(String name);

// 在你的 provider 类中实现 ProviderMethodResolver 接口
class UserSqlProvider implements ProviderMethodResolver {
  // 默认实现中,会将映射器办法的调用解析到实现的同名办法上
  public static String getUsersByName(final String name) {return new SQL(){{SELECT("*");
      FROM("users");
      if (name != null) {WHERE("name like #{value} ||'%'");
      }
      ORDER_BY("id");
    }}.toString();}
}

This example shows usage the databaseId attribute on the statement annotation(Available since 3.5.5):

@Select(value = "SELECT SYS_GUID() FROM dual", databaseId = "oracle") // Use this statement if DatabaseIdProvider provide "oracle"
@Select(value = "SELECT uuid_generate_v4()", databaseId = "postgres") // Use this statement if DatabaseIdProvider provide "postgres"
@Select("SELECT RANDOM_UUID()") // Use this statement if the DatabaseIdProvider not configured or not matches databaseId
String generateId();

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";

解决方案

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 类

这里有一些示例:

// 匿名外部类格调
public String deletePersonSql() {return new SQL() {{DELETE_FROM("PERSON");
    WHERE("ID = #{id}");
  }}.toString();}

// Builder / Fluent 格调
public String insertPersonSql() {String sql = new SQL()
    .INSERT_INTO("PERSON")
    .VALUES("ID, FIRST_NAME", "#{id}, #{firstName}")
    .VALUES("LAST_NAME", "#{lastName}")
    .toString();
  return sql;
}

// 动静条件(留神参数须要应用 final 润饰,以便匿名外部类对它们进行拜访)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(String…)` 开始新的或追加到已有的 SELECT子句。能够被屡次调用,参数会被追加到 SELECT 子句。参数通常应用逗号分隔的列名和别名列表,但也能够是数据库驱动程序承受的任意参数。
SELECT_DISTINCT(String)`SELECT_DISTINCT(String…)` 开始新的或追加到已有的 SELECT子句,并增加 DISTINCT 关键字到生成的查问中。能够被屡次调用,参数会被追加到 SELECT 子句。参数通常应用逗号分隔的列名和别名列表,但也能够是数据库驱动程序承受的任意参数。
FROM(String)`FROM(String…)` 开始新的或追加到已有的 FROM子句。能够被屡次调用,参数会被追加到 FROM子句。参数通常是一个表名或别名,也能够是数据库驱动程序承受的任意参数。
JOIN(String)`JOIN(String…)INNER_JOIN(String)INNER_JOIN(String…)LEFT_OUTER_JOIN(String)LEFT_OUTER_JOIN(String…)RIGHT_OUTER_JOIN(String)RIGHT_OUTER_JOIN(String…)` 基于调用的办法,增加新的适合类型的 JOIN 子句。参数能够蕴含一个由列和连贯条件形成的规范连贯。
WHERE(String)`WHERE(String…)` 插入新的 WHERE 子句条件,并应用 AND 拼接。能够被屡次调用,对于每一次调用产生的新条件,会应用 AND 拼接起来。要应用 OR 分隔,请应用 OR()
OR() 应用 OR 来分隔以后的 WHERE 子句条件。能够被屡次调用,但在一行中屡次调用会生成谬误的 SQL
AND() 应用 AND 来分隔以后的 WHERE子句条件。能够被屡次调用,但在一行中屡次调用会生成谬误的 SQL。因为 WHEREHAVING都会主动应用 AND 拼接, 因而这个办法并不罕用,只是为了完整性才被定义进去。
GROUP_BY(String)`GROUP_BY(String…)` 追加新的 GROUP BY 子句,应用逗号拼接。能够被屡次调用,每次调用都会应用逗号将新的条件拼接起来。
HAVING(String)`HAVING(String…)` 追加新的 HAVING 子句。应用 AND 拼接。能够被屡次调用,每次调用都应用 AND 来拼接新的条件。要应用 OR 分隔,请应用 OR()
ORDER_BY(String)`ORDER_BY(String…)` 追加新的 ORDER BY 子句,应用逗号拼接。能够屡次被调用,每次调用会应用逗号拼接新的条件。
LIMIT(String)`LIMIT(int)` 追加新的 LIMIT 子句。仅在 SELECT()、UPDATE()、DELETE() 时无效。当在 SELECT() 中应用时,应该配合 OFFSET() 应用。(于 3.5.2 引入)
OFFSET(String)`OFFSET(long)` 追加新的 OFFSET 子句。仅在 SELECT() 时无效。当在 SELECT() 时应用时,应该配合 LIMIT() 应用。(于 3.5.2 引入)
OFFSET_ROWS(String)`OFFSET_ROWS(long)` 追加新的 OFFSET n ROWS 子句。仅在 SELECT() 时无效。该办法应该配合 FETCH_FIRST_ROWS_ONLY() 应用。(于 3.5.2 退出)
FETCH_FIRST_ROWS_ONLY(String)`FETCH_FIRST_ROWS_ONLY(int)` 追加新的 FETCH FIRST n ROWS ONLY 子句。仅在 SELECT() 时无效。该办法应该配合 OFFSET_ROWS() 应用。(于 3.5.2 退出)
DELETE_FROM(String) 开始新的 delete 语句,并指定删除表的表名。通常它前面都会跟着一个 WHERE 子句!
INSERT_INTO(String) 开始新的 insert 语句,并指定插入数据表的表名。前面应该会跟着一个或多个 VALUES() 调用,或 INTO_COLUMNS() 和 INTO_VALUES() 调用。
SET(String)`SET(String…)` 对 update 语句追加 “set” 属性的列表
UPDATE(String) 开始新的 update 语句,并指定更新表的表名。前面都会跟着一个或多个 SET() 调用,通常也会有一个 WHERE() 调用。
VALUES(String, String) 追加数据值到 insert 语句中。第一个参数是数据插入的列名,第二个参数则是数据值。
INTO_COLUMNS(String...) 追加插入列子句到 insert 语句中。应与 INTO_VALUES() 一起应用。
INTO_VALUES(String...) 追加插入值子句到 insert 语句中。应与 INTO_COLUMNS() 一起应用。
ADD_ROW() 增加新的一行数据,以便执行批量插入。(于 3.5.2 引入)

提醒 留神,SQL 类将原样插入 LIMITOFFSETOFFSET n ROWS 以及 FETCH FIRST n ROWS ONLY 子句。换句话说,类库不会为不反对这些子句的数据库执行任何转换。因而,用户应该要理解指标数据库是否反对这些子句。如果指标数据库不反对这些子句,产生的 SQL 可能会引起运行谬误。

从版本 3.4.2 开始,你能够像上面这样应用可变长度参数:

public String selectPersonSql() {return new SQL()
    .SELECT("P.ID", "A.USERNAME", "A.PASSWORD", "P.FULL_NAME", "D.DEPARTMENT_NAME", "C.COMPANY_NAME")
    .FROM("PERSON P", "ACCOUNT A")
    .INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID", "COMPANY C on D.COMPANY_ID = C.ID")
    .WHERE("P.ID = A.ID", "P.FULL_NAME like #{name}")
    .ORDER_BY("P.ID", "P.FULL_NAME")
    .toString();}

public String insertPersonSql() {return new SQL()
    .INSERT_INTO("PERSON")
    .INTO_COLUMNS("ID", "FULL_NAME")
    .INTO_VALUES("#{id}", "#{fullName}")
    .toString();}

public String updatePersonSql() {return new SQL()
    .UPDATE("PERSON")
    .SET("FULL_NAME = #{fullName}", "DATE_OF_BIRTH = #{dateOfBirth}")
    .WHERE("ID = #{id}")
    .toString();}

从版本 3.5.2 开始,你能够像上面这样构建批量插入语句:

public String insertPersonsSql() {// INSERT INTO PERSON (ID, FULL_NAME)
  //     VALUES (#{mainPerson.id}, #{mainPerson.fullName}) , (#{subPerson.id}, #{subPerson.fullName})
  return new SQL()
    .INSERT_INTO("PERSON")
    .INTO_COLUMNS("ID", "FULL_NAME")
    .INTO_VALUES("#{mainPerson.id}", "#{mainPerson.fullName}")
    .ADD_ROW()
    .INTO_VALUES("#{subPerson.id}", "#{subPerson.fullName}")
    .toString();}

从版本 3.5.2 开始,你能够像上面这样构建限度返回后果数的 SELECT 语句,:

public String selectPersonsWithOffsetLimitSql() {
  // SELECT id, name FROM PERSON
  //     LIMIT #{limit} OFFSET #{offset}
  return new SQL()
    .SELECT("id", "name")
    .FROM("PERSON")
    .LIMIT("#{limit}")
    .OFFSET("#{offset}")
    .toString();}

public String selectPersonsWithFetchFirstSql() {
  // SELECT id, name FROM PERSON
  //     OFFSET #{offset} ROWS FETCH FIRST #{limit} ROWS ONLY
  return new SQL()
    .SELECT("id", "name")
    .FROM("PERSON")
    .OFFSET_ROWS("#{offset}")
    .FETCH_FIRST_ROWS_ONLY("#{limit}")
    .toString();}

SqlBuilder 和 SelectBuilder (曾经废除)

在版本 3.2 之前,咱们的实现形式不太一样,咱们利用 ThreadLocal 变量来覆盖一些对 Java DSL 不太敌对的语言限度。当初,古代 SQL 构建框架应用的构建器和匿名外部类思维已被人们所熟知。因而,咱们废除了基于这种实现形式的 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.*;

而后就能够像上面这样创立一些办法:

/* 已被废除 */
public String selectBlogsSql() {BEGIN(); // 重置 ThreadLocal 状态变量
  SELECT("*");
  FROM("BLOG");
  return SQL();}
        
/* 已被废除 */
private String selectPersonSql() {BEGIN(); // 重置 ThreadLocal 状态变量
  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 通过应用内置的日志工厂提供日志性能。内置日志工厂将会把日志工作委托给上面的实现之一:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

MyBatis 内置日志工厂会基于运行时检测信息抉择日志委托实现。它会(按下面列举的程序)应用第一个查找到的实现。当没有找到这些实现时,将会禁用日志性能。

不少应用服务器(如 Tomcat 和 WebShpere)的类门路中曾经蕴含 Commons Logging。留神,在这种配置环境下,MyBatis 会把 Commons Logging 作为日志工具。这就意味着在诸如 WebSphere 的环境中,因为提供了 Commons Logging 的公有实现,你的 Log4J 配置将被疏忽。这个时候你就会感觉很郁闷:看起来 MyBatis 将你的 Log4J 配置疏忽掉了(其实是因为在这种配置环境下,MyBatis 应用了 Commons Logging 作为日志实现)。如果你的利用部署在一个类门路曾经蕴含 Commons Logging 的环境中,而你又想应用其它日志实现,你能够通过在 MyBatis 配置文件 mybatis-config.xml 外面增加一项 setting 来抉择其它日志实现。

<configuration>
  <settings>
    ...
    <setting name="logImpl" value="LOG4J"/>
    ...
  </settings>
</configuration>
      

可选的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了 org.apache.ibatis.logging.Log 接口,且构造方法以字符串为参数的类齐全限定名。

你也能够调用以下任一办法来抉择日志实现:

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 办法之前调用以上的某个办法。另外,仅当运行时类门路中存在该日志实现时,日志实现的切换才会失效。如果你的环境中并不存在 Log4J,你却试图调用了相应的办法,MyBatis 就会疏忽这所有换申请,并将以默认的查找程序决定应用的日志实现。

对于 SLF4J、Apache Commons Logging、Apache Log4J 和 JDK Logging 的 API 介绍不在本文档介绍范畴内。不过,上面的例子能够作为一个疾速入门。无关这些日志框架的更多信息,能够参考以下链接:

  • SLF4J
  • Apache Commons Logging
  • Apache Log4j 1.x and 2.x
  • JDK Logging API

日志配置

你能够通过在包、映射类的全限定名、命名空间或全限定语句名上开启日志性能,来查看 MyBatis 的日志语句。

再次揭示,具体配置步骤取决于日志实现。接下来咱们会以 Log4J 作为示范。配置日志性能非常简单:增加一个或多个配置文件(如 log4j.properties),有时还须要增加 jar 包(如 log4j.jar)。上面的例子将应用 Log4J 来配置残缺的日志服务。一共两个步骤:

步骤 1:增加 Log4J 的 jar 包

因为咱们应用的是 Log4J,咱们要确保它的 jar 包能够被利用应用。为此,须要将 jar 包增加到利用的类门路中。Log4J 的 jar 包能够在下面的链接中下载。

对于 web 利用或企业级利用,你能够将 log4j.jar 增加到 WEB-INF/lib 目录下;对于独立利用,能够将它增加到 JVM 的 -classpath 启动参数中。

步骤 2:配置 Log4J

配置 Log4J 比较简单。假如你须要记录这个映射器的日志:

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

在利用的类门路中创立一个名为 log4j.properties 的文件,文件的具体内容如下:

# 全局日志配置
log4j.rootLogger=ERROR, stdout
# MyBatis 日志配置
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# 控制台输入
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 的日志,对于利用的其它局部,只打印错误信息。

为了实现更细粒度的日志输入,你也能够只打印特定语句的日志。以下配置将只打印语句 selectBlog 的日志:

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

或者,你也能够打印一组映射器的日志,只须要关上映射器所在的包的日志性能即可:

log4j.logger.org.mybatis.example=TRACE

某些查问可能会返回宏大的后果集。这时,你可能只想查看 SQL 语句,而疏忽返回的后果集。为此,SQL 语句将会在 DEBUG 日志级别下记录(JDK 日志则为 FINE)。返回的后果集则会在 TRACE 日志级别下记录(JDK 日志则为 FINER)。因而,只有将日志级别调整为 DEBUG 即可:

log4j.logger.org.mybatis.example=DEBUG

但如果你要为上面的映射器 XML 文件打印日志,又该怎么办呢?

<?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>

这时,你能够通过关上命名空间的日志性能来对整个 XML 记录日志:

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

而要记录具体语句的日志,能够这样做:

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

你应该会发现,为映射器和 XML 文件关上日志性能的语句毫无差异。

提醒 如果你应用的是 SLF4J 或 Log4j 2,MyBatis 会设置 tag 为 MYBATIS。

配置文件 log4j.properties 的余下内容用来配置输入器(appender),这一内容曾经超出本文档的范畴。对于 Log4J 的更多内容,能够参考下面的 Log4J 网站。或者,你也能够简略地做个试验,看看不同的配置会产生怎么的成果。

正文完
 0