跳到主要内容

MyBatis

MyBatis 3

入门

  • SqlSessionFactoryBuilder
  • SqlSessionFactory
    • 运行期间一直存在。
    • 不建议多次创建,最好使用单例。
  • SqlSession
    • 每个线程都应该有自己的 SqlSession 实例。
    • 不是线程安全的,因此不允许共享。
  • mapper 映射器实例
    • 通过 SqlSession 获得。
    • 不需要关闭,用完即丢。
  • TransactionManager
  • DataSource
  • namespace
    1. 利用更长的全限定名来将不同的语句隔离开来
    2. 接口绑定
  • 命名解析:MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则
    1. 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
    2. 短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar. selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。
  • 作用域(scope)和生命周期
    • 依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。

XML 配置

properties 属性

优先级从低到高

  • properties 元素。
  • 外部文件
  • SqlSessionFactoryBuilder 的 build 方法传入的属性。

占位符的默认值

  • 需要开启这个特性。
    • <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
  • ${username:root} username 未配置时, root 为默认值。
    • <property name="username" value="${username:ut_user}"/>
  • 如果在 SQL 映射中或者属性名中使用到了 :,就需要修改默认的占位符分隔符。
    • <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/>
    • <property name="username" value="${db:username?:ut_user}"/>

settings 设置

typeAliases 类型别名

  • typeAlias
  • package
  • @Alias("blog)

typeHandlers 类型处理器

MyBatis 从 PreparedStatement 中的参数或结果集中取出一个值时,都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。

typeHandler 是用来处理 Java 类型和 JDBC 类型之间的转换的。

自定义 typeHandlers

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

  • 继承 org.apache.ibatis.type.BaseTypeHandler 类。

  • Java 类型

    • xml 中配置 typeHandler 元素的 javaType="String" 属性
    • @MappedTypes 注解
  • JDBC 类型

    • xml 中配置 typeHandler 元素的 javaType="VARCHAR" 属性
    • @MappedJdbcTypes 注解
      • includeNullJdbcType=true 可以在 ResultNap隐式地使用类型处理器

使用自定义类型处理器的时候,需要在参数和结果映射中指明字段类型

objectFactory 对象工厂

  • 无参构造方法
  • 有参构造方法

plugins 插件

environments 环境配置

每个 SqlSessionFactory 实例只能选择一种环境.

事物管理器 transactionManager

  • JDBC
    • 直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用域。
  • MANAGED
    • 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。
    • 默认会关闭连接
      • 通过将 skipSetAutoCommitOnClose 属性设置为 true 来跳过这个步骤。

数据源 dataSource

  • UNPOOLED 每次请求都会打开一个新的连接。
    • driver
    • url
    • username
    • password
    • defaultTransactionIsolationLevel
    • defaultNetworkTimeout
    • driver.properties
      • driver.encoding=UTF-8
  • POOLED 采用数据库连接池,避免了创建新的连接实例时所必需的初始化和认证时间。
    • poolMaximumActiveConnections
    • poolMaximumIdleConnections
    • poolMaximumCheckoutTime
    • poolTimeToWait
    • poolMaximumLocalBadConnectionTolerance
    • poolPingQuery
    • poolPingEnabled
    • poolPingConnectionsNotUsedFor
  • JNDI 在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
    • initial_context
    • data_source
    • env.encoding=UTF-8

databaseIdProvider 数据库厂商标识

<databaseIdProvider type="DB_VENDOR" />

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

mapper 映射器

<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>

<mapper url="file:///var/mappers/BlogMapper.xml"/>

<mapper class="org.mybatis.builder.PostMapper"/>

<package name="org.mybatis.builder"/>
</mappers>

XML 映射器

  • cache 该命名空间的缓存配置
  • cache-ref 引用其它命名空间的缓存配置
  • resultMap 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • parameterMap
  • sql 可被其它语句引用的可重用语句块。
  • insert
  • update
  • delete
  • select

select

<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</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">

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

<selectKey
keyProperty="id"
resultType="int"
order="BEFORE"
statementType="PREPARED">

主键自增

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

for 循环

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

不支持自动生成主键的数据库

在执行 insert 语句之前,会先运行 selectKey 元素中的语句。

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

sql

可重用 SQL 代码片段。

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </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>
<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>

参数

参数是一个原始类型或简单数据类型

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

参数是一个复杂对象

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

指定一个特殊的数据类型

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

如果列允许使用 null 值,需要指定 JDBC 类型(jdbcType)

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

数值类型,通过设置 numericScale 指定小数点后保留的位数

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

mode 属性

  • IN
  • OUT
  • INOUT

字符串替换

  • ${} 字符替换 不安全
  • #{} 占位符
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);

结果映射 resultMap

没有显式指定 resultMap

返回值的类型是 HashMap

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

返回 JavaBean

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

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>

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

高级结果映射

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

结果映射

  • constructor 用于在实例化类时,注入结果到构造方法中
    • idArg ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg 将被注入到构造方法的一个普通结果
  • id 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result 注入到字段或 JavaBean 属性的普通结果
  • association 一个复杂类型的关联
  • collection 一个复杂类型的集合
  • discriminator 使用结果值来觉得使用哪个 resultMap
    • case

id&result

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

构造方法

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

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

关联

  • 嵌套 select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
  • 嵌套结果查询:使用嵌套的结果映射来处理连接结果的重复子集。
<association property="author" column="blog_author_id" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
  • property:映射到列结果的字段或属性。
  • javaType:类的全限定名称或类型别名。
  • jdbcType:JDBC 类型。
  • typeHandler:类型处理器。

关联嵌套查询

<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>
  • column:数据库中的列名,或者是列的别名。
  • select:用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。
  • fetchType:延迟加载属性的配置。
    • eager
    • lazy

关联的嵌套结果映射

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

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

columnPrefix 重复使用该结果映射来映射 co-author 的结果。

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

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

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

关联的多结果集

SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM AUTHOR WHERE ID = #{id}

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

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

集合

ofType 字段很重要。它告诉 MyBatis 集合中存放的对象类型。

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

集合的嵌套 Select 查询

<resultMap id="blogResult" type="Blog">
<!-- posts 是一个存储 Post 的 ArrayList 集合 -->
<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>

集合的嵌套结果映射

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

集合的多结果集

SELECT * FROM BLOG WHERE ID = #{id}

SELECT * FROM POST WHERE BLOG_ID = #{id}

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

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

鉴别器

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

<!-- 只有当 vehicle_type = 1 的时候,才会加载 doorCount 属性。 -->
<resultMap id="carResult" type="Car">
<result property="doorCount" column="door_count" />
</resultMap>

<!-- 当 vehicle_type = 1 的时候,会加载 carResult 和 vehicleResult。 -->
<resultMap id="carResult" type="Car" extends="vehicleResult">
<result property="doorCount" column="door_count" />
</resultMap>
<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>

自动映射

需将 mapUnderscoreToCamelCase 设置为 true

<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 自动映射所有属性

缓存

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行。(在 xxxMapper.xml 中添加。)

<cache/>

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

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

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

<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 语句执行时不要刷新缓存。

动态 SQL

  • if
  • choose
  • trim
  • foreach

if

<select id="findActiveBlogWithTitleLike"
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

<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

where

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

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

foreach

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

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

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

多数据库支持

<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 中的插入脚本语言

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);
}

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

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

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

Java API

/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

通过这个接口来执行命令,获取映射器实例和管理事务。

SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession

SqlSessionFactoryBuilder

SqlSessionFactory

  • 事务处理
  • 数据库连接
  • 语句执行

SqlSession

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

立即批量更新方法

将 ExecutorType 设置为 ExecutorType.BATCH 时,可以使用这个方法清除(执行)缓存在 JDBC 驱动类中的批量更新语句。

List<BatchResult> flushStatements()

本地缓存

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

每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 session 执行过的查询结果都会被保存在本地缓存中,所以,当再次执行参数相同的相同查询时,就不需要实际查询数据库了。 本地缓存将会在做出修改、事务提交或回滚,以及关闭session 时清空。

默认情况下,本地缓存数据的生命周期等同于整个 session 的周期。由于缓存会被用来解决循环 引用问题和加快重复嵌套查询的速度,所以无法将其完全禁用。 但是你可以通过设置 localCacheScope=STATEMENT 来只在语句执行时使用缓存。

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

日志

日志配置

可以对包、映射类的全限定名、命名空间或全限定语句名开启日志功能来查看 MyBatis 的日志语句。