文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。
MyBatis 的安装
org.mybatis
mybatis
x.x.x
MyBatis 的核心组件
MyBatis 主要可以分为 4 个部分
类名称 | 说明 | 生命周期 |
---|---|---|
SqlSessionFactoryBuilder(构造器) | 根据配置或代码生成 SqlSessionFactory,采用 Builder 分布构建。 | 一旦创建了 SqlSessionFactory,就不再需要。 |
SqlSessionFactory(工厂接口) | 工厂生成 SqlSession。 | 单例,一旦被创建就应该在应用的运行期间一直存在。 |
SqlSession(会话) | 发送 SQL 执行返回结果,获取 Mapper 接口。 | 每个线程都应该有它自己的 SqlSession 实例。 |
SQL Mapper(映射器) | 负责发送 SQL 执行,并返回结果。 | 映射器是创建用来绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的,与 SqlSession 生命周期一致。 |
MyBatis 的解析与运行
MyBatis 的运行过程,可以分为两步:第一步,读取配置文件缓存到 Configuration 对象,用来创建 SqlSessionFactory;第二步,执行 SqlSession。
1. 构建 SqlSessionFactory
- 通过 XMLConfigBuilder 解析配置文件,创建 Configuration 对象(单例模式),将 XML 内容放入对象中。
- 使用 Configuration 对象,创建 SqlSessionFactory。
SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
1.1 构建 Configuration 对象
- 通过 XMLConfigBuilder 解析配置文件,进行初始化。
初始化步骤名称 | 说明 |
---|---|
properties | 全局属性 |
settings | 设置 |
typeAliases | 类型别名 |
objectFactory | 对象工厂 |
plugins | 插件 |
environments | 数据库环境变量 |
dataSource | 数据源 |
databaseIdProvider | 数据库厂商标识 |
typeHandlers | 类型处理器 |
mappers | 映射器 |
1.2 映射器的内部组成
- MappedStatement 作用保存映射器节点(select|insert|delete|update)的内容。包括配置 SQL 的 id、缓存信息、resultMap、parameterType、resultType、languageDriver 等。还包含 SqlSource,用以获取某条 SQL 的配置信息。
- SqlSource 提供 BoundSql 对象,是 MappedStatement 的属性,是一个接口,主要实现有 DynamicSqlSource(动态 SQL)、ProviderSqlSource、RawSqlSource、StaticSqlSource 等。
- BoundSql 是结果对象,是由 SqlSource 对 SQL 和参数解析而获得,包含 sql、parameterObject、parameterMappings 属性。
属性名称 | 说明 |
---|---|
sql | 被 SqlSource 解析后的可执行的 SQL 语句。 |
parameterObject | 参数对象本身,传递简单参数、POJO 或者 Map、@Param 注解参数。 |
parameterMappings | 是一个List,对每个参数进行描述包括属性名称、表达式、javaType、jdbcType、typeHandler 等。 |
2. SqlSession 的运行过程
- 通过 SqlSessionFactory 获得。
- Mapper 采用了动态代理方式调用,实际执行了 SqlSession 自身的方法。
- SqlSession 的执行过程通过 Executor、StatementHandler、ParameterHandler、ResultSetHandler 完成。
对象名称 | 说明 |
---|---|
Executor | 执行器,由它调度 StatementHandler、ParameterHandler 和 ResultSetHandler 。 |
StatementHandler | 使用数据库 Statement 完成数据库操作。 |
ParameterHandler | 处理 SQL 参数。 |
ResultSetHandler | 封装数据集(ResultSet)后返回。 |
- Executor 包含三种执行器。
执行器名称 | 说明 |
---|---|
SIMPLE | 简易执行器,MyBits 默认使用。 |
REUSE | 能够执行重用预处理语句的执行器。 |
BATCH | 能执行重用语句和批量更新的执行器。 |
- 在 Executor 中,通过 Configuration 构造 StatementHandler,通过 prepareStatement 方法,对 SQL 编译和参数进行初始化。
- StatementHandler 作为数据库会话器,实际创建了 RoutingStatementHandler 对象,并且采用了适配模式,寻找对应的 StatementHandler 来执行。
适配 Handler 名称 | 说明 |
---|---|
SimpleStatementHandler | 简单处理 |
PreparedStatementHandler | 预编译处理 |
CallableStatementHandler | 存储过程处理 |
- Executor 调度 StatementHandler,执行 prepared 预编译 SQL。
- 执行 parameterize 方法,启用 ParameterHandler 设置参数。
- 执行查询或者 update 的 SQL 语句,返回通过 ResultSetHandler 封装结果。
- ParameterHandler 设置参数,是根据类型处理器 typeHandler 处理的。
- update 的 SQL 语句,返回整数,查询返回通过 typeHandler 处理结果类型,再用 ObjectFactory 提供的规则封装结果对象。
MyBatis 映射文件配置
1. properties
引用 propertis 文件并读取配置信息,可以在
标签中定义属性。 创建一个资源文件 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 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。
2. settings
- MyBatis 的调整设置,会改变 MyBatis 的运行时行为。
- 配置方式
常用设置名称 | 说明 | 默认值 |
---|---|---|
cacheEnabled | 配置影响的所有映射器中配置的缓存的全局开关 | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | false |
aggressiveLazyLoading | 开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载 | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | / |
3. typeAliases
类型别名。给java类型取一个别名,方便在核心配置、映射配置中来使用这个 java 类型。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。在 mybatis 中别名是不区分大小写的。
比较(区别看 resultType)不使用别名
- 使用别名,在核心配置文件中加上配置
- 或者注解方式,用以避免因为别名重名导致的扫描失败问题。
@Alias("user")
public class User {
...
}
在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名,若有注解,则别名为其注解值,修改 UserMapper.xml 的配置。
已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,需要注意的是由基本类型名称重复导致的特殊处理。
4. typeHandlers
- 类型处理器,无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
- 用途:1)获取数据库的值,以合适的方式转变为对应的 java 类型;2)将java类型,以合适的方式转化为数据库的保存类型。
- 自定义类型处理器,需要实现 org.apache.ibatis.type.TypeHandler 接口,或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler,然后可以选择性地将它映射到一个 JDBC 类型。
// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler {
@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 值转换成对应的名字。
5. databaseIdProvider
- 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性,MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库。
- 当 databaseIdProvider 的 type 属性被配置时,系统会优先取到和数据库配置一致的 SQL。如果没有,则取没有 databaseId 的 SQL,可以把它当作默认值。如果还是取不到,则会抛出异常,说明无法匹配到对应的 SQL。
6. 运行环境(environments)
- 配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,尽管可以配置多个环境,每个 SqlSessionFactory 实例只能选择其一。
- 应用场景:1)为了开发设置不同的数据库配置;2)测试和生产环境数据库不同;3)有多个数据库却共享相同的模式,即对不同的数据库使用相同的SQL映射。
- 用 default 指定默认的数据库链接
- 根据数据库环境,获取 SqlSessionFactory
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment,properties);
6.1 environment 中的配置
- transactionManager 事务管理器
type 取值 | 说明 |
---|---|
JDBC | 简单的使用JDBC的提交和回滚设置,一览与从数据员得到的链接来管理事务范围。 |
MANAGED | 让容器来管理事务的整个生命周期。 |
- dataSource 数据源
type 取值 | 说明 |
---|---|
UNPOOLED | 每次被请求时打开和关闭连接。速度会有一些慢,适用于简单的应用程序。 |
POOLED | JDBC 链接对象的数据源连接池的实现,用来避免创建新的链接实例时必要的连接和认证时间。适用于当前Web应用程序用来快速响应请求。 |
JNDI | 为了使用如 Spring 或应用服务器这类的容器,容器可以集中或在外部配置数据源,然后设置 JNDI 上下文的引用。 |
公用配置属性 | 说明 |
---|---|
driver | JDBC 驱动的 Java 类的完全限定名(并不是JDBC驱动中可能包含的数据源类)。 |
url | 数据库的 JDBC URL 地址。 |
username | 登录数据库的用户名。 |
password | 登录数据库的密码。 |
defaultTransactionIsolationLevel | 默认的连接事务隔离级别。 |
POOLED 扩展的配置属性 | 说明 |
---|---|
poolMaximumActiveConnections | 在任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10 |
poolMaximumIdleConnections | 任意时间可能存在的空闲连接数。 |
poolMaximumCheckoutTime | 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒) |
poolTimeToWait | 如果获取连接花费的相当长的时间,它会给连接池打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败),默认值:20000 毫秒(即 20 秒)。 |
poolPingQuery | 发送到数据库的侦测查询,用来检验连接是否处在正常工作秩序中并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动失败时带有一个恰当的错误消息。 |
poolPingEnabled | 是否启用侦测查询。若开启,也必须使用一个可执行的 SQL 语句设置。poolPingQuery 属性(最好是一个非常快的 SQL),默认值:false。 |
poolPingConnectionsNotUsedFor | 配置 poolPingQuery 的使用频度。这可以被设置成匹配具体的数据库连接超时时间,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。 |
JNDI 扩展的配置属性 | 说明 |
---|---|
initial_context | 用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么 data_source 属性将会直接从 InitialContext 中寻找。 |
data_source | 引用数据源实例位置的上下文的路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。 |
- 通过需要实现接口 org.apache.ibatis.datasource.DataSourceFactory,也可使用任何第三方数据源。
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
7. 映射器(mappers)
- 用于引用定义好的映射定义,告诉 mybatis 去哪里找我们的 sql 定义配置。
- 直接引用 xml 文件。
- 通过绝对路径引用,注意在绝对路径前加上:“file:///”。
- 引用 mapper 接口对象的方式。
- 引用 mapper 接口包的方式。
MyBatis 的 mapper XML 文件配置
1. select 标签(映射查询语句)
属性 | 说明 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句,必选 |
parameterType | 将会传入这条语句的参数类的完全限定名或别名,可选 |
parameterMap | 这是引用外部 parameterMap 的已经被废弃的方法。使用内联参数映射和 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 | 设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。 |
databaseId | 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
2. insert、update 标签
- 除具备 id、parameterType、parameterMap 、flushCache、timeout、statementType、databaseId 属性外,还具备以下属性。
属性 | 说明 |
---|---|
useGeneratedKeys | 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。 |
keyProperty | 唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
keyColumn | 通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
通常主键的生成都是让数据库自动生成的,比如 mysql 中主键设置 auto_increment,主流的数据库一般都支持。
数据库支持自动生成主键,可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上。
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
- 数据库不支持自动生成主键,在 insert 中使用 selectKey 语句。随机生成一个 ID做为主键,selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用。这给你了一个和数据库中来处理自动生成的主键类似的行为,避免了使 Java 代码变得复杂。
3. sql 标签
- 这个标签可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。它可以被静态地(在加载参数) 参数化. 不同的属性值通过包含的实例变化。
- 属性值可以用于包含的refid属性或者包含的字句里面的属性值。
${alias}.id,${alias}.username,${alias}.password
4. resultMap 标签
一级标签 | 二级标签 | 三级标签 | 说明 |
---|---|---|---|
constructor | 类在实例化时,用来注入结果到构造方法中。 | ||
idArg | ID 参数,标记结果作为 ID 可以帮助提高整体效能。 | ||
arg | 注入到构造方法的一个普通结果。 | ||
id | 一个 ID 结果,标记结果作为 ID 可以帮助提高整体效能。 | ||
result | 注入到字段或 JavaBean 属性的普通结果。 | ||
association | 一个复杂的类型关联,许多结果将包成这种类型。 | ||
嵌入结果映射 | 结果映射自身的关联,或者参考一个。 | ||
collection | 复杂类型的集 | ||
嵌入结果映射 | 结果映射自身的集,或者参考一个。 | ||
discriminator | 使用结果值来决定使用哪个结果映射 | ||
case | 基于某些值的结果映射 | ||
嵌入结果映射 | 这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射。 |
4.1 discriminator(鉴别器)
- 有时一个单独的数据库查询也许返回很多不同 (但是希望有些关联) 数据类型的结果集,表现很像 Java 语言中的 switch 语句
5. cache(缓存)标签
- 要开启二级缓存,需要在 SQL 映射文件中添加一行
,效果如下。 - 映射语句文件中的所有 select 语句将会被缓存。
- 映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
- 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
- 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
- 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
- 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,默认的是 LRU。
收回策略 | 说明 |
---|---|
LRU | 最近最少使用的,移除最长时间不被使用的对象。 |
FIFO | 先进先出,按对象进入缓存的顺序来移除它们。 |
SOFT | 软引用,移除基于垃圾回收器状态和软引用规则的对象。 |
WEAK | 弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象 |
6. 动态 SQL
6.1 if 标签
- 做条件判断的,如果我们不使用这个标签,我们肯定会在代码中判断如查询的元素是否为空,传入的元素是否为空,而这时我们直接使用这个标签,就减少了代码的书写。
- 动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。
6.2 choose (when, otherwise) 标签
- 采用多个选项中找一个,就像单项选择题,但是你不会都选择,只会从中选择1个来作为条件。就有点类似于switch。。case。
6.3 trim (where, set) 标签
- 如果使用第一个if语句的话,就会发现没有写 where 标签就会报错。而这类标签通常是搭配条件标签使用的。where 一般可以用 1=1 来解决。
6.4 foreach 标签
- 用于循环,用于遍历,如果我们传入的参数是一个数组或者集合类,那么这个标签可以循环遍历。一般我们都是使用 sql 中的 in 语句时才使用。
- 可以将任何可迭代对象(如列表、集合等)和任何的字典或者数组对象传递给 foreach 作为集合参数
- 当使用可迭代对象或者数组时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。
7 #{} 和 ${} 的区别
7.1 #{}
- 使用 #{} 格式的语法在 mybatis 中使用 Preparement 语句来安全的设置值。
- # 方式能够很大程度防止 sql 注入。
7.2 ${}
- 只是想直接在 SQL 语句中插入一个不改变的字符串。
- $ 将传入的数据直接显示生成在 sql 中。
- 使用 $ 要么不允许用户输入这些字段,要么自行转义并检验。
- 一般能用 # 的就别用 $。
参考资料
http://www.mybatis.org/mybatis-3/zh/index.html
http://www.broadview.com.cn/book/80
http://www.jianshu.com/p/06b73e8d9f56
http://www.jianshu.com/p/8867e21655da