# mybatis-study
**Repository Path**: zhenghun/mybatis-study
## Basic Information
- **Project Name**: mybatis-study
- **Description**: 用于mybatis学些
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2018-08-19
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 说明
该学习内容主要参考mybatis官方文档及《深入浅出MyBatis技术原理与实战》
# 1.简介
Mybatis的核心组件:
- SqlSessionFactoryBuilder: 他会根据配置信息或代码类来生成SqlSessionFactory
- SqlSessionFactory:根据工厂来生成SqlSession会话
- SqlSession:既可以发送SQL去执行并返回结果,可以获取Mapper的接口。
- SQL Mapper:它是Mybatis新设计的组件。由java接口和XML配置文件(或注解)构成。需要给定对应的SQL和映射规则。负责发送SQL去执行并返回结果。
- Configuration:配置类,用于保存配置信息,方便后面使用,XML配置文件信息将由Configuration承载。也就是说,当SqlSessionFactoryBuilder通过xml配置文件构建SqlSessionFactory时会将XML配置文件信息装载到Configuration中。
## 1.1.构建SqlSessionFactory
SqlSessionFactory是一个接口,由两个实现类DefaultSqlSessionFactory和SqlSessionManager,常用的是DefaultSqlSessionFactory。

从上图可以看出,Configuration类是怎个系统的核心内容,所有核心类都围绕着它进行业务逻辑的处理。
### 1.1.1.通过XML方式构建SqlSessionFactory
在mybatis-x.x.x.jar的org.apache.ibatis.builder.xml文件夹中定义了mybatis的config和mapper元数据格式dtd文件。从文件中可以了解到配置文件具体内容。
```java
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.builder(in);
```
# 2.配置文件
```text
configuration (
properties?, //属性
settings?, //设置
typeAliases?, //类型别名
typeHandlers?, //类型拦截器
objectFactory?,
objectWrapperFactory?,
reflectorFactory?,
plugins?,
environments?,
databaseIdProvider?,
mappers?
)
```
从上面描述的dtd文件中可以看出,configuration具有这些子节点。接下来将一一解释作用。
## 2.1.properties
properties是一个配置属性的 元素,让我们能够在配置文件的上下文中使用它。
mybatis属性有三个来源:
- 在config文件中使用properties节点配置的内容
- 外部properties属性文件进行配置的内容
- 方法参数传入properties内容
```xml
```
上面例子中 username 和 password 来自properties节点配置的内容,driver和url来自 properties的resource属性指定的外部properties文件中的url和driver属性内容。
属性也可以被传递到 SqlSessionFactoryBuilder.build()方法中。例如:
```java
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
// ... or ...
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);
```
这种方式的应用场景应该是某些内容是被加密了的,需要在程序中进行解密,如数据库用户名和密码被加密了,需要程序解密之后再来创建SqlSessionFactory。
````java
InputStream configStream = Resources.getResourceAsStream("mybatis-config.xml");
InputStream jdbcStream = Resources.getResourceAsStream("jdbc.properties");
Properties pro = new Properties();
pro.load(jdbcStream);
pro.setProperty("username", decode(pro.getProperty("username")));
pro.setProperty("password", decode(pro.getProperty("password")));
synchronized(CLASS_LOCK){
if(sqlSessionFactory == null){
sqlSessionFactory = new SqlSessionFactoryBuilder().builder(configStream, pro);
}
}
````
如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:
- 在config配置文件中配置的 properties 元素体内指定的属性首先被读取。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。
因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的是 properties 属性中指定的属性。
从MyBatis 3.4.2开始,你可以为占位符指定一个默认值。例如:
```xml
```
这个特性默认是关闭的。如果你想为占位符指定一个默认值, 你应该添加一个指定的属性来开启这个特性。例如:
```xml
```
**你可以使用 ":" 作为属性键(e.g. db:username) 或者你也可以在sql定义中使用 OGNL 表达式的三元运算符(e.g. ${tableName != null ? tableName : 'global_constants'}), 你应该通过增加一个指定的属性来改变分隔键和默认值的字符。例如:**
```xml
```
## 2.2.setting
完整配置:
```xml
```
## 2.3.typeAliases
别名是一个指代的名称。因为我们遇到的全限定名称过长,所以我们希望使用一个简短的名称来指代它,这个别名可以在mybatis上下文中使用。别名分为系统别名和自定义别名两种。**别名不区分大小写** 。
### 2.3.1.系统别名
别名|映射的类型|支持数组
--|--|--
_byte|byte|是
_long|long|是
_short|short|是
_int|int|是
_integer|int|是
_double|double|是
_float|float|是
_boolean|boolean|是
string|String|否
byte|Byte|是
long|Long|是
short|Short|是
int|Integer|是
integer|Integer|是
double|Double|是
float|Float|是
boolean|Boolean|是
date|Date|是
decimal|BigDecimal|是
bigdecimal|BigDecimal|是
object|Object|是
map|Map|否
hashmap|HashMap|否
list|List|否
arraylist|ArrayList|否
collection|Collection|否
iterator|Iterator|否
这些信息可以从类 TypeAliasRegistry 中找到。
### 2.3.2.自定义别名
可以通过xml配置文件的方式进行配置(**目前测试不论设置alias为什么,最终只能取到类名小写的结果**):
```xml
```
如果我们系统中有很多java bean,这样配置实在过于繁琐,因此我们还有另外两种方式来实现,一种是设置包,一种是注解:
MyBatis 会在包名下面搜索需要的 Java Bean,比如:
```xml
```
每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;在有注解的情况下别名就是注解的值(测试好像没有生效)。
```java
@Alias("author")
public class Author {
...
}
```
## 2.4.typeHandlers
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。 和typeAliases一样,typeHandlers也分为系统定义的和用户自定义两种。
### 2.4.1.系统定义
可以在TypeHandlerRegistry类中看到所有系统定义的解析器。
### 2.4.2.用于自定义
要实现自定义类型拦截器需要两步:
1. 自定义拦截器
> 你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个 JDBC 类型。
2. 在配置文件中申明该自定义拦截器
> 有两种方式,一种是一个一个拦截器声明,一个是声明包。测试时发现,即便不写jdbcType和javaType,mybatis自动调用了自定义的handler拦截器
```xml
```
2. 去标识哪些参数或结果类型去用自定义拦截器进行转换,在没有任何标识情况下,MyBatis是不会启用你定义的拦截器进行转化的(前提未全局配置拦截器,也就是mabatis的congfiguration中没有配置typeHandlers)。在字段上引用该拦截器,只有引用了mybatis才知道怎么处理,否则会使用默认的拦截器进行处理。
> 有两种方式:
```xml
insert into users2 (id, name, funkyNumber, roundingMode) values (
#{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
)
```
### 2.4.3.总结
根据测试结果,要启用自定义拦截器有两种方式,一种是全局配置(在configuration节点中配置typeHandlers),另一种是在要转化的字段上直接通过typeHandler属性来引用自定义拦截器。
对于全局配置,可以不用指定javaType和jdbcType。mybatis会自动根据类型来调用自定义handler,需要注意的是自定义handler是继承自BaseTypeHandler,如果实现TypeHandler接口则无效(自测结果,未跟踪代码,可能有误,测试版本是3.4.6)这点和书中描述不一致。
## 2.5.ObjectFactory
Mybatis在构建结果集的时候,都会使用ObjectFactory去构建POJO,在MyBatis中可以实现自定义的ObjectFactory。在MyBatis中默认的对象工厂是DefaultObjectFactory。
主要是通过集成DefaultObjectFactory类或实现ObjectFactory对象工厂类,并在configuration中通过objectFactory申明即可。
## 2.6.environments配置
配置环境可以注册多个数据源(dataSource),每个数据源分为两大部分:
- 数据源配置
- 事务配置
```xml
```
通过上面配置文件来对各个属性进行说明:
- environments 的default元素说明,我们默认使用哪一个数据源配置(一个environment是一个数据源配置,可以有多个)
- environment 用于配置数据源,可以配置多个,id作为唯一标识,可以在mybatis上下文中通过该id获取或使用数据源
- transactionManager 配置数据库事务,type有三种配置方式:
- JDBC :采用JDBC方式管理事务,在独立编码中常常使用
- MANAGED :采用容器方式管理,在JNDI数据源中常使用
- 自定义:由开发者自行定义数据库事务管理方法,适用于特殊应用
- transactionManager的property 元素则是可以配置数据源的各类属性,如 autoCommit = false,要求数据源不自动提交
- dataSource 配置数据源连接信息,type指定连接方式,提供了如下方式:
- UNPOOLED :非连接池数据库(UnpooledDataSource)
- POOLED :连接池数据库(PooledDataSource)
- JNDI :JNDI数据源(JNDIDataSource)
- 自定义数据源
- dataSource中property:定义数据库各种参数
### 2.6.1.事务
mybatis将事务交由SqlSession来控制,可以通过SqlSession来控制事务的提交或回滚:
```java
try{
sqlSession.commit();
}catch(Exception e){
sqlSession.rollback();
} finally{
sqlSession.close();
}
```
大部分情况下将交由spring来控制事务。
### 2.6.2.自定义数据源
需要实现DataSourceFactory接口,如想使用DBCP数据源,这时就需要自定义数据源:
```java
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.apache.ibatis.datasource.DataSourceFactory;
public class DbcpDataSourceFactory extends BasicDataSource implements DataSourceFactory{
private Properties props = null;
@Override
public void setProperties(Properties props) {
this.props = props;
}
@Override
public DataSource getDataSource(){
DataSource dataSource = null;
try{
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch(Exception ex){
}
return dataSource;
}
}
```
使用DBCP数据源需要使用一个类去配置它,然后在dataSource标签中这样应用即可:
```xml
...
```
## 2.7.映入 mapper 的方式
1. 使用文件路径引入
```xml
```
2. 用包名引入
```xml
```
3. 用类注册引入映射器
```xml
```
4. 使用文件系统路径引入
```xml
```
## 3.mapper
### 3.1.select
### 3.2.insert
### 3.3.update
### 3.4.delete
### 3.5.sql
### 3.6.resultMap
#### 3.6.1.结构
```xml
```
#### 3.6.2.constructor
#### 3.6.3.id
#### 3.6.4.result
#### 3.6.5.association
用于一对一关系
- properties :
- column :指定传递给select语句的参数,一般是当前对象的id字段,如果有多个参数,那么column的值用逗号分隔
#### 3.6.6.collection
用于一对多关系
#### 3.6.7.discriminator
鉴别器
#### 3.6.8.N+1问题
使用懒加载解决
```xml
```
当一个对象有多个子关联对象时,一旦调用其中一个子关联,另外的子关联也会被查询,这也不符合我们想要的,我们想要的是访问那个子关联就查询哪个子关联,可以通过下面的参数实现:
```xml
```
false:调用哪个子关联就查询哪个子关联(调用要求加载);true:调用一个子关联将同级子关联一并查出。默认为true(层级加载策略)
上面的是全局配置,这样就不够灵活,有时候我们可能需要立即加载,这样被限制住了。为了实现灵活性,mybatis在association和collection上提供了fetchType属性。
fetchType有两个属性:
- eager:立即加载
- lazy:懒加载
**一旦配置了fetchType,全局配置就会被覆盖**
延迟加载使用动态代理实现,mybatis3.3以上使用JAVASSIST实现,低版本使用CGLIB实现。
## 4.缓存
### 4.1.一级缓存
在默认配置情况下mybatis只提供一级缓存(**一级缓存只是相对于同一个SqlSession**而言)
在同一个SqlSession中,使用同样条件进行查询时,SqlSession会使用缓存(第一次相同条件查询时放入缓存),前提是缓存未过期,且未指定需要刷新。
### 4.2.二级缓存
SqlSessionFactory中存放二级缓存,默认关闭,需要进行配置。实现二级缓存时,mybatis要求缓存的POJO可以序列化。配置方法很简单:
```xml
```
只配置这一句意味着:
- 所有select都将被缓存
- 所有insert update delete会刷新缓存
- 使用LRU进行回收
- 不刷新
- 缓存可read/write
- 存储1024个缓存对象
```xml
```
- eviction:淘汰策略
- LRU
- FIFO
- SOFT :软引用,由垃圾回收器回收
- WEAK :弱引用,由垃圾回收器回收
- flushInterval:刷新时间间隔,单位毫秒,如不配置那么只有在sql执行时才会刷新
- size :存储个数
- readOnly :只读,不可修改,默认是false
### 4.3.自定义缓存
需要实现org.apache.ibatis.cache.Cache接口
配置使用自定义接口
```xml
```
我们在映射器上可以配置insert update delete select元素。我们可以配置SQL层面的缓存规则,来决定 他们是否需要使用或者刷新缓存。使用useCache和flushCache来完成:
```xml
```