前言
学完 SpringMVC 才发现 Mybatis 的知识没有整理,由于没有经常使用,所以知识一直要忘,就先来整理一下,当做复习,以后需要查看也比较方便。
配置文件不动他,但是会把其他和 MyBatis 相关的文件整合过来。
Mybatis 介绍
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
简单的说,MyBatis 就是来接管 jdbc 的工作,省去复杂的配置。
简单入门
配置文件
使用 MyBatis 自然是离不开使用相关的 jar 包,在使用 Maven 工程的情况下,简单导入即可,不过本文使用了一些其他 jar 包为了后面的内容,所以导入的东西会比较多
1 |
|
SqlSessionFactory 以及 SqlSession
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。
但是在 SqlSessionFactory 的生成需要配置文件,所以我们先创建一个文件,在里面先完成数据库的配置
1 |
|
1 | driver=com.mysql.jdbc.Driver |
编写相关工具方法,避免每一次使用 SqlSessionFactory 都需要编写相同代码
1 | package com.jeislu.utils; |
通过获取的的 SqlSession,我们可以关联 Mapper ,然后来执行相应方法
1 |
|
当然关联的 Mapper 需要有相关的配置文件或注释,不然也无法工作,这里使用配置文件来展示一下。
先编写 UserMapper.java 文件,来配置需要使用的方法
1 | package com.jeislu.dao; |
UserMapper.java 的配置文件 UserMapper.xml ,记得去 Mybatis 的配置文件中进行映射
1 |
|
生命周期
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。
配置
属性(properties)
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。例如:
1 | <!-- 先导入配置文件 --> |
设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如:
1 | <dataSource type="POOLED"> |
如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
- 首先读取在 properties 元素体内指定的属性。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。
从 MyBatis 3.4.2 开始,你可以为占位符指定一个默认值。例如:
1 | <dataSource type="POOLED"> |
这个特性默认是关闭的。要启用这个特性,需要添加一个特定的属性来开启这个特性。例如:
1 | <properties resource="org/mybatis/example/config.properties"> |
如果你在属性名中使用了 ":" 字符(如:db:username),或者在 SQL 映射中使用了 OGNL 表达式的三元运算符(如: ${tableName != null ? tableName : 'global_constants'}),就需要设置特定的属性来修改分隔属性名和默认值的字符。例如:
1 | <properties resource="org/mybatis/example/config.properties"> |
别名(TypeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
1 | <typeAliases> |
当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
1 | <typeAliases> |
每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 com.jeislu.pojo.User 的别名为 user,若有注解,则别名为其注解值。见下面的例子:
1 |
|
环境配置(environment)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可(如果忽略了环境参数,那么将会加载默认环境)。
1 | SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment); |
environments 元素定义了如何配置环境。
1 | <environments default="development"> |
注意一些关键点:
- 默认使用的环境 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 来阻止默认的关闭行为。例如:
1
2
3<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
映射器(mappers)
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:
1 | <!-- 使用相对于类路径的资源引用 --> |
XML映射文件(XXX.mapper)
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
cache– 该命名空间的缓存配置。cache-ref– 引用其它命名空间的缓存配置。resultMap– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。parameterMap– 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。sql– 可被其它语句引用的可重用语句块。insert– 映射插入语句。update– 映射更新语句。delete– 映射删除语句。select– 映射查询语句。
这些 sql 标签没什么好说的,主要说一些标签内的配置
| 属性 | 描述 |
|---|---|
id |
在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType |
将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
resultType |
期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
resultMap |
对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。 |
timeout |
这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
resultSetType |
FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。 |
resultSets |
这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。 |
参数映射
之前见到的所有语句都使用了简单的参数形式。但实际上,参数是 MyBatis 非常强大的元素。对于大多数简单的使用场景,你都不需要使用复杂的参数,比如:
1 | <select id="selectUsers" resultType="User"> |
上面的这个示例说明了一个非常简单的命名参数映射。鉴于参数类型(parameterType)会被自动设置为 int,这个参数可以随意命名。原始类型或简单数据类型(比如 Integer 和 String)因为没有其它属性,会用它们的值来作为参数。 然而,如果传入一个复杂的对象,行为就会有点不一样了。比如:
1 | <insert id="insertUser" parameterType="User"> |
如果 User 类型的参数对象传递到了语句中,会查找 id、username 和 password 属性,然后将它们的值传入预处理语句的参数中。
结果映射
MyBatis 创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序,那就太好了,但可惜也没有。 而 ResultMap 就是 MyBatis 对这个问题的答案。
比如,我们如何映射下面这个语句?
1 | <!-- 非常复杂的语句 --> |
你可能想把它映射到一个智能的对象模型,这个对象表示了一篇博客,它由某位作者所写,有很多的博文,每篇博文有零或多条的评论和标签。 我们先来看看下面这个完整的例子,它是一个非常复杂的结果映射(假设作者,博客,博文,评论和标签都是类型别名)。 不用紧张,我们会一步一步地来说明。虽然它看起来令人望而生畏,但其实非常简单。
1 | <!-- 非常复杂的结果映射 --> |
关联
1 | <association property="author" column="blog_author_id" javaType="Author"> |
关联(association)元素处理“有一个”类型的关系。 比如,在我们的示例中,一个博客有一个用户。关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。
关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:
- 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
- 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。
| 属性 | 描述 |
|---|---|
property |
映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。 |
javaType |
一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
jdbcType |
JDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。 |
typeHandler |
我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。 |
动态SQL
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
if
例子:
1 | <select id="findActiveBlogWithTitleLike" resultType="Blog"> |
当 if 条件成立的时候,会动态的拼接上语句
choose、when、otherwise
例子:
1 | <select id="findActiveBlogLike" |
在 choose 标签里面的 when 和 otherwise 会被结合,例如 Java 的 if - else if - else 语句一样
trim、where、set
下面让我再来看一个例子
1 | <select id="findActiveBlogLike" |
如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:
1 | SELECT * FROM BLOG |
这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:
1 | SELECT * FROM BLOG |
MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:
1 | <select id="findActiveBlogLike" |
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
1 | <trim prefix="WHERE" prefixOverrides="AND |OR "> |
这里的 prefix 表示触发的条件(放在 where 后面),prefixOverrides 表示会动态删除的元素
MyBatis 的坑
1. Maven资源过滤
除了resource文件夹下的资源文件,Maven默认是不将其输出到target文件夹的,所以为了让 com.jeislu.dao 中mapper配置xml可以输入到target,需要设置Maven的pom.xml,具体设置和注意事项见代码
1 | <!-- 在bulid中的resources中添加一个新的resouce,如果没有父标签就创建 --> |
2. 连接数据库问题
百度上说 mysql 8.0+版本需要配置时区,但是我这个是5啊,不知道是IDEA还是什么的原因,还是报错了,所以乖乖加上时区配置
在 mybatis的配置XML中连接数据库那一行URL中设置Timezone
1 | <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8"/> |
在粘贴的时候才发现我没有这个问题,不过IDEA连接 mysql 的时候需要这个,也先记起来了
3. Mapper配置问题
每一个 mapper.xml 都需要在 mybatis 的配置文件中注册,如果没有注册,就会出现
org.apache.ibatis.binding.BindingException: Type interface com.jeislu.dao.UserMapper is not known to the MapperRegistry.
这个错误,在配置文件中注册就好了
4. Maven输出中文乱码的问题
这个是真的坑,浪费我好久,还以为是哪里出问题了,原来是不知道哪里的编码问题,导致 xml 文件中的字符集不是使用 UTF-8 ,然后在 target 下导致中文注释乱码
Caused by: org.xml.sax.SAXParseException; lineNumber: 5; columnNumber: 15; 1
大致的错误如上,已经解决了,所以找不到之前完整的报错了。
解决方法就是,把IDEA中所有有关 UTF 和 GBK 的设置全部设置成为 UTF-8 ,由于改了很多个,具体是哪个的设置我也不清楚,不过里面有一个是 xml 不使用 utf-8 的设置,我把√去掉了,可能是这个(也可能不是)反正把和字符集相关的都设置成为 UTF- 8 就可以了
5. 增删改操作不生效
在不配置的情况下,增删改需要手动提交事务,否则不生效。
6. MyBatis 配置顺序问题
properties标签需要放在配置XML的最上面,配置XML有顺序要求
7. Mapper 里面 class 和 resource 的区别
- Class:会绑定配置的接口和 XML 文件
- Resource:只绑定配置的 XML 文件
因此,class可能会导致一个问题,方法已经加了注释,又在 XML 中编写了对应的 SQL 语句标签,出现这种情况,会报以下错误
Error parsing SQL Mapper Configuration. Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.jeislu.dao.UserMapper.getUserList. please check com/jeislu/dao/UserMapper.xml and com/jeislu/dao/UserMapper.java (best guess)