MyBatis
入门
- SqlSessionFactoryBuilder
- SqlSessionFactory
- 运行期间一直存在。
- 不建议多次创建,最好使用单例。
- SqlSession
- 每个线程都应该有自己的 SqlSession 实例。
- 不是线程安全的,因此不允许共享。
- mapper 映射器实例
- 通过 SqlSession 获得。
- 不需要关闭,用完即丢。
- TransactionManager
- DataSource
- namespace
- 利用更长的全限定名来将不同的语句隔离开来
- 接口绑定
- 命名解析:MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则
- 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
- 短名称(比如 “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
注解
- xml 中配置 typeHandler 元素的
-
JDBC 类型
- xml 中配置 typeHandler 元素的
javaType="VARCHAR"
属性 @MappedJdbcTypes
注解includeNullJdbcType=true
可以在ResultNap
中隐式地使用类型处理器。
- xml 中配置 typeHandler 元素的
使用自定义类型处理器的时候,需要在参数和结果映射中指明字段类型
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>