导图社区 mybatis
吐血整理,共21个页面,包含mybatis全套技术栈:MyBatis框架整体设计、mybatis初始化与执行sql过程、mybatis源码的几个主要部件等等。
编辑于2022-02-10 18:09:04mybatis
1、mybatis简介
2、与hibernate对比
3、几个关键类
4、mybatis执行浅析
5、MyBatis框架整体设计
6、mybatis初始化与执行sql过程
7、mybatis源码的几个主要部件
8、XML 映射配置文件(一)
9、XML 映射配置文件(二)
10、XML 映射配置文件(三)
11、mybatis的优缺点
12、与spring整合
13、mapper的xml文件(一)
14、mapper的xml文件(二)
15、mapper的xml文件(三)
16、mapper的xml文件(四)
17、resultMap子元素
18、元素集合collection
19、mapper的xml文件(五)
20、动态 SQL
21、#{}和${}的区别
21、#{}和${}的区别
#{}
解读
使用#{}格式的语法在mybatis中使用Preparement语句来安全的设置值
PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1,id);
例子
执行SQL:Select * from emp where name = #{employeeName}
参数:employeeName=>Smith
解析后执行的SQL:Select * from emp where name = ?
#方式能够很大程度防止sql注入
${}
解读
有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY
$将传入的数据直接显示生成在sql中
Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql);
例子
执行SQL:Select * from emp where name = ${employeeName}
参数:employeeName传入值为:Smith
解析后执行的SQL:Select * from emp where name =Smith
总结
#方式能够很大程度防止sql注入,$方式无法防止Sql注入
$方式一般用于传入数据库对象
使用$要么不允许用户输入这些字段,要么自行转义并检验。
一般能用#的就别用$
19、mapper的xml文件(五)
鉴别器discriminator
概念
有时一个单独的数据库查询也许返回很多不同 (但是希望有些关联) 数据类型的结果集
表现很像 Java 语言中的 switch 语句
例子
缓存cache
要开启二级缓存,你需要在你的 SQL 映射文件中添加一行<cache/>
<cache/>效果如下
映射语句文件中的所有 select 语句将会被缓存。
映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
属性
以上配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的
收回策略
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
默认的是 LRU
18、元素集合collection
例子
对应实体代码
private List<Post> posts;
概念
集合元素的作用几乎和关联是相同的。
要映射嵌套结果集合到 List 中,我们使用集合元素。就像关联元素一样,我们可以从 连接中使用嵌套查询,或者嵌套结果。
集合的嵌套查询
集合的嵌套结果
属性ofType说明
关联集合的元素类型
例子
说明
联合了博客表和文章,博客和文章是一对多的关系
17、resultMap子元素
id & result
例子
结果映射最基本内容
都映射一个单独列的值到简单数据类型(字符 串,整型,双精度浮点数,日期等)的单独属性或字段
这两者之间的唯一不同是 id 表示的结果将是当比较对象实例时用到的标识属性
构造方法constructor
例子
对应构造方法
public class User { //... public User(Integer id, String username, int age) { //... } //... }
关联association
例子
对应类型关系
public class User { //... private Author author; //... }
一个博客有一个用户
关联的嵌套查询
我们有两个查询语句:一个来加载博客,另外一个来加载作者,而且博客的结果映射描 述了“selectAuthor”语句应该被用来加载它的 author 属性。 其他所有的属性将会被自动加载,假设它们的列和属性名相匹配。 这种方式很简单, 但是对于大型数据集合和列表将不会表现很好。 问题就是我们熟知的 “N+1 查询问题”。概括地讲,N+1 查询问题可以是这样引起的: 你执行了一个单独的 SQL 语句来获取结果列表(就是“+1”)。 对返回的每条记录,你执行了一个查询语句来为每个加载细节(就是“N”)。 这个问题会导致成百上千的 SQL 语句被执行。这通常不是期望的。 MyBatis 能延迟加载这样的查询就是一个好处,因此你可以分散这些语句同时运行的消 耗。然而,如果你加载一个列表,之后迅速迭代来访问嵌套的数据,你会调用所有的延迟加 载,这样的行为可能是很糟糕的。
<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>
两个查询语句:一个来加载博客,另外一个来加载作者
关联的嵌套结果
注意
id元素在嵌套结果映射中扮演着非 常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。
图片截图资料
有关关联的部分建议通过给出的链接来学习, 应该有代码结合才能更好理解关联association的用法
16、mapper的xml文件(四)
Result Maps
幕后创建ResultMap
<!-- In mybatis-config.xml file --> <typeAlias type="com.someapp.model.User" alias="User"/> <!-- In SQL Mapping XML file --> <select id="selectUsers" resultType="User"> select id, username, hashedPassword from some_table where id = #{id} </select>
说明
这些情况下,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
<select id="selectUsers" resultMap="userResultMap"> select user_id, user_name, hashed_password from some_table where id = #{id} </select>
以上是解决列名不匹配的两种方式
高级结果映射
例子
高级结果映射 MyBatis 创建的一个想法:数据库不用永远是你想要的或需要它们是什么样的。而我们 最喜欢的数据库最好是第三范式或 BCNF 模式,但它们有时不是。如果可能有一个单独的 数据库映射,所有应用程序都可以使用它,这是非常好的,但有时也不是。结果映射就是 MyBatis 提供处理这个问题的答案。 比如,我们如何映射下面这个语句? 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} 你可能想把它映射到一个智能的对象模型,包含一个作者写的博客,有很多的博文,每 篇博文有零条或多条的评论和标签。 下面是一个完整的复杂结果映射例子 (假设作者, 博客, 博文, 评论和标签都是类型的别名) 我们来看看, 。 但是不用紧张, 我们会一步一步来说明。 当天最初它看起来令人生畏,但实际上非常简单。
resultMap 元素
属性
id
当前命名空间中的一个唯一标识,用于标识一个result map.
type
类的全限定名, 或者一个类型别名 (内置的别名可以参考上面的表格).
autoMapping
如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性autoMappingBehavior。默认值为:unset。
resultMap子元素概念视图
constructor - 类在实例化时,用来注入结果到构造方法中
idArg - ID 参数;标记结果作为 ID 可以帮助提高整体效能
arg - 注入到构造方法的一个普通结果
id – 一个 ID 结果;标记结果作为 ID 可以帮助提高整体效能
result – 注入到字段或 JavaBean 属性的普通结果
association – 一个复杂的类型关联;许多结果将包成这种类型
嵌入结果映射 – 结果映射自身的关联,或者参考一个
collection – 复杂类型的集
嵌入结果映射 – 结果映射自身的集,或者参考一个
discriminator – 使用结果值来决定使用哪个结果映射
case – 基于某些值的结果映射
嵌入结果映射 – 这种情形结果也映射它本身,因此可以包含很多相 同的元素,或者它可以参照一个外部的结果映射。
15、mapper的xml文件(三)
sql
概念
这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。它可以被静态地(在加载参数) 参数化. 不同的属性值通过包含的实例变化.
属性值可以用于包含的refid属性或者包含的字句里面的属性值
${prefix}Table from select field1, field2, field3
例子
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
参数(Parameters)
例子
<insert id="insertUser" parameterType="User"> insert into users (id, username, password) values (#{id}, #{username}, #{password}) </insert>
如果 User 类型的参数对象传递到了语句中,id、username 和 password 属性将会被查找,然后将它们的值传入预处理语句的参数中
参数也可以指定一个特殊的数据类型
#{property,javaType=int,jdbcType=NUMERIC}
javaType 通常可以从参数对象中来去确定,前提是只要对象不是一个 HashMap。 那么 javaType 应该被确定来保证使用正确类型处理器
字符串替换
概念
使用$符号代替#设置值
默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值(比如?)
这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串
例子
ORDER BY ${columnName}
注意
以这种方式接受从用户输出的内容并提供给语句中不变的字符串是不安全的,会导致潜在的 SQL 注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。
14、mapper的xml文件(二)
增删改实例语句
<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中主键设置auto_increment,主流的数据库一般都支持,当然也有其他不支持的。。。
分类
数据库支持自动生成主键
方法
可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上就OK了
实例
<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id"> insert into Author (username,password,email,bio) values (#{username},#{password},#{email},#{bio}) </insert>
<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>
随机生成一个 ID做为主键
在上面的示例中,selectKey 元素将会首先运行,Author 的 id 会被设置,然后插入语句会被调用。这给你了一个和数据库中来处理自动生成的主键类似的行为,避免了使 Java 代码变得复杂
13、mapper的xml文件(一)
select – 映射查询语句
实例
<select id="selectPerson" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select>
这个语句被称作 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值
属性
id
在命名空间中唯一的标识符,可以被用来引用这条语句
必选
parameterType
将会传入这条语句的参数类的完全限定名或别名
可选
resultType
从这条语句中返回的期望类型的类的完全限定名或别名
注意
如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身
使用 resultType 或 resultMap,但不能同时使用
resultMap
外部 resultMap 的命名引用
注意
使用 resultMap 或 resultType,但不能同时使用
flushCache
将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空
默认值:false
useCache
将其设置为 true,将会导致本条语句的结果被二级缓存
默认值:对 select 元素为 true
timeout
这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数
默认值为 unset(依赖驱动)
fetchSize
这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等
默认值为 unset(依赖驱动)
statementType
STATEMENT,PREPARED 或 CALLABLE 的一个
这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement
默认值:PREPARED
resultSetType
这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。
delete – 映射删除语句
insert – 映射插入语句
update – 映射更新语句
insert、update公共属性
useGeneratedKeys
(仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段)
默认值:false。
keyProperty
(仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值
默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn
(仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
insert、update、delete公共属性
id
命名空间中的唯一标识符,可被用来代表这条语句。
parameterType
将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
parameterMap
这是引用外部 parameterMap 的已经被废弃的方法。使用内联参数映射和 parameterType 属性。
flushCache
将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,
默认值:true(对应插入、更新和删除语句)。
timeout
这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。
默认值为 unset(依赖驱动)。
statementType
STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,
默认值:PREPARED。
databaseId
如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。
12、与spring整合
添加MyBatis-Spring包
概念
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中
使用这个类库中的类, Spring 将会加载必要的 MyBatis 工厂类和 session 类
这个类库也提供一个简单的方式来注入 MyBatis 数据映射器和 SqlSession 到业务层的 bean 中
而且它也会处理事务, 翻译 MyBatis 的异常到 Spring 的 DataAccessException 异常(数据访问异常,译者注)中
安装
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>x.x.x</version> </dependency>
配置SqlSessionFactory
概念
直接在spring的上下文配置就可以
整合后,可以不需要单独的mybatis配置文件,全部的配置内容都可以在spring的上下文当中
在spring配置文件中
注意
dataSource是数据源配置,常用有DBCP,C3P0,Druid等
mapperLocations是指接口xml的文件配置,如果不配置的话映射接口类文件(.java)和映射XML文件(.xml)需要放在相同的包下 mapperLocations好像和mybatis-config.xml的mappers功能相似,两个不需要同时配。
configLocation不是必须的,如果没有全局配置文件可以去掉
配置数据映射器类
两种方法
方法一:利用xml来进行显示的逐一配置
mapper很多的话就会很麻烦
方法二:利用mybatis-spring提供的自动扫描机制
方法二
建议使用扫描
11、mybatis的优缺点
优点
1. 易于上手和掌握。
2. sql写在xml里,便于统一管理和优化。
3. 解除sql与程序代码的耦合。
4. 提供映射标签,支持对象与数据库的orm字段关系映射
5. 提供对象关系映射标签,支持对象关系组建维护
6. 提供xml标签,支持编写动态sql。
缺点
1. sql工作量很大,尤其是字段多、关联表多时,更是如此。
2. sql依赖于数据库,导致数据库移植性差。
3. 由于xml里标签id必须唯一,导致DAO中方法不支持方法重载。
4. 字段映射标签和对象关系映射标签仅仅是对映射关系的描述,具体实现仍然依赖于sql。(比如配置了一对多Collection标签,如果sql里没有join子表或查询子表的话,查询后返回的对象是不具备对象关系的,即Collection的对象为null)
5. DAO层过于简单,对象组装的工作量较大。
6. 不支持级联更新、级联删除。
7. 编写动态sql时,不方便调试,尤其逻辑复杂时。
8 提供的写动态sql的xml标签功能简单(连struts都比不上),编写动态sql仍然受限,且可读性低。
9. 若不查询主键字段,容易造成查询出的对象有“覆盖”现象。
10. 参数的数据类型支持不完善。(如参数为Date类型时,容易报没有get、set方法,需在参数上加@param)
11. 多参数时,使用不方便,功能不够强大。(目前支持的方法有map、对象、注解@param以及默认采用012索引位的方式)
12. 缓存使用不当,容易产生脏数据。
1、mybatis简介
参考网站
http://www.mybatis.org/mybatis-3/zh/index.html
什么是 MyBatis
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs映射成数据库中的记录
安装
pom.xml 文件
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>x.x.x</version> </dependency>
2、与hibernate对比
简介
mybatis
iBATIS 的着力点,则在于POJO 与SQL之间的映射关系
然后通过映射配置文件,将SQL所需的参数,以及返回的结果字段映射到指定POJO
相对Hibernate“O/R”而言,iBATIS 是一种“Sql Mapping”的ORM实现
Hibernate
对数据库结构提供了较为完整的封装
Hibernate的O/R Mapping实现了POJO 和数据库表之间的映射,以及SQL 的自动生成和执行
Hibernate/OJB 会根据制定的存储逻辑,自动生成对应的SQL 并调用JDBC 接口加以执行
开发对比
mybatis
Mybatis框架相对简单很容易上手,但也相对简陋些
针对高级查询,Mybatis需要手动编写SQL语句,以及ResultMap
Hibernate
Hibernate的真正掌握要比Mybatis来得难些
Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专注于业务流程。
调优方案
mybatis
可以进行详细的SQL优化设计
采用合理的Session管理机制;
Hibernate
制定合理的缓存策略;
尽量使用延迟加载特性;
采用合理的Session管理机制;
使用批量抓取,设定合理的批处理参数(batch_size);
进行合理的O/R映射设计
扩展性方面
mybatis
MyBatis项目中所有的SQL语句都是依赖所用的数据库的,所以不同数据库类型的支持不好
Hibernate
Hibernate与具体数据库的关联只需在XML文件中配置即可,所有的HQL语句与具体使用的数据库无关,移植性很好
缓存机制
相同点 Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓存方案,创建适配器来完全覆盖缓存行为。 不同点 Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存。 MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。 两者比较 因为Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。 而MyBatis在这一方面,使用二级缓存时需要特别小心。如果不能完全确定数据更新操作的波及范围,避免Cache的盲目使用。否则,脏数据的出现会给系统的正常运行带来很大的隐患。
mybatis
默认情况下是没有开启缓存
要开启二级缓存,你需要在你的 SQL 映射文件中添加一行: <cache/>
映射语句文件中的所有 select 语句将会被缓存
映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存
缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回
根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
Hibernate
一级缓存是Session缓存,利用好一级缓存就需要对Session的生命周期进行管理好
二级缓存是SessionFactory级的缓存,分为内置缓存和外置缓存
内置缓存中存放的是SessionFactory对象的一些集合属性包含的数据(映射元素据及预定SQL语句等),对于应用程序来说,它是只读的。 外置缓存中存放的是数据库数据的副本,其作用和一级缓存类似.二级缓存除了以内存作为存储介质外,还可以选用硬盘等外部存储设备。二级缓存称为进程级缓存或SessionFactory级缓存,它可以被所有session共享,它的生命周期伴随着SessionFactory的生命周期存在和消亡。
对比总结
mybatis
MyBatis可以进行更为细致的SQL优化,可以减少查询字段。
MyBatis容易掌握,而Hibernate门槛较高。
Hibernate
Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。
Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。
Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。
Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。
3、几个关键类
mybatis-config.xml
PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
XML 配置文件(configuration XML)中包含了对 MyBatis 系统的核心设置,
包含获取数据库连接实例的数据源(DataSource)和决定事务作用域和控制方式的事务管理器(TransactionManager)
SqlSessionFactoryBuilder
package org.apache.ibatis.session; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.util.Properties; import org.apache.ibatis.builder.xml.XMLConfigBuilder; import org.apache.ibatis.exceptions.ExceptionFactory; import org.apache.ibatis.executor.ErrorContext; import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory; /** * Builds {@link SqlSession} instances. * * @author Clinton Begin */ public class SqlSessionFactoryBuilder { public SqlSessionFactory build(Reader reader) { return build(reader, null, null); } public SqlSessionFactory build(Reader reader, String environment) { return build(reader, environment, null); } public SqlSessionFactory build(Reader reader, Properties properties) { return build(reader, null, properties); } public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment) { return build(inputStream, environment, null); } public SqlSessionFactory build(InputStream inputStream, Properties properties) { return build(inputStream, null, properties); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } }
概念
SqlSessionFactoryBuilder通过类名就可以看出这个类的主要作用就是创建一个SqlSessionFactory,通过输入mybatis配置文件的字节流或者字符流,生成XMLConfigBuilder,XMLConfigBuilder创建一个Configuration,Configuration这个类中包含了mybatis的配置的一切信息,mybatis进行的所有操作都需要根据Configuration中的信息来进行。
作用域(Scope)和生命周期
可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)
SqlSessionFactory接口
package org.apache.ibatis.session; import java.sql.Connection; /** * Creates an {@link SqlSession} out of a connection or a DataSource * * @author Clinton Begin */ public interface SqlSessionFactory { SqlSession openSession(); SqlSession openSession(boolean autoCommit); SqlSession openSession(Connection connection); SqlSession openSession(TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType); SqlSession openSession(ExecutorType execType, boolean autoCommit); SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType, Connection connection); Configuration getConfiguration(); }
概念
sql会话工厂,用于创建SqlSession
作用域(Scope)和生命周期
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建
最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
如何创建
使用xml构建
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
java代码构建
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);
SqlSession接口
package org.apache.ibatis.session; import java.io.Closeable; import java.sql.Connection; import java.util.List; import java.util.Map; import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.executor.BatchResult; /** * The primary Java interface for working with MyBatis. * Through this interface you can execute commands, get mappers and manage transactions. * * @author Clinton Begin */ public interface SqlSession extends Closeable { /** * Retrieve a single row mapped from the statement key * @param the returned object type * @param statement * @return Mapped object */ T selectOne(String statement); /** * Retrieve a single row mapped from the statement key and parameter. * @param the returned object type * @param statement Unique identifier matching the statement to use. * @param parameter A parameter object to pass to the statement. * @return Mapped object */ T selectOne(String statement, Object parameter); /** * Retrieve a list of mapped objects from the statement key and parameter. * @param the returned list element type * @param statement Unique identifier matching the statement to use. * @return List of mapped object */ List selectList(String statement); /** * Retrieve a list of mapped objects from the statement key and parameter. * @param the returned list element type * @param statement Unique identifier matching the statement to use. * @param parameter A parameter object to pass to the statement. * @return List of mapped object */ List selectList(String statement, Object parameter); /** * Retrieve a list of mapped objects from the statement key and parameter, * within the specified row bounds. * @param the returned list element type * @param statement Unique identifier matching the statement to use. * @param parameter A parameter object to pass to the statement. * @param rowBounds Bounds to limit object retrieval * @return List of mapped object */ List selectList(String statement, Object parameter, RowBounds rowBounds); /** * The selectMap is a special case in that it is designed to convert a list * of results into a Map based on one of the properties in the resulting * objects. * Eg. Return a of Map[Integer,Author] for selectMap("selectAuthors","id") * @param the returned Map keys type * @param the returned Map values type * @param statement Unique identifier matching the statement to use. * @param mapKey The property to use as key for each value in the list. * @return Map containing key pair data. */ Map selectMap(String statement, String mapKey); /** * The selectMap is a special case in that it is designed to convert a list * of results into a Map based on one of the properties in the resulting * objects. * @param the returned Map keys type * @param the returned Map values type * @param statement Unique identifier matching the statement to use. * @param parameter A parameter object to pass to the statement. * @param mapKey The property to use as key for each value in the list. * @return Map containing key pair data. */ Map selectMap(String statement, Object parameter, String mapKey); /** * The selectMap is a special case in that it is designed to convert a list * of results into a Map based on one of the properties in the resulting * objects. * @param the returned Map keys type * @param the returned Map values type * @param statement Unique identifier matching the statement to use. * @param parameter A parameter object to pass to the statement. * @param mapKey The property to use as key for each value in the list. * @param rowBounds Bounds to limit object retrieval * @return Map containing key pair data. */ Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds); /** * A Cursor offers the same results as a List, except it fetches data lazily using an Iterator. * @param the returned cursor element type. * @param statement Unique identifier matching the statement to use. * @return Cursor of mapped objects */ Cursor selectCursor(String statement); /** * A Cursor offers the same results as a List, except it fetches data lazily using an Iterator. * @param the returned cursor element type. * @param statement Unique identifier matching the statement to use. * @param parameter A parameter object to pass to the statement. * @return Cursor of mapped objects */ Cursor selectCursor(String statement, Object parameter); /** * A Cursor offers the same results as a List, except it fetches data lazily using an Iterator. * @param the returned cursor element type. * @param statement Unique identifier matching the statement to use. * @param parameter A parameter object to pass to the statement. * @param rowBounds Bounds to limit object retrieval * @return Cursor of mapped objects */ Cursor selectCursor(String statement, Object parameter, RowBounds rowBounds); /** * Retrieve a single row mapped from the statement key and parameter * using a {@code ResultHandler}. * @param statement Unique identifier matching the statement to use. * @param parameter A parameter object to pass to the statement. * @param handler ResultHandler that will handle each retrieved row */ void select(String statement, Object parameter, ResultHandler handler); /** * Retrieve a single row mapped from the statement * using a {@code ResultHandler}. * @param statement Unique identifier matching the statement to use. * @param handler ResultHandler that will handle each retrieved row */ void select(String statement, ResultHandler handler); /** * Retrieve a single row mapped from the statement key and parameter * using a {@code ResultHandler} and {@code RowBounds} * @param statement Unique identifier matching the statement to use. * @param rowBounds RowBound instance to limit the query results * @param handler ResultHandler that will handle each retrieved row */ void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler); /** * Execute an insert statement. * @param statement Unique identifier matching the statement to execute. * @return int The number of rows affected by the insert. */ int insert(String statement); /** * Execute an insert statement with the given parameter object. Any generated * autoincrement values or selectKey entries will modify the given parameter * object properties. Only the number of rows affected will be returned. * @param statement Unique identifier matching the statement to execute. * @param parameter A parameter object to pass to the statement. * @return int The number of rows affected by the insert. */ int insert(String statement, Object parameter); /** * Execute an update statement. The number of rows affected will be returned. * @param statement Unique identifier matching the statement to execute. * @return int The number of rows affected by the update. */ int update(String statement); /** * Execute an update statement. The number of rows affected will be returned. * @param statement Unique identifier matching the statement to execute. * @param parameter A parameter object to pass to the statement. * @return int The number of rows affected by the update. */ int update(String statement, Object parameter); /** * Execute a delete statement. The number of rows affected will be returned. * @param statement Unique identifier matching the statement to execute. * @return int The number of rows affected by the delete. */ int delete(String statement); /** * Execute a delete statement. The number of rows affected will be returned. * @param statement Unique identifier matching the statement to execute. * @param parameter A parameter object to pass to the statement. * @return int The number of rows affected by the delete. */ int delete(String statement, Object parameter); /** * Flushes batch statements and commits database connection. * Note that database connection will not be committed if no updates/deletes/inserts were called. * To force the commit call {@link SqlSession#commit(boolean)} */ void commit(); /** * Flushes batch statements and commits database connection. * @param force forces connection commit */ void commit(boolean force); /** * Discards pending batch statements and rolls database connection back. * Note that database connection will not be rolled back if no updates/deletes/inserts were called. * To force the rollback call {@link SqlSession#rollback(boolean)} */ void rollback(); /** * Discards pending batch statements and rolls database connection back. * Note that database connection will not be rolled back if no updates/deletes/inserts were called. * @param force forces connection rollback */ void rollback(boolean force); /** * Flushes batch statements. * @return BatchResult list of updated records * @since 3.0.6 */ List flushStatements(); /** * Closes the session */ @Override void close(); /** * Clears local session cache */ void clearCache(); /** * Retrieves current configuration * @return Configuration */ Configuration getConfiguration(); /** * Retrieves a mapper. * @param the mapper type * @param type Mapper interface class * @return a mapper bound to this SqlSession */ T getMapper(Class type); /** * Retrieves inner database connection * @return Connection */ Connection getConnection(); }
概念
SqlSession是MyBatis的一个重要接口,定义了数据库的增删改查以及事务管理的常用方法。
SqlSession还提供了查找Mapper接口的有关方法。
作用域(Scope)和生命周期
每个线程都应该有它自己的 SqlSession 实例
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。
如何创建
SqlSession session = sqlSessionFactory.openSession(); try { // do work } finally { session.close(); }
Mapper接口
概念
承载了实际的业务逻辑,其生命周期比较短,由SqlSession创建,用于将Java对象和实际的SQL语句对应起来。
Mapper接口是指程序员自行定义的一个数据操纵接口,类似于通常所说的DAO接口。跟DAO不同的地方在于Mapper接口只需要程序员定义,不需要程序员去实现,MyBatis会自动为Mapper接口创建动态代理对象。Mapper接口的方法通常与Mapper配置文件中的select、insert、update、delete等XML结点存在一一对应关系。
实现方式
(1)使用XML配置文件的方式。
(2)使用注解方式。
(3直接使用MyBatis提供的API。
如何创建
20、动态 SQL
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结果返回
choose (when, otherwise)
概念
对于这类标签,就是采用多个选项中找一个,就像单项选择题,但是你不会都选择,只会从中选择1个来作为条件。就有点类似于switch。。case。
例子
<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)
概念
如果使用第一个if语句的话,就会发现没有写where标签就会报错。而这类标签通常是搭配条件标签使用的。
where一般可以用1=1来解决
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作为集合参数
当使用可迭代对象或者数组时,index是当前迭代的次数,item的值是本次迭代获取的元素。
4、mybatis执行浅析
JDBC执行流程
public static List> queryForList(){ Connection connection = null; ResultSet rs = null; PreparedStatement stmt = null; List> resultList = new ArrayList>(); try { // 加载JDBC驱动 Class.forName("oracle.jdbc.driver.OracleDriver").newInstance(); String url = "jdbc:oracle:thin:@localhost:1521:ORACLEDB"; String user = "trainer"; String password = "trainer"; // 获取数据库连接 connection = DriverManager.getConnection(url,user,password); String sql = "select * from userinfo where user_id = ? "; // 创建Statement对象(每一个Statement为一次数据库执行请求) stmt = connection.prepareStatement(sql); // 设置传入参数 stmt.setString(1, "zhangsan"); // 执行SQL语句 rs = stmt.executeQuery(); // 处理查询结果(将查询结果转换成List格式) ResultSetMetaData rsmd = rs.getMetaData(); int num = rsmd.getColumnCount(); while(rs.next()){ Map map = new HashMap(); for(int i = 0;i String columnName = rsmd.getColumnName(i+1); map.put(columnName,rs.getString(columnName)); } resultList.add(map); } } catch (Exception e) { e.printStackTrace(); } finally { try { // 关闭结果集 if (rs != null) { rs.close(); rs = null; } // 关闭执行 if (stmt != null) { stmt.close(); stmt = null; } if (connection != null) { connection.close(); connection = null; } } catch (SQLException e) { e.printStackTrace(); } } return resultList; }
加载JDBC驱动;
建立并获取数据库连接;
创建 JDBC Statements 对象;
设置SQL语句的传入参数;
执行SQL语句并获得查询结果;
对查询结果进行转换处理并将处理结果返回;
释放相关资源(关闭Connection,关闭Statement,关闭ResultSet);
mybatis在jdbc的基础上进行了封装
三层功能架构
API接口层
使用传统的MyBatis提供的API
使用Mapper接口
接口中声明的方法和<mapper> 节点中的<select|update|delete|insert> 节点项对应
即<select|update|delete|insert> 节点的id值为Mapper 接口中的方法名称
parameterType 值表示Mapper 对应方法的入参类型
resultMap 值则对应了Mapper 接口表示的返回值类型或者返回结果集的元素类型
数据处理层
通过传入参数构建动态SQL语句
MyBatis 通过传入的参数值,使用 Ognl 来动态地构造SQL语句,使得MyBatis 有很强的灵活性和扩展性
参数映射指的是对于java 数据类型和jdbc数据类型之间的转换
SQL语句的执行以及封装查询结果集成List<E>
支持结果集关系一对多和多对一的转换
嵌套查询语句的查询
嵌套结果集的查询
基础支撑层
事务管理机制
连接池管理机制
缓存机制
SQL语句的配置方式
5、MyBatis框架整体设计
框架图
部件关系,层次结构图
6、mybatis初始化与执行sql过程
初始化
概念
可以这么说,MyBatis初始化的过程,就是创建 Configuration对象的过程
过程
加载配置文件mybatis-config.xml到MyBatis内部
使用 org.apache.ibatis.session.Configuration对象作为一个所有配置信息的容器,Configuration对象的组织结构和XML配置文件的组织结构几乎完全一样, 这样配置文件的信息就存到了Configuration这个类中了
流程图
sql执行总体流程
加载配置并初始化
加载配置文件,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中
接收调用请求
触发条件:
调用Mybatis提供的API
传入参数:
为SQL的ID和传入参数对象
处理过程:
将请求传递给下层的请求处理层进行处理。
处理操作请求
触发条件:
API接口层传递请求过来
传入参数:
为SQL的ID和传入参数对象
处理过程:
(A)根据SQL的ID查找对应的MappedStatement对象。
(B)根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
(C)获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
(D)根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果
(E)释放连接资源。
(4)返回处理结果将最终的处理结果返回。
整体执行流程图.jpg
浮动主题
小结过程
加载配置
SQL解析
SQL执行
结果映射
7、mybatis源码的几个主要部件
SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;
Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;
StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数;
ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换;
MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装;
SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回;
BoundSql:表示动态生成的SQL语句以及相应的参数信息;
Configuration:MyBatis所有的配置信息都维持在Configuration对象之中;
8、XML 映射配置文件(一)
properties
作用
引用propertis文件并读取配置信息,也可以在<properties/>标签中定义属性
例子
创建一个资源文件jdbc.properties
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver jdbc.url=jdbc:oracle:thin:@localhost:1521:orcl jdbc.username=mybatis jdbc.password=mybatis
mybatis-config.xml中引入
也可以在<properties/>标签中定义属性
使用properties文件里的属性
配置的加载顺序
在 properties 元素体内指定的属性首先被读取。
然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。
最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。
settings
概念
MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为
配置方式
常用
cacheEnabled
该配置影响的所有映射器中配置的缓存的全局开关。
默认true
lazyLoadingEnabled
延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。
默认false
aggressiveLazyLoading
当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载
默认false (true in ≤3.4.1)
logImpl
指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
9、XML 映射配置文件(二)
typeAliases
概念
类型别名。
给java类型取一个别名,方便在核心配置、映射配置中来使用这个java类型。
它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余
比较(区别看resultType)
不使用别名
<select id="findUserById" resultType="com.yu.model.User" parameterType="long"> select * from t_user where id = #{id} </select>
使用别名
在核心配置文件中加上配置
方式一
或者注解方式
@Alias("user") public class User { ... }
在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名
若有注解,则别名为其注解值
方式二
修改UserMapper.xml的配置
<select id="findUserById" resultType="User" parameterType="long"> select * from t_user where id = #{id} </select>
内置类型别名
已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,需要注意的是由基本类型名称重复导致的特殊处理。
内置类型别名.png
typeHandlers
概念
类型处理器
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型
内置类处理器
内置类处理器.png
用途
1)获取数据库的值,以合适的方式转变为对应的java类型
2)将java类型,以合适的方式转化为数据库的保存类型
自定义类型处理器
使用这个的类型处理器将会覆盖已经存在的处理 Java 的 String 类型属性和 VARCHAR 参数及结果的类型处理器。 要注意 MyBatis 不会窥探数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明那是 VARCHAR 类型的字段, 以使其能够绑定到正确的类型处理器上。 这是因为:MyBatis 直到语句被执行才清楚数据类型。 通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变: 在类型处理器的配置元素(typeHandler element)上增加一个 javaType 属性(比如:javaType="String"); 在类型处理器的类上(TypeHandler class)增加一个 @MappedTypes 注解来指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解方式将被忽略。 可以通过两种方式来指定被关联的 JDBC 类型: 在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType="VARCHAR"); 在类型处理器的类上(TypeHandler class)增加一个 @MappedJdbcTypes 注解来指定与其关联的 JDBC 类型列表。 如果在 jdbcType 属性中也同时指定,则注解方式将被忽略。 当决定在ResultMap中使用某一TypeHandler时,此时java类型是已知的(从结果类型中获得),但是JDBC类型是未知的。 因此Mybatis使用javaType=[TheJavaType], jdbcType=null的组合来选择一个TypeHandler。 这意味着使用@MappedJdbcTypes注解可以限制TypeHandler的范围,同时除非显示的设置,否则TypeHandler在ResultMap中将是无效的。 如果希望在ResultMap中使用TypeHandler,那么设置@MappedJdbcTypes注解的includeNullJdbcType=true即可。 然而从Mybatis 3.4.0开始,如果只有一个注册的TypeHandler来处理Java类型,那么它将是ResultMap使用Java类型时的默认值(即使没有includeNullJdbcType=true)。 最后,可以让 MyBatis 为你查找类型处理器: 注意在使用自动检索(autodiscovery)功能的时候,只能通过注解方式来指定 JDBC 的类型。 你能创建一个泛型类型处理器,它可以处理多于一个类。为达到此目的, 需要增加一个接收该类作为参数的构造器,这样在构造一个类型处理器的时候 MyBatis 就会传入一个具体的类。 //GenericTypeHandler.java public class GenericTypeHandler extends BaseTypeHandler { private Class type; public GenericTypeHandler(Class type) { if (type == null) throw new IllegalArgumentException("Type argument cannot be null"); this.type = type; } ...
实现 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); } }
使用这个的类型处理器将会覆盖已经存在的处理 Java 的 String 类型属性和 VARCHAR 参数及结果的类型处理器。
处理枚举类型
若想映射枚举类型 Enum,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选一个来使用。 比如说我们想存储取近似值时用到的舍入模式。默认情况下,MyBatis 会利用 EnumTypeHandler 来把 Enum 值转换成对应的名字。 注意 EnumTypeHandler 在某种意义上来说是比较特别的,其他的处理器只针对某个特定的类,而它不同,它会处理任意继承了 Enum 的类。 不过,我们可能不想存储名字,相反我们的 DBA 会坚持使用整形值代码。那也一样轻而易举: 在配置文件中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可, 这样每个 RoundingMode 将通过他们的序数值来映射成对应的整形。 但是怎样能将同样的 Enum 既映射成字符串又映射成整形呢? 自动映射器(auto-mapper)会自动地选用 EnumOrdinalTypeHandler 来处理, 所以如果我们想用普通的 EnumTypeHandler,就非要为那些 SQL 语句显式地设置要用到的类型处理器不可。
若想映射枚举类型 Enum,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选一个来使用
默认情况下,MyBatis 会利用 EnumTypeHandler 来把 Enum 值转换成对应的名字。
10、XML 映射配置文件(三)
插件(plugins)
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用
允许使用插件来拦截的方法调用包括
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
方法
只需实现 Interceptor 接口,并指定了想要拦截的方法签名即可
例子
拦截在 Executor 实例中所有的 “update” 方法调用
// ExamplePlugin.java @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class ExamplePlugin implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { return invocation.proceed(); } public Object plugin(Object target) { return Plugin.wrap(target, this); } public void setProperties(Properties properties) { } }
databaseIdProvider
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可: 这里的 DB_VENDOR 会通过 DatabaseMetaData#getDatabaseProductName() 返回的字符串进行设置。 由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短,如下: 在有 properties 时,DB_VENDOR databaseIdProvider 的将被设置为第一个能匹配数据库产品名称的属性键对应的值,如果没有匹配的属性将会设置为 “null”。 在这个例子中,如果 getDatabaseProductName() 返回“Oracle (DataDirect)”,databaseId 将被设置为“oracle”。 你可以通过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注册来构建自己的 DatabaseIdProvider: public interface DatabaseIdProvider { void setProperties(Properties p); String getDatabaseId(DataSource dataSource) throws SQLException; }
可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性
MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句
映射器(mappers)
概念
用于引用定义好的映射定义,告诉mybatis去哪里找我们的sql定义配置
四中方式
直接引用xml文件
通过绝对路径引用,注意在绝对路径前加上:“file:///”
引用mapper接口对象的方式
引用mapper接口包的方式
配置环境(environments)
概念
配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中
不过要记住:尽管可以配置多个环境,每个 SqlSessionFactory 实例只能选择其一。
使用场景
1)为了开发设置不同的数据库配置
2)测试和生产环境数据库不同
3)有多个数据库却共享相同的模式,即对不同的数据库使用相同的SQL映射
代码
用default指定默认的数据库链接::<environments default="oracle_jdbc">
根据数据库环境,获取SqlSessionFactory
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment,properties);