Netty,IO模型

pull/28/head
AmyliaY 5 years ago
parent 49b7bef419
commit 57fa2deb8f

@ -83,11 +83,29 @@
## Netty
### IO
- [把被说烂的 BIO、NIO、AIO 再从头到尾扯一遍](docs/Netty/IO/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md)
### 网络 IO 技术基础
- [把被说烂的 BIO、NIO、AIO 再从头到尾扯一遍](docs/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md)
- [IO模型](docs/Netty/IOTechnologyBase/IO模型.md)
- [四种IO编程及对比](docs/Netty/IOTechnologyBase/四种IO编程及对比.md)
### 设计原理
- 努力编写中...
### Netty NIO开发指南
- [TCP粘包/拆包问题的解决之道]()
- [分隔符解码器 和 定长解码器]()
### Netty 编解码开发指南
- [编解码技术]()
### Netty 多协议开发和应用
- [WebSocket 协议开发]()
### Netty 源码分析
- [Channel和Unsafe组件]()
- [ChannelPipeline和ChannelHandler组件]()
- [EventLoop和EventLoopGroup组件]()
### Netty高级特性
- [Java多线程编程在Netty中的应用]()
- [Netty的高性能之道]()
## Redis
- 努力编写中...

@ -1,51 +1,51 @@
在Mybatis的基础支持层主要看一下支撑ORM实现的底层代码。
Mybatis 的基础支持层主要看一下支撑 ORM实现 的底层代码。
## 1 反射工具包
### 1.1Reflector
Reflector类主要实现了对JavaBean的元数据属性的封装比如可读属性列表可写属性列表及反射操作的封装属性对应的setter方法getter方法的反射调用。源码实现如下
Reflector类 主要实现了对 JavaBean 的元数据属性的封装,比如:可读属性列表,可写属性列表;及反射操作的封装,如:属性对应的 setter方法getter方法 的反射调用。源码实现如下:
```java
public class Reflector {
/** JavaBean的Class类型在调用Reflector的构造方法时初始化该值 */
/** JavaBean Class类型在调用 Reflector 的构造方法时初始化该值 */
private final Class<?> type;
/** 可读的属性列表 */
private final String[] readablePropertyNames;
private final String[] writablePropertyNames;
/** key属性名value该属性名对应的setter方法调用器 */
/** key 属性名value 该属性名对应的 setter方法调用器 */
private final Map<String, Invoker> setMethods = new HashMap<>();
private final Map<String, Invoker> getMethods = new HashMap<>();
/** key属性名称value该属性setter方法的返回值类型 */
/** key 属性名称value 该属性 setter方法的返回值类型 */
private final Map<String, Class<?>> setTypes = new HashMap<>();
private final Map<String, Class<?>> getTypes = new HashMap<>();
/** type的默认构造方法 */
/** type 的默认构造方法 */
private Constructor<?> defaultConstructor;
/** 所有属性名称的集合 */
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
/**
* 里面的大部分方法都是通过简单的JDK反射操作实现的
* 里面的大部分方法都是通过简单的 JDK反射操作 实现的
* @param clazz
*/
public Reflector(Class<?> clazz) {
type = clazz;
addDefaultConstructor(clazz);
// 处理clazz中的所有getter方法填充getMethods集合和getTypes集合
// 处理 clazz 中的 所有getter方法填充 getMethods集合 getTypes集合
addGetMethods(clazz);
addSetMethods(clazz);
// 处理没有getter、setter方法的字段
// 处理没有 getter、setter方法 的字段
addFields(clazz);
// 根据getMethods、setMethods集合初始化可读、可写的属性
// 根据 getMethods、setMethods集合 初始化可读、可写的属性
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
// 初始化caseInsensitivePropertyMap集合key属性名的大写value属性名
// 初始化 caseInsensitivePropertyMap集合key 属性名的大写value 属性名
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
@ -56,7 +56,7 @@ public class Reflector {
}
```
### 1.2 ReflectorFactory
顾名思义Reflector的工厂模式跟大部分工厂类一样里面肯定有通过标识获取对象的方法。类的设计也遵照了 接口,实现类的模式,虽然本接口只有一个默认实现。
顾名思义Reflector 的工厂模式,跟大部分工厂类一样,里面肯定有通过标识获取对象的方法。类的设计也遵照了 接口,实现类的模式,虽然本接口只有一个默认实现。
```java
public interface ReflectorFactory {
@ -65,7 +65,7 @@ public interface ReflectorFactory {
void setClassCacheEnabled(boolean classCacheEnabled);
/**
* 主要看一下这个方法通过JavaBean的clazz获取该JavaBean对应的Reflector
* 主要看一下这个方法,通过 JavaBean clazz 获取该 JavaBean 对应的 Reflector
*/
Reflector findForClass(Class<?> type);
}
@ -77,7 +77,7 @@ public class DefaultReflectorFactory implements ReflectorFactory {
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
/**
* 实例化一个ConcurrentMap全局变量然后暴露一个方法从map中获取目标对象这种设计是很多框架都会用的
* 实例化一个 ConcurrentMap全局变量然后暴露一个方法从 map 中获取目标对象,这种设计是很多框架都会用的
*/
@Override
public Reflector findForClass(Class<?> type) {
@ -104,14 +104,14 @@ public class DefaultReflectorFactory implements ReflectorFactory {
}
/**
* 支持定制化ReflectorFactory
* 支持定制化 ReflectorFactory
*/
public class CustomReflectorFactory extends DefaultReflectorFactory {
}
```
### 1.3 ObjectFactory
该类也是接口+一个默认实现类并且支持自定义扩展mybatis中有很多这样的设计方式。
该类也是接口加一个默认实现类并且支持自定义扩展Mybatis 中有很多这样的设计方式。
```java
/**
* MyBatis uses an ObjectFactory to create all needed new Objects.
@ -144,8 +144,8 @@ public interface ObjectFactory {
}
/**
* ObjectFactory接口的唯一直接实现反射工厂根据传入的参数列表选择
* 合适的构造函数实例化对象,不传参数,则直接调用其午餐构造方法
* ObjectFactory接口 的唯一直接实现,反射工厂,根据传入的参数列表,选择
* 合适的构造函数实例化对象,不传参数,则直接调用其无参构造方法
*/
public class DefaultObjectFactory implements ObjectFactory, Serializable {
@ -165,7 +165,7 @@ public class DefaultObjectFactory implements ObjectFactory, Serializable {
}
/**
* 通过反射来实例化给定的类如果调用无参构造方法则直接constructor.newInstance()
* 通过反射来实例化给定的类,如果调用无参构造方法,则直接 constructor.newInstance()
* 如果有参,则根据参数类型和参数值进行调用
*/
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
@ -206,12 +206,12 @@ public class DefaultObjectFactory implements ObjectFactory, Serializable {
}
```
## 2 类型转换
类型转换是实现ORM的重要一环由于 数据库中的数据类型与Java语言的数据类型并不对等所以在PrepareStatement为sql语句绑定参数时需要从Java类型转换成JDBC类型而从结果集获取数据时又要将JDBC类型转换成Java类型mybatis使用TypeHandler完成了上述的双向转换。
类型转换是实现 ORM 的重要一环,由于数据库中的数据类型与 Java语言 的数据类型并不对等,所以在 PrepareStatement sql语句 绑定参数时,需要从 Java类型 转换成 JDBC类型而从结果集获取数据时又要将 JDBC类型 转换成 Java类型Mybatis 使用 TypeHandler 完成了上述的双向转换。
### 2.1 JdbcType
mybatis通过JdbcType这个枚举类型代表了JDBC中的数据类型
Mybatis 通过 JdbcType 这个枚举类型代表了 JDBC 中的数据类型。
```java
/**
* 该枚举类描述了JDBC中的数据类型
* 该枚举类描述了 JDBC 中的数据类型
*/
public enum JdbcType {
/*
@ -262,7 +262,7 @@ public enum JdbcType {
public final int TYPE_CODE;
/** 该静态集合维护了 常量编码 与 JdbcType之间的关系 */
/** 该静态集合维护了 常量编码 与 JdbcType 之间的关系 */
private static Map<Integer,JdbcType> codeLookup = new HashMap<>();
static {
@ -282,14 +282,14 @@ public enum JdbcType {
}
```
### 2.2 TypeHandler
TypeHandler是mybatis中所有类型转换器的顶层接口,主要用于实现 数据从Java类型到JdbcType类型的相互转换。
TypeHandler 是 Mybatis 中所有类型转换器的顶层接口,主要用于实现数据从 Java类型 JdbcType类型 的相互转换。
```java
public interface TypeHandler<T> {
/** 通过PreparedStatement为SQL语句绑定参数时将数据从Java类型转换为JDBC类型 */
/** 通过 PreparedStatement SQL语句 绑定参数时,将数据从 Java类型 转换为 JDBC类型 */
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/** 从结果集获取数据时将数据由JDBC类型转换成Java类型 */
/** 从结果集获取数据时,将数据由 JDBC类型 转换成 Java类型 */
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
@ -299,7 +299,7 @@ public interface TypeHandler<T> {
}
/**
* 可用于实现自定义的TypeHandler
* 可用于实现自定义的 TypeHandler
*/
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
@ -344,13 +344,13 @@ public abstract class BaseTypeHandler<T> extends TypeReference<T> implements Typ
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
/**
1. NonNull就是NoneNull非空的意思
* NonNull 就是 NoneNull非空的意思
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
// IntegerTypeHandler就调用PreparedStatement的setInt方法
// BooleanTypeHandler就调用PreparedStatement的setBoolean方法
// IntegerTypeHandler 就调用 PreparedStatement setInt()方法
// BooleanTypeHandler 就调用 PreparedStatement setBoolean()方法
// 其它的基本数据类型,以此类推
ps.setInt(i, parameter);
}
@ -377,29 +377,29 @@ public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
}
}
```
TypeHandler主要用于单个参数的类型转换如果要将多个列的值转换成一个Java对象可以在映射文件中定义合适的映射规则&lt;resultMap&gt; 完成映射。
TypeHandler 主要用于单个参数的类型转换,如果要将多个列的值转换成一个 Java对象可以在映射文件中定义合适的映射规则 &lt;resultMap&gt; 完成映射。
### 2.3 TypeHandlerRegistry
TypeHandlerRegistry主要负责管理所有已知的TypeHandlermybatis在初始化过程中会为所有已知的TypeHandler创建对象并注册到TypeHandlerRegistry。
TypeHandlerRegistry 主要负责管理所有已知的 TypeHandlerMybatis 在初始化过程中会为所有已知的 TypeHandler 创建对象,并注册到 TypeHandlerRegistry。
```java
//TypeHandlerRegistry中的核心字段
// TypeHandlerRegistry 中的核心字段如下
/** 该集合主要用于从结果集读取数据时将数据从JDBC类型转换成Java类型 */
/** 该集合主要用于从结果集读取数据时,将数据从 JDBC类型 转换成 Java类型 */
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
/**
* 记录了Java类型向指定JdbcType转换时需要使用的TypeHandler对象。
* 如String可能转换成数据库的char、varchar等多种类型所以存在一对多的关系
* 记录了 Java类型 向指定 JdbcType 转换时,需要使用的 TypeHandler对象。
* 如String 可能转换成数据库的 char、varchar 等多种类型,所以存在一对多的关系
*/
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
/** key TypeHandler的类型value 该TypeHandler类型对应的TypeHandler对象 */
/** keyTypeHandler 的类型value该 TypeHandler类型 对应的 TypeHandler对象 */
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
```
1. **注册TypeHandler对象**
TypeHandlerRegistry中的register()方法实现了注册TypeHandler对象的功能该方法存在多种重载但大多数register()方法最终都会走register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler)的处理逻辑该重载方法中分别指定了TypeHandler能够处理的Java类型、JDBC类型、TypeHandler对象。
**1、注册TypeHandler对象**
TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler对象 的功能,该方法存在多种重载,但大多数 register()方法 最终都会走 register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) 的处理逻辑,该重载方法中分别指定了 TypeHandler 能够处理的 Java类型、JDBC类型、TypeHandler对象。
```java
/**
* TypeHandlerRegistry中对register方法实现了多种重载本register方法
* TypeHandlerRegistry 中对 register()方法 实现了多种重载,本 register()方法
* 被很多重载方法调用,用来完成注册功能。
*/
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
@ -414,14 +414,14 @@ TypeHandlerRegistry中的register()方法实现了注册TypeHandler对象的功
allTypeHandlersMap.put(handler.getClass(), handler);
}
```
另外TypeHandlerRegistry还提供了扫描并注册指定包目录下TypeHandler实现类 的register()方法重载。
另外TypeHandlerRegistry 还提供了扫描并注册指定包目录下 TypeHandler实现类 的 register()方法 重载。
```java
/**
* 从指定包名packageName中获取自定义的TypeHandler实现类
* 从指定 包名packageName 中获取自定义的 TypeHandler实现类
*/
public void register(String packageName) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 查找指定包下的TypeHandler接口实现类
// 查找指定包下的 TypeHandler接口实现类
resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
for (Class<?> type : handlerSet) {
@ -432,14 +432,14 @@ TypeHandlerRegistry中的register()方法实现了注册TypeHandler对象的功
}
}
```
最后看一下TypeHandlerRegistry的构造方法其通过多种register()方法重载完成了所有已知的TypeHandler的重载。
最后看一下 TypeHandlerRegistry 的构造方法,其通过多种 register()方法 重载,完成了所有已知的 TypeHandler 的重载。
```java
/**
* 进行Java及JDBC基本数据类型的TypeHandler注册
* 除了注册mybatis提供的基本TypeHandler外我们也可以添加自定义的TypeHandler
* 接口实现在mybatis-config.xml配置文件中<typeHandlers>节点 添加相应的
* <typeHandlers>节点配置,并指定自定义的TypeHandler实现类。mybatis在初始化时
* 会解析该节点,并将TypeHandler类型的对象注册到TypeHandlerRegistry中 供mybatis后续使用
* 进行 Java JDBC基本数据类型 TypeHandler 注册
* 除了注册 Mybatis 提供的 基本TypeHandler 外,我们也可以添加自定义的 TypeHandler
* 接口实现,在 mybatis-config.xml配置文件 <typeHandlers>节点 添加相应的
* <typeHandlers>节点配置,并指定自定义的 TypeHandler实现类。Mybatis 在初始化时
* 会解析该节点,并将 TypeHandler类型 的对象注册到 TypeHandlerRegistry 中供 Mybatis 后续使用
*/
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
@ -515,20 +515,20 @@ TypeHandlerRegistry中的register()方法实现了注册TypeHandler对象的功
register(JapaneseDate.class, new JapaneseDateTypeHandler());
}
```
2. **查找TypeHandler**
TypeHandlerRegistry其实就是一个容器前面注册了一堆东西也就是为了方便获取其对应的方法为getTypeHandler(),也存在多种重载其中最重要的一个重载为getTypeHandler(Type type, JdbcType jdbcType)它会根据指定的Java类型和JdbcType类型查找相应的TypeHandler对象。
**2、查找TypeHandler**
TypeHandlerRegistry 其实就是一个容器,前面注册了一堆东西,也就是为了方便获取,其对应的方法为 getTypeHandler()该方法也存在多种重载,其中最重要的一个重载为 getTypeHandler(Type type, JdbcType jdbcType),它会根据指定的 Java类型 JdbcType类型 查找相应的 TypeHandler对象。
```java
/**
* 获取TypeHandler对象
* getTypeHandler方法亦存在多种重载而本重载方法被其它多个重载方法调用
* 获取 TypeHandler对象
* getTypeHandler()方法 亦存在多种重载,而本重载方法被其它多个重载方法调用
*/
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
return null;
}
// Java数据类型与JDBC数据类型的关系往往是一对多
// 所以一般会先根据Java数据类型获取Map<JdbcType, TypeHandler<?>>
// 再根据JDBC数据类型获取对应的TypeHandler
// Java数据类型 JDBC数据类型 的关系往往是一对多,
// 所以一般会先根据 Java数据类型 获取 Map<JdbcType, TypeHandler<?>>对象
// 再根据 JDBC数据类型 获取对应的 TypeHandler对象
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null) {
@ -545,4 +545,4 @@ TypeHandlerRegistry其实就是一个容器前面注册了一堆东西
return (TypeHandler<T>) handler;
}
```
除了mabatis本身自带的TypeHandler实现我们还可以添加自定义的TypeHandler实现类在配置文件mybatis-config.xml中的&lt;typeHandler&gt;标签下配置好自定义TypeHandlermybatis就会在初始化时解析该标签内容完成自定义TypeHandler的注册。
除了 Mabatis 本身自带的 TypeHandler实现我们还可以添加自定义的 TypeHandler实现类在配置文件 mybatis-config.xml 中的 &lt;typeHandler&gt; 标签下配置好 自定义TypeHandlerMybatis 就会在初始化时解析该标签内容,完成 自定义TypeHandler 的注册。

@ -1,15 +1,15 @@
在数据持久层,数据源和事务是两个非常重要的组件,对数据持久层的影响很大,在实际开发中,一般会使用mybatis集成第三方数据源组件c3p0、Druid另外mybatis也提供了自己的数据库连接池实现本文会通过mybatis的源码实现来了解数据库连接池的设计。而事务方面一般使用spring进行事务的管理这里不做详细分析。下面我们看一下mybatis是如何对这两部分进行封装的。
在数据持久层,数据源和事务是两个非常重要的组件,对数据持久层的影响很大,在实际开发中,一般会使用 Mybatis 集成第三方数据源组件c3p0、Druid另外Mybatis 也提供了自己的数据库连接池实现,本文会通过 Mybatis 的源码实现来了解数据库连接池的设计。而事务方面,一般使用 Spring 进行事务的管理,这里不做详细分析。下面我们看一下 Mybatis 是如何对这两部分进行封装的。
## 1 DataSource
常见的数据源都会实现javax.sql.DataSource接口mybatis中提供了两个该接口的实现类分别是PooledDataSource和UnpooledDataSource并使用不同的工厂类分别管理这两个类的对象。
常见的数据源都会实现 javax.sql.DataSource接口Mybatis 中提供了两个该接口的实现类分别是PooledDataSource UnpooledDataSource并使用不同的工厂类分别管理这两个类的对象。
### 1.1 DataSourceFactory
DataSourceFactory系列类的设计比较简单DataSourceFactory作为顶级接口UnpooledDataSourceFactory实现了该接口PooledDataSourceFactory又继承了UnpooledDataSourceFactory。
DataSourceFactory系列类 的设计比较简单DataSourceFactory 作为顶级接口UnpooledDataSourceFactory 实现了该接口PooledDataSourceFactory 又继承了 UnpooledDataSourceFactory。
```java
public interface DataSourceFactory {
// 设置DataSource的属性一般紧跟在DataSource初始化之后
// 设置 DataSource 的属性,一般紧跟在 DataSource 初始化之后
void setProperties(Properties props);
// 获取DataSource对象
// 获取 DataSource对象
DataSource getDataSource();
}
@ -21,7 +21,7 @@ public class UnpooledDataSourceFactory implements DataSourceFactory {
protected DataSource dataSource;
// 在实例化该工厂时就完成了DataSource的实例化
// 在实例化该工厂时,就完成了 DataSource 的实例化
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
@ -29,13 +29,13 @@ public class UnpooledDataSourceFactory implements DataSourceFactory {
@Override
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
// 创建dataSource对应的MetaObject
// 创建 dataSource 对应的 MetaObject
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
// 处理properties中配置的数据源信息
// 处理 properties 中配置的数据源信息
for (Object key : properties.keySet()) {
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
// 以"driver."开头的配置项是对DataSource的配置将其记录到driverProperties中
// 以 "driver." 开头的配置项是对 DataSource 的配置,将其记录到 driverProperties
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
@ -47,8 +47,8 @@ public class UnpooledDataSourceFactory implements DataSourceFactory {
}
}
if (driverProperties.size() > 0) {
// 设置数据源UnpooledDataSource的driverProperties属性
// PooledDataSource中持有UnpooledDataSource对象
// 设置数据源 UnpooledDataSource driverProperties属性
// PooledDataSource 中持有 UnpooledDataSource对象
metaDataSource.setValue("driverProperties", driverProperties);
}
}
@ -62,7 +62,7 @@ public class UnpooledDataSourceFactory implements DataSourceFactory {
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
// 与UnpooledDataSourceFactory的不同之处是其初始化的DataSource为PooledDataSource
// 与 UnpooledDataSourceFactory 的不同之处是,其初始化的 DataSource PooledDataSource
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
@ -70,17 +70,17 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
```
### 1.2 UnpooledDataSource
本实现类实现了DataSource接口中的getConnection()及其重载方法,用于获取数据库连接。其中的主要属性及方法如下:
本实现类实现了 DataSource接口 中的 getConnection() 及其重载方法,用于获取数据库连接。其中的主要属性及方法如下:
```java
public class UnpooledDataSource implements DataSource {
// 加载Driver驱动类 类加载器
// 加载 Driver驱动类 类加载器
private ClassLoader driverClassLoader;
// 数据库连接驱动的相关配置通过UnpooledDataSourceFactory的setProperties()方法设置进来的
// 数据库连接驱动的相关配置,通过 UnpooledDataSourceFactory setProperties()方法 设置进来的
private Properties driverProperties;
// 缓存所有已注册的数据库连接驱动Driver
// 缓存所有已注册的 数据库连接驱动Driver
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
// 数据库连接驱动名称
@ -100,8 +100,8 @@ public class UnpooledDataSource implements DataSource {
private Integer defaultNetworkTimeout;
/**
* UnpooledDataSource被加载时会通过该静态代码块将已经在DriverManager
* 中注册JDBC Driver复制一份到registeredDrivers
* UnpooledDataSource 被加载时,会通过该静态代码块将已经在 DriverManager
* 中注册的 JDBC Driver 注册到 registeredDrivers 中
*/
static {
Enumeration<Driver> drivers = DriverManager.getDrivers();
@ -111,11 +111,11 @@ public class UnpooledDataSource implements DataSource {
}
}
// getConnection()及其重载方法、doGetConnection(String username, String password)方法
// getConnection() 及其重载方法、doGetConnection(String username, String password)方法
// 最终都会调用本方法
private Connection doGetConnection(Properties properties) throws SQLException {
// 初始化数据库驱动该方法会创建配置中指定的Driver对象
// 并将其注册到DriverManager和registeredDrivers中
// 初始化数据库驱动,该方法会创建配置中指定的 Driver对象
// 并将其注册到 DriverManager registeredDrivers
initializeDriver();
Connection connection = DriverManager.getConnection(url, properties);
// 配置数据库连接属性,如:连接超时时间、是否自动提交事务、事务隔离级别
@ -134,12 +134,12 @@ public class UnpooledDataSource implements DataSource {
} else {
driverType = Resources.classForName(driver);
}
// 通过反射 获取Driver实例对象
// 通过反射获取 Driver实例对象
Driver driverInstance = (Driver)driverType.newInstance();
// 注册驱动到DriverManagerDriverProxy是UnpooledDataSource的内部类
// 也是Driver的静态代理类
// 注册驱动到 DriverManagerDriverProxy UnpooledDataSource 的内部类
// 也是 Driver 的静态代理类
DriverManager.registerDriver(new DriverProxy(driverInstance));
// 将driver缓存到registeredDrivers
// 将 driver 缓存到 registeredDrivers
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
@ -171,14 +171,14 @@ public class UnpooledDataSource implements DataSource {
3. 连接池会控制总连接上限及空闲连接上线,如果连接池中的连接总数已达上限,且都被占用,后续的连接请求会进入阻塞队列等待,直到有连接可用;
4. 如果连接池中空闲连接较多,已达到空闲连接上限,则返回的连接会被关闭掉,以降低系统开销。
PooledDataSource实现了简易的数据库连接池功能其创建数据库连接的功能依赖了上面的UnpooledDataSource。
PooledDataSource 实现了简易的数据库连接池功能,其创建数据库连接的功能依赖了上面的 UnpooledDataSource。
#### 1.3.1 PooledConnection
PooledDataSource通过管理PooledConnection来实现对java.sql.Connection的管理。PooledConnection封装了java.sql.Connection数据库连接对象及其代理对象JDK动态代理生成的。PooledConnection继承了JDK动态代理的InvocationHandler接口。
PooledDataSource 通过管理 PooledConnection 来实现对 java.sql.Connection 的管理。PooledConnection 封装了 java.sql.Connection数据库连接对象 及其代理对象JDK动态代理生成的。PooledConnection 继承了 JDK动态代理 InvocationHandler接口。
```java
class PooledConnection implements InvocationHandler {
// 记录当前PooledConnection对象所属的PooledDataSource对象
// 当调用close()方法时会将PooledConnection放回该PooledDataSource
// 记录当前 PooledConnection对象 所属的 PooledDataSource对象
// 当调用 close()方法 时会将 PooledConnection 放回该 PooledDataSource
private final PooledDataSource dataSource;
// 真正的数据库连接对象
private final Connection realConnection;
@ -190,30 +190,30 @@ class PooledConnection implements InvocationHandler {
private long createdTimestamp;
// 最后一次使用的 时间戳
private long lastUsedTimestamp;
// 由 数据库URL、用户名、密码 计算出来的hash值可用于标识该连接所在的连接池
// 由 数据库URL、用户名、密码 计算出来的 hash值可用于标识该连接所在的连接池
private int connectionTypeCode;
// 检测当前PooledConnection连接池连接对象是否有效主要用于 防止程序通过close()方法将
// 检测当前 PooledConnection连接池连接对象 是否有效,主要用于 防止程序通过 close()方法
// 连接还给连接池之后,依然通过该连接操作数据库
private boolean valid;
/**
* invoke()方法是本类的重点实现也是proxyConnection代理连接对象的代理逻辑实现
* 它会对close()方法的调用进行处理并在调用realConnection的方法之前进行校验
* invoke()方法 是本类的重点实现,也是 proxyConnection代理连接对象 的代理逻辑实现
* 它会对 close()方法 的调用进行处理,并在调用 realConnection对象 的方法之前进行校验
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 如果调用的是close()方法,则将其放进连接池,而不是真的关闭连接
// 如果调用的是 close()方法,则将其放进连接池,而不是真的关闭连接
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
try {
if (!Object.class.equals(method.getDeclaringClass())) {
// 通过上面的valid字段 校验连接是否有效
// 通过上面的 valid字段 校验连接是否有效
checkConnection();
}
// 调用realConnection的对应方法
// 调用 realConnection对象 的对应方法
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
@ -229,7 +229,7 @@ class PooledConnection implements InvocationHandler {
}
```
#### 1.3.2 PoolState
PoolState主要用于管理PooledConnection对象状态其通过持有两个List&lt;PooledConnection&gt;集合分别管理空闲状态的连接 和 活跃状态的连接。另外PoolState还定义了一系列用于统计的字段。
PoolState 主要用于管理 PooledConnection 对象状态,其通过持有两个 List&lt;PooledConnection&gt;集合 分别管理空闲状态的连接 和 活跃状态的连接。另外PoolState 还定义了一系列用于统计的字段。
```java
public class PoolState {
@ -336,8 +336,8 @@ public class PoolState {
}
```
#### 1.3.3 PooledDataSource
PooledDataSource管理的数据库连接对象 是由其持有的UnpooledDataSource对象创建的并由PoolState管理所有连接的状态。
PooledDataSource的getConnection()方法会首先调用popConnection()方法获取PooledConnection对象然后通过PooledConnection的getProxyConnection()方法获取数据库连接的代理对象。popConnection()方法是PooledDataSource的核心逻辑之一其整体的逻辑关系如下图
PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource对象 创建的,并由 PoolState 管理所有连接的状态。
PooledDataSource getConnection()方法 会首先调用 popConnection()方法 获取 PooledConnection对象然后通过 PooledConnection getProxyConnection()方法 获取数据库连接的代理对象。popConnection()方法 PooledDataSource 的核心逻辑之一,其整体的逻辑关系如下图:
![avatar](/images/mybatis/数据库连接池流程图.png)
@ -375,8 +375,8 @@ public class PooledDataSource implements DataSource {
private int expectedConnectionTypeCode;
/**
* 下面的两个getConnection()方法都会调用popConnection()
* 获取PooledConnection对象然后调用该对象的getProxyConnection()方法
* 下面的两个 getConnection()方法 都会调用 popConnection()
* 获取 PooledConnection对象然后调用该对象的 getProxyConnection()方法
* 获取数据库连接的代理对象
*/
@Override
@ -390,7 +390,7 @@ public class PooledDataSource implements DataSource {
}
/**
* 本方法实现了连接池获取连接对象的具体逻辑是PooledDataSource的核心逻辑之一
* 本方法实现了连接池获取连接对象的具体逻辑,是 PooledDataSource 的核心逻辑之一
*/
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
@ -421,7 +421,7 @@ public class PooledDataSource implements DataSource {
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// 如果最老的连接超时了就在PoolState中记录一下相关信息然后将该连接对象释放掉
// 如果最老的连接超时了,就在 PoolState 中记录一下相关信息,然后将该连接对象释放掉
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
@ -442,7 +442,7 @@ public class PooledDataSource implements DataSource {
log.debug("Bad connection. Could not roll back");
}
}
// 从最老连接中取出真正的 数据库连接对象及相关信息用来构建新的PooledConnection对象
// 从最老连接中取出真正的 数据库连接对象及相关信息,用来构建新的 PooledConnection对象
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
@ -453,7 +453,7 @@ public class PooledDataSource implements DataSource {
}
} else {
// 如果最老的连接对象也没超时,则进入阻塞等待,
// 等待时间poolTimeToWait可自行设置
// 等待时间 poolTimeToWait 可自行设置
try {
if (!countedWait) {
// 等待次数加一
@ -464,7 +464,7 @@ public class PooledDataSource implements DataSource {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
// native方法使执行到这里的线程阻塞等待poolTimeToWait毫秒
// native方法使执行到这里的线程阻塞等待 poolTimeToWait毫秒
state.wait(poolTimeToWait);
// 统计累计等待的时间
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
@ -529,9 +529,9 @@ public class PooledDataSource implements DataSource {
}
/**
* 看一下之前讲过的PooledConnection中的动态代理方法invoke(),可以发现
* 当调用数据库连接代理对象的close()方法时,并未关闭真正的数据库连接,
* 而是调用了本方法将连接对象归还给连接池方便后续使用本方法也是PooledDataSource的核心逻辑之一
* 看一下之前讲过的 PooledConnection 中的 动态代理方法invoke(),可以发现
* 当调用数据库连接代理对象的 close()方法 时,并未关闭真正的数据库连接,
* 而是调用了本方法,将连接对象归还给连接池,方便后续使用,本方法也是 PooledDataSource 的核心逻辑之一
*/
protected void pushConnection(PooledConnection conn) throws SQLException {
// 国际惯例,操作公共资源先上个锁
@ -540,7 +540,8 @@ public class PooledDataSource implements DataSource {
state.activeConnections.remove(conn);
// 如果该连接有效
if (conn.isValid()) {
// 如果连接池中的空闲连接数未达到阈值 且 该连接确实属于本连接池通过之前获取的expectedConnectionTypeCode进行校验
// 如果连接池中的空闲连接数未达到阈值 且 该连接确实属于
// 本连接池(通过之前获取的 expectedConnectionTypeCode 进行校验)
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
// CheckoutTime = 应用从连接池取出连接到归还连接的时长
// accumulatedCheckoutTime = 所有连接累计的CheckoutTime
@ -549,14 +550,14 @@ public class PooledDataSource implements DataSource {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
// 从conn中取出真正的 数据库连接对象重新封装成PooledConnection
// 从 conn 中取出真正的 数据库连接对象,重新封装成 PooledConnection
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
// 将newConn放进空闲连接对象列表
// 将 newConn 放进空闲连接对象列表
state.idleConnections.add(newConn);
// 设置newConn的相关属性
// 设置 newConn 的相关属性
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
// 将原本的conn作废
// 将原本的 conn 作废
conn.invalidate();
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
@ -565,7 +566,7 @@ public class PooledDataSource implements DataSource {
state.notifyAll();
} else {
// 如果空闲连接已达阈值 或 该连接对象不属于本连接池,则做好统计数据
// 回滚连接的事务关闭真正的连接最后作废该conn
// 回滚连接的事务,关闭真正的连接,最后作废 该conn
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
@ -632,11 +633,11 @@ public class PooledDataSource implements DataSource {
}
}
```
最后我们来看一下popConnection()和pushConnection()都调用了的isValid()方法该方法除了检测PooledConnection中的valid字段外 还还会调用PooledDataSource中的pingConnection()方法,让数据库连接对象 执行指定的sql语句检测连接是否正常。
最后,我们来看一下 popConnection() pushConnection() 都调用了的 isValid()方法,该方法除了检测 PooledConnection 中的 valid字段 外 还还会调用 PooledDataSource 中的 pingConnection()方法,让数据库连接对象 执行指定的 sql语句检测连接是否正常。
```java
class PooledConnection implements InvocationHandler {
/**
* 检测PooledConnection对象的有效性
* 检测 PooledConnection对象 的有效性
*/
public boolean isValid() {
return valid && realConnection != null && dataSource.pingConnection(this);
@ -646,7 +647,7 @@ class PooledConnection implements InvocationHandler {
public class PooledDataSource implements DataSource {
/**
* ping一下数据库检测数据库连接是否正常
* ping 一下数据库,检测数据库连接是否正常
*/
protected boolean pingConnection(PooledConnection conn) {
boolean result = true;
@ -661,10 +662,10 @@ public class PooledDataSource implements DataSource {
}
if (result) {
// 是否允许发送检测语句检测数据库连接是否正常poolPingEnabled可自行配置
// 是否允许发送检测语句检测数据库连接是否正常poolPingEnabled 可自行配置
// 该检测会牺牲一定的系统资源,以提高安全性
if (poolPingEnabled) {
// 超过poolPingConnectionsNotUsedFor毫秒未使用的连接 才会检测其连接状态
// 超过 poolPingConnectionsNotUsedFor毫秒 未使用的连接 才会检测其连接状态
if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
try {
if (log.isDebugEnabled()) {
@ -703,8 +704,8 @@ public class PooledDataSource implements DataSource {
}
```
## 2 Transaction
遵循接口-实现类的设计原则mybatis也是先使用Transaction接口对数据库事务做了抽象而实现类则只提供了两个JdbcTransaction和ManagedTransaction。这两种对象的获取使用了两个对应的工厂类JdbcTransactionFactory和ManagedTransactionFactory。
不过一般我们并不会使用mybatis管理事务而是将mybatis集成到spring由spring进行事务的管理。细节部分会在后面的文章中详细讲解。
遵循 “接口-实现类” 的设计原则Mybatis 也是先使用 Transaction接口 对数据库事务做了抽象而实现类则只提供了两个JdbcTransaction ManagedTransaction。这两种对象的获取使用了两个对应的工厂类 JdbcTransactionFactory ManagedTransactionFactory。
不过一般我们并不会使用 Mybatis 管理事务,而是将 Mybatis 集成到 Spring由 Spring 进行事务的管理。细节部分会在后面的文章中详细讲解。
```java
public interface Transaction {
@ -765,7 +766,7 @@ public class JdbcTransaction implements Transaction {
return connection;
}
// 提交、回滚、关闭等操作的代码都比较简单只对原生的JDBC操作做了简单封装
// 提交、回滚、关闭等操作的代码都比较简单,只对原生的 JDBC操作 做了简单封装
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
@ -863,10 +864,10 @@ public class ManagedTransaction implements Transaction {
private TransactionIsolationLevel level;
// 对应的数据库连接
private Connection connection;
// 控制是否关闭持有的连接在close()方法中用其判断是否真的关闭连接
// 控制是否关闭持有的连接,在 close()方法 中用其判断是否真的关闭连接
private final boolean closeConnection;
// 本类的实现也很简单commit、rollback方法都是空实现
// 本类的实现也很简单commit()、rollback()方法 都是空实现
public ManagedTransaction(Connection connection, boolean closeConnection) {
this.connection = connection;
this.closeConnection = closeConnection;
@ -926,7 +927,7 @@ public class ManagedTransaction implements Transaction {
public interface TransactionFactory {
/**
* 配置TransactionFactory对象一般会在完成TransactionFactory对象
* 配置 TransactionFactory对象一般会在完成 TransactionFactory对象
* 初始化之后 就进行自定义属性配置
*/
default void setProperties(Properties props) {
@ -934,12 +935,12 @@ public interface TransactionFactory {
}
/**
* 在指定的数据库连接上创建Transaction事务对象
* 在指定的数据库连接上创建 Transaction事务对象
*/
Transaction newTransaction(Connection conn);
/**
* 从指定数据源获取数据库连接并在此连接上创建Transaction对象
* 从指定数据源获取数据库连接,并在此连接上创建 Transaction对象
*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

@ -1,29 +1,30 @@
binding模块主要为了解决一个历史遗留问题原先查询一个VO对象时需要调用SqlSession.queryForObject(“selectXXVOById”, primaryKey)方法执行指定的sql语句第一个参数selectXXVOById指定了执行的sql语句id如果我们不小心写错了参数mybatis是无法在初始化时发现这个错误的只会在实际调用queryForObject(“selectXXVOById”, primaryKey)方法时才会抛出异常这对于工程师来说是非常难受的就像泛型出来之前很多类型转换不会在编译期发现错误一样。而binding模块就像Java的泛型机制一样将程序的错误提前暴露出来为开发人员省去不少排查问题的精力。
binding模块 主要为了解决一个历史遗留问题,原先查询一个 VO对象 时需要调用 SqlSession.queryForObject(“selectXXVOById”, primaryKey)方法,执行指定的 sql语句第一个参数 selectXXVOById 指定了执行的 sql语句id如果我们不小心写错了参数Mybatis 是无法在初始化时发现这个错误的,只会在实际调用 queryForObject(“selectXXVOById”, primaryKey)方法 时才会抛出异常,这对于工程师来说是非常难受的,就像泛型出来之前,很多类型转换不会在编译期发现错误一样。而 binding模块 就像 Java的泛型机制 一样,将程序的错误提前暴露出来,为开发人员省去不少排查问题的精力。
binding模块的解决方案是定义一个Mapper接口在接口中定义sql语句对应的方法名(Id)及参数这些方法在mybatis的初始化过程中会与该Mapper接口对应的映射配置文件中的sql语句相关联如果存在无法关联的sql语句mybatis就会抛出异常,帮助我们及时发现问题。示例代码如下:
binding模块 的解决方案是,定义一个 Mapper接口在接口中定义 sql语句 对应的 方法名Id 及 参数,这些方法在 Mybatis 的初始化过程中,会与该 Mapper接口 对应的映射配置文件中的 sql语句 相关联,如果存在无法关联的 sql语句Mybatis 就会抛出异常,帮助我们及时发现问题。示例代码如下:
```java
public interface HeroMapper {
// 映射文件中会存在一个<select>节点id为“selectHeroVOById”
// 映射文件中会存在一个 <select>节点id “selectHeroVOById”
public HeroVO selectHeroVOById(int id);
}
// 首先获取HeroMapper对应的代理对象
// 首先,获取 HeroMapper 对应的代理对象
HeroMapper heroMapper = session.getMapper(HeroMapper.class);
// 直接调用HeroMapper接口中的方法 获取结果集
// 直接调用 HeroMapper接口 中的方法,获取结果集
HeroVO heroVO = heroMapper.selectHeroVOById("23333");
```
## 1 MapperRegistry和MapperProxyFactory
MapperRegistry是Mapper接口及其对应的代理对象工厂的注册中心。Configuration是mybatis中全局性的配置对象根据mybatis的核心配置文件mybatis-config.xml解析而成。Configuration通过mapperRegistry属性持有该对象。
mybatis在初始化过程中会读取映射配置文件和Mapper接口中的注解信息并调用MapperRegistry的addMappers()方法填充knownMappers集合在需要执行某sql语句时会先调用getMapper()方法获取实现了Mapper接口的动态代理对象。
MapperRegistry 是 Mapper接口 及其对应的代理对象工厂的注册中心。Configuration 是 Mybatis 中全局性的配置对象,根据 Mybatis 的核心配置文件 mybatis-config.xml 解析而成。Configuration 通过 mapperRegistry属性 持有该对象。
Mybatis 在初始化过程中会读取映射配置文件和 Mapper接口 中的注解信息,并调用 MapperRegistry 的 addMappers()方法 填充 knownMappers集合在需要执行某 sql语句 时,会先调用 getMapper()方法 获取实现了 Mapper接口 的动态代理对象。
```java
public class MapperRegistry {
// mybatis全局唯一的配置对象,包含了几乎所有配置信息
// Mybatis 全局唯一的配置对象,包含了几乎所有配置信息
private final Configuration config;
// keyMapper接口valueMapperProxyFactory为Mapper接口创建代理对象的工厂
// keyMapper接口valueMapperProxyFactory Mapper接口 创建代理对象的工厂
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
// 下面的两个重载方法 通过扫描指定的包目录获取所有的Mapper接口
// 下面的两个重载方法 通过扫描指定的包目录,获取所有的 Mapper接口
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
@ -38,7 +39,7 @@ public class MapperRegistry {
}
public <T> void addMapper(Class<T> type) {
// 该type是不是接口
// 该 type 是不是接口
if (type.isInterface()) {
// 是否已经加载过
if (hasMapper(type)) {
@ -46,9 +47,9 @@ public class MapperRegistry {
}
boolean loadCompleted = false;
try {
// 将Mapper接口的Class对象 和 对应的MapperProxyFactory对象添加到knownMappers集合
// 将 Mapper接口 Class对象 和 对应的 MapperProxyFactory对象 添加到 knownMappers集合
knownMappers.put(type, new MapperProxyFactory<>(type));
// XML解析和注解的处理
// XML 解析和注解的处理
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
@ -62,45 +63,45 @@ public class MapperRegistry {
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取type对应的MapperProxyFactory对象
// 获取 type 对应的 MapperProxyFactory对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 根据sqlSession创建 type接口的代理对象
// 根据 sqlSession 创建 type接口 的代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// 获取所有的MapperProxyFactory
// 获取所有的 MapperProxyFactory
public Collection<Class<?>> getMappers() {
return Collections.unmodifiableCollection(knownMappers.keySet());
}
// 初始化的时候会持有Configuration对象
// 初始化的时候会持有 Configuration对象
public MapperRegistry(Configuration config) {
this.config = config;
}
// 是否存在指定的MapperProxyFactory
// 是否存在指定的 MapperProxyFactory
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
}
```
MapperProxyFactory主要负责创建代理对象。
MapperProxyFactory 主要负责创建代理对象。
```java
public class MapperProxyFactory<T> {
// 要创建的动态代理对象 所实现的接口
private final Class<T> mapperInterface;
// 缓存mapperInterface接口中Method对象和其对应的MapperMethod对象
// 缓存 mapperInterface接口 Method对象 和其对应的 MapperMethod对象
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
// 初始化时为mapperInterface注入值
// 初始化时为 mapperInterface 注入值
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@ -114,15 +115,15 @@ public class MapperProxyFactory<T> {
}
public T newInstance(SqlSession sqlSession) {
// 每都会创建一个新的MapperProxy对象
// 每都会创建一个新的 MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
/**
* 非常眼熟的JDK动态代理 代码创建了实现mapperInterface接口的代理对象
* 根据国际惯例mapperProxy对应的类 肯定实现了InvocationHandler接口
* 为mapperInterface接口方法的调用织入统一处理逻辑
* 非常眼熟的 JDK动态代理 代码,创建了实现 mapperInterface接口 的代理对象
* 根据国际惯例mapperProxy对应的类 肯定实现了 InvocationHandler接口
* 为 mapperInterface接口方法的调用 织入统一处理逻辑
*/
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
@ -130,17 +131,17 @@ public class MapperProxyFactory<T> {
}
```
## 2 MapperProxy
MapperProxy实现了InvocationHandler接口为Mapper接口的方法调用织入了统一处理。
MapperProxy 实现了 InvocationHandler接口 Mapper接口 的方法调用织入了统一处理。
```java
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
// 记录关联的sqlSession对象
// 记录关联的 sqlSession对象
private final SqlSession sqlSession;
// 对应的Mapper接口的Class对象
// 对应的 Mapper接口 Class对象
private final Class<T> mapperInterface;
// 用于缓存MapperMethod对象keyMapper接口中方法对应的Method对象
// valueMapperMethod对象该对象会完成参数转换 及 sql语句的执行功能
// 用于缓存 MapperMethod对象keyMapper接口 中方法对应的 Method对象
// valueMapperMethod对象该对象会完成参数转换 及 sql语句 的执行功能)
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
@ -152,7 +153,7 @@ public class MapperProxy<T> implements InvocationHandler, Serializable {
// 为被代理对象的方法 织入统一处理
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果目标方法继承自Object则直接调用目标方法
// 如果目标方法继承自 Object则直接调用目标方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
@ -161,30 +162,31 @@ public class MapperProxy<T> implements InvocationHandler, Serializable {
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 从缓存中获取mapperMethod对象如果没有就创建新的
// 从缓存中获取 mapperMethod对象如果没有就创建新的
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行sql语句返回结果集
// 执行 sql语句返回结果集
return mapperMethod.execute(sqlSession, args);
}
// 主要负责维护methodCache 缓存
// 主要负责维护 methodCache 缓存
private MapperMethod cachedMapperMethod(Method method) {
// 这里用到了Java8的新特性computeIfAbsent()是Java8的方法Lambda表达式也是Java8中最重要的新特性之一
// computeIfAbsent()方法表示 当前map中若key对应的value为空则执行传入的Lambda表达式将key和表达式的value
// 存入当前map并返回value值
// 在这段代码中的意思是若methodCache中没有method对应的value就执行右侧的Lambda表达式并将表达式的结果
// 存入methodCache 并 返回
// 这里用到了 Java8 的新特性computeIfAbsent() Java8 新增的方法Lambda表达式 也是 Java8中 最重要的新特性之一
// computeIfAbsent()方法 表示 当前map中 key 对应的 value 为空,则执行传入的 Lambda表达式 key 和表达式的 value
// 存入 当前map并返回 value值
// 在这段代码中的意思是:若 methodCache 中没有 method 对应的 value就执行右侧的 Lambda表达式并将表达式的结果
// 存入 methodCache 并返回
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}
```
## 3 MapperMethod
MapperMethod中封装了Mapper接口中对应方法的信息和对应sql语句的信息是连接Mapper接口及映射配置文件中定义的sql语句的桥梁。
MapperMethod中持有两个非常重要的属性这两个属性对应的类 都是MapperMethod中的静态内部类。另外MapperMethod在被实例化时就对这两个属性进行了初始化。
MapperMethod 中封装了 Mapper接口 中对应方法的信息,和对应 sql语句 的信息,是连接 Mapper接口 及映射配置文件中定义的 sql语句 的桥梁。
MapperMethod 中持有两个非常重要的属性,这两个属性对应的类 都是 MapperMethod 中的静态内部类。另外MapperMethod 在被实例化时就对这两个属性进行了初始化。
```java
public class MapperMethod {
/** 下面这俩货都是内部类而且还是public static的 */
/** 下面这俩货都是内部类,而且还是 public static 的 */
private final SqlCommand command;
private final MethodSignature method;
@ -194,25 +196,25 @@ public class MapperMethod {
}
}
```
MapperMethod中的核心方法execute()就主要用到了这两个类所以我们先看一下SqlCommand和MethodSignature的源码。
MapperMethod 中的核心方法 execute() 就主要用到了这两个类,所以我们先看一下 SqlCommand MethodSignature 的源码。
### 3.1 SqlCommand
```java
public static class SqlCommand {
// sql语句的id
private final String name;
// sql语句的类型SqlCommandType是枚举类型持有常用的 增、删、改、查等操作类型
// sql语句的类型SqlCommandType 是枚举类型,持有常用的 增、删、改、查等操作类型
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
// 方法名
final String methodName = method.getName();
// 该方法对应的类的Class对象
// 该方法对应的类的 Class对象
final Class<?> declaringClass = method.getDeclaringClass();
// MappedStatement封装了sql语句相关的信息在mybatis初始化时创建
// MappedStatement 封装了 sql语句 相关的信息,在 Mybatis初始化 时创建
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
if (ms == null) {
// 处理Flush注解
// 处理 Flush注解
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
@ -221,7 +223,7 @@ MapperMethod中的核心方法execute()就主要用到了这两个类,所以
+ mapperInterface.getName() + "." + methodName);
}
} else {
// 初始化name 和 type
// 初始化 name 和 type
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
@ -232,17 +234,17 @@ MapperMethod中的核心方法execute()就主要用到了这两个类,所以
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// sql语句的名称默认是由Mapper接口方法的 包名.类名.方法名
// sql语句 的名称默认是由 Mapper接口方法 的 包名.类名.方法名
String statementId = mapperInterface.getName() + "." + methodName;
// 检测是否有该名称的sql语句
// 检测是否有该名称的 sql语句
if (configuration.hasStatement(statementId)) {
// 从configuration的mappedStatements容器中获取statementId对应的MappedStatement对象
// 从 configuration mappedStatements容器 中获取 statementId 对应的 MappedStatement对象
return configuration.getMappedStatement(statementId);
// 如果此方法不是mapperInterface接口定义的则返回空
// 如果此方法不是 mapperInterface接口 定义的,则返回空
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
// 对mapperInterface的父接口 进行递归处理
// 对 mapperInterface 的父接口 进行递归处理
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
@ -272,33 +274,33 @@ MapperMethod中的核心方法execute()就主要用到了这两个类,所以
private final boolean returnsMany;
// 返回值类型是否为 Map类型
private final boolean returnsMap;
// 返回值类型是否为void
// 返回值类型是否为 void
private final boolean returnsVoid;
// 返回值类型是否为Cursor
// 返回值类型是否为 Cursor
private final boolean returnsCursor;
// 返回值类型是否为Optional
// 返回值类型是否为 Optional
private final boolean returnsOptional;
// 返回值类型的Class对象
// 返回值类型的 Class对象
private final Class<?> returnType;
// 如果返回值类型为Map则用该字段记录了作为key的列名
// 如果返回值类型为 Map则用该字段记录了作为 key 的列名
private final String mapKey;
// 标记该方法参数列表中ResultHandler类型参数的位置
// 标记该方法参数列表中 ResultHandler类型参数 的位置
private final Integer resultHandlerIndex;
// 标记该方法参数列表中RowBounds类型参数的位置
// 标记该方法参数列表中 RowBounds类型参数 的位置
private final Integer rowBoundsIndex;
/**
* 顾名思义这是一个处理Mapper接口中 方法参数列表的解析器它使用了一个SortedMap<Integer, String>
* 类型的容器 记录了参数在参数列表中的位置索引 与 参数名之间的对应关系key参数在参数列表中的索引位置
* 顾名思义,这是一个处理 Mapper接口 中 方法参数列表的解析器,它使用了一个 SortedMap<Integer, String>
* 类型的容器记录了参数在参数列表中的位置索引 与 参数名之间的对应关系key参数 在参数列表中的索引位置,
* value参数名(参数名可用@Param注解指定默认使用参数索引作为其名称)
*/
private final ParamNameResolver paramNameResolver;
/**
* MethodSignature的构造方法会解析对应的method并初始化上述字段
* MethodSignature 的构造方法会解析对应的 method并初始化上述字段
*/
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
// 获取method方法的返回值类型
// 获取 method方法 的返回值类型
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
@ -307,7 +309,7 @@ MapperMethod中的核心方法execute()就主要用到了这两个类,所以
} else {
this.returnType = method.getReturnType();
}
// 对MethodSignature持有的各属性 进行初始化
// 对 MethodSignature 持有的各属性 进行初始化
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
@ -340,18 +342,18 @@ MapperMethod中的核心方法execute()就主要用到了这两个类,所以
}
```
### 3.3 execute()方法
execute()方法会根据sql语句的类型(CRUD)调用SqlSession对应的方法完成数据库操作SqlSession是mybatis的核心组件之一,后面会详细解读。
execute()方法 会根据 sql语句 的类型(CRUD)调用 SqlSession 对应的方法完成数据库操作SqlSession 是 Mybatis 的核心组件之一,后面会详细解读。
```java
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据sql语句的类型 调用sqlSession对应的方法
// 根据 sql语句 的类型 调用 sqlSession 对应的方法
switch (command.getType()) {
case INSERT: {
// 使用ParamNameResolver处理args实参列表将用户传入的实参与
// 使用 ParamNameResolver 处理 args实参列表将用户传入的实参与
// 指定参数名称关联起来
Object param = method.convertArgsToSqlCommandParam(args);
// 获取返回结果rowCountResult()方法 根据method属性中的returnType
// 获取返回结果rowCountResult()方法 根据 method属性 中的 returnType
// 对结果的类型进行转换
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
@ -367,24 +369,24 @@ public class MapperMethod {
break;
}
case SELECT:
// 处理返回值为void且ResultSet通过ResultHandler处理的方法
// 处理返回值为 void ResultSet 通过 ResultHandler 处理的方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
// 处理返回值为集合 或 数组的方法
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
// 处理返回值为Map的方法
// 处理返回值为 Map 的方法
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
// 处理返回值为Cursor的方法
// 处理返回值为 Cursor 的方法
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 处理返回值为单一对象的方法
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
// 处理返回值为Optional的方法
// 处理返回值为 Optional 的方法
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
@ -405,20 +407,20 @@ public class MapperMethod {
}
/**
* 当执行insert、update、delete类型的sql语句时其执行结果都要经过本方法处理
* 当执行 insert、update、delete 类型的 sql语句 时,其执行结果都要经过本方法处理
*/
private Object rowCountResult(int rowCount) {
final Object result;
// 方法的返回值为void时
// 方法的返回值为 void
if (method.returnsVoid()) {
result = null;
// 方法的返回值为Integer时
// 方法的返回值为 Integer
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
// 方法的返回值为Long时
// 方法的返回值为 Long
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long)rowCount;
// 方法的返回值为Boolean时
// 方法的返回值为 Boolean
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
@ -428,12 +430,12 @@ public class MapperMethod {
}
/**
* 如果Mapper接口中定义的方法准备使用ResultHandler处理查询结果集则通过此方法处理
* 如果 Mapper接口 中定义的方法准备使用 ResultHandler 处理查询结果集,则通过此方法处理
*/
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
// 获取sql语句对应的MappedStatement对象该对象中记录了sql语句相关信息
// 获取 sql语句 对应的 MappedStatement对象该对象中记录了 sql语句 相关信息
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
// 当使用ResultHandler处理结果集时必须指定ResultMap或ResultType
// 当使用 ResultHandler 处理结果集时,必须指定 ResultMap ResultType
if (!StatementType.CALLABLE.equals(ms.getStatementType())
&& void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
@ -442,11 +444,11 @@ public class MapperMethod {
}
// 转换实参列表
Object param = method.convertArgsToSqlCommandParam(args);
// 如果实参列表中有RowBounds类型参数
// 如果实参列表中有 RowBounds类型参数
if (method.hasRowBounds()) {
// 从args参数列表中获取RowBounds对象
// 从 args参数列表 中获取 RowBounds对象
RowBounds rowBounds = method.extractRowBounds(args);
// 执行查询并用指定的ResultHandler处理结果对象
// 执行查询,并用指定的 ResultHandler 处理结果对象
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
@ -454,22 +456,22 @@ public class MapperMethod {
}
/**
* 如果Mapper接口中对应方法的返回值为集合(Collection接口实现类) 或 数组,
* 如果 Mapper接口 中对应方法的返回值为集合(Collection接口实现类) 或 数组,
* 则调用本方法将结果集处理成 相应的集合或数组
*/
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 参数列表转换
Object param = method.convertArgsToSqlCommandParam(args);
// 参数列表中是否有RowBounds类型的参数
// 参数列表中是否有 RowBounds类型的参数
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
// 这里使用了selectList()方法进行查询所以返回的结果集就是List类型的
// 这里使用了 selectList()方法 进行查询,所以返回的结果集就是 List类型的
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// 将结果集转换为数组或Collection集合
// 将结果集转换为数组或 Collection集合
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
@ -481,27 +483,27 @@ public class MapperMethod {
}
/**
* 将结果集转换成Collection集合
* 将结果集转换成 Collection集合
*/
private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
// 使用前面介绍的ObjectFactory通过反射方式创建集合对象
// 使用前面介绍的 ObjectFactory通过反射方式创建集合对象
Object collection = config.getObjectFactory().create(method.getReturnType());
MetaObject metaObject = config.newMetaObject(collection);
// 实际上就是调用了Collection的addAll()方法
// 实际上就是调用了 Collection addAll()方法
metaObject.addAll(list);
return collection;
}
/**
* 本方法和上面的convertToDeclaredCollection()功能类似,主要负责将结果对象转换成数组
* 本方法和上面的 convertToDeclaredCollection()功能 类似,主要负责将结果对象转换成数组
*/
@SuppressWarnings("unchecked")
private <E> Object convertToArray(List<E> list) {
// 获取数组中元素的类型Class
// 获取数组中元素的 类型Class
Class<?> arrayComponentType = method.getReturnType().getComponentType();
// 根据元素类型 和 元素数量 初始化数组
Object array = Array.newInstance(arrayComponentType, list.size());
// 将List转换成数组
// 将 List 转换成数组
if (arrayComponentType.isPrimitive()) {
for (int i = 0; i < list.size(); i++) {
Array.set(array, i, list.get(i));
@ -513,7 +515,7 @@ public class MapperMethod {
}
/**
* 如果Mapper接口中对应方法的返回值为类型为Map则调用此方法执行sql语句
* 如果 Mapper接口 中对应方法的返回值为类型为 Map则调用此方法执行 sql语句
*/
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
@ -521,7 +523,7 @@ public class MapperMethod {
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
// 注意这里调用的是sqlSession的selectMap方法使用返回的是一个Map类型结果集
// 注意这里调用的是 SqlSession 的 selectMap()方法,返回的是一个 Map类型结果集
result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
@ -530,7 +532,7 @@ public class MapperMethod {
}
/**
* 本方法与上面的executeForMap()方法类似只不过sqlSession调用的是selectCursor()
* 本方法与上面的 executeForMap()方法 类似,只不过 sqlSession 调用的是 selectCursor()
*/
private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
Cursor<T> result;

@ -1,21 +1,21 @@
MyBatis中的缓存分为一级缓存、二级缓存但在本质上是相同的它们使用的都是Cache接口的实现。MyBatis缓存模块的设计 使用了装饰器模式,这里不对此进行过多解析,以后会专门开一篇博文分析常用框架中使用到的设计模式。
MyBatis 中的缓存分为一级缓存、二级缓存,但在本质上是相同的,它们使用的都是 Cache接口 的实现。MyBatis缓存模块 的设计,使用了装饰器模式,这里不对此进行过多解析,以后会专门开一篇博文分析常用框架中使用到的设计模式。
## 1 Cache组件
MyBatis中缓存模块相关的代码位于org.apache.ibatis.cache包下其中Cache接口是缓存模块中最核心的接口它定义了所有缓存的基本行为。
MyBatis 中缓存模块相关的代码位于 org.apache.ibatis.cache包 下,其中 Cache接口 是缓存模块中最核心的接口,它定义了所有缓存的基本行为。
```java
public interface Cache {
/**
* 获取当前缓存的Id
* 获取当前缓存的 Id
*/
String getId();
/**
* 存入缓存的key和valuekey一般为CacheKey对象
* 存入缓存的 key valuekey 一般为 CacheKey对象
*/
void putObject(Object key, Object value);
/**
* 根据key获取缓存值
* 根据 key 获取缓存值
*/
Object getObject(Object key);
@ -37,7 +37,7 @@ public interface Cache {
/**
*
* 获取读写锁,可以看到,这个接口方法提供了默认的实现!!
* 这是Java8的新特性只是平时开发时很少用到
* 这是 Java8 的新特性!!只是平时开发时很少用到!!!
*
*/
default ReadWriteLock getReadWriteLock() {
@ -45,18 +45,19 @@ public interface Cache {
}
}
```
如下图所示Cache接口的实现类有很多但大部分都是装饰器只有PerpetualCache提供了Cache 接口的基本实现。
如下图所示Cache接口 的实现类有很多,但大部分都是装饰器,只有 PerpetualCache 提供了 Cache 接口 的基本实现。
![avatar](/images/mybatis/Cache组件.png)
### 1.1 PerpetualCache
PerpetualCachePerpetual永恒的持续的在缓存模块中扮演着被装饰的角色其实现比较简单底层使用HashMap记录缓存项也是通过该HashMap对象的方法实现的Cache接口中定义的相应方法。
PerpetualCachePerpetual永恒的持续的在缓存模块中扮演着被装饰的角色其实现比较简单底层使用 HashMap 记录缓存项,也是通过该 HashMap对象 的方法实现的 Cache接口 中定义的相应方法。
```java
public class PerpetualCache implements Cache {
// Cache对象的唯一标识
// Cache对象 的唯一标识
private final String id;
// 其所有的缓存功能实现都是基于JDK的HashMap提供的方法
// 其所有的缓存功能实现,都是基于 JDK HashMap 提供的方法
private Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
@ -94,7 +95,7 @@ public class PerpetualCache implements Cache {
}
/**
* 其重写了Object中的equals()和hashCode()方法两者都只关心id字段
* 其重写了 Object 中的 equals() hashCode()方法,两者都只关心 id字段
*/
@Override
public boolean equals(Object o) {
@ -121,9 +122,9 @@ public class PerpetualCache implements Cache {
}
}
```
下面来看一下cache.decorators包下提供的装饰器它们都直接实现了Cache接口扮演着装饰器的角色。这些装饰器会在PerpetualCache的基础上提供一些额外的功能通过多个组合后满足一个特定的需求。
下面来看一下 cache.decorators包 下提供的装饰器,它们都直接实现了 Cache接口扮演着装饰器的角色。这些装饰器会在 PerpetualCache 的基础上提供一些额外的功能,通过多个组合后满足一个特定的需求。
### 1.2 BlockingCache
BlockingCache是阻塞版本的缓存装饰器它会保证只有一个线程到数据库中查找指定key对应的数据。
BlockingCache 是阻塞版本的缓存装饰器,它会保证只有一个线程到数据库中查找指定 key 对应的数据。
```java
public class BlockingCache implements Cache {
@ -131,7 +132,7 @@ public class BlockingCache implements Cache {
private long timeout;
// 持有的被装饰者
private final Cache delegate;
// 每个key都有其对应的ReentrantLock锁对象
// 每个 key 都有其对应的 ReentrantLock锁对象
private final ConcurrentHashMap<Object, ReentrantLock> locks;
// 初始化 持有的持有的被装饰者 和 锁集合
@ -141,11 +142,11 @@ public class BlockingCache implements Cache {
}
}
```
假设线程A在BlockingCache中未查找到keyA对应的缓存项时线程A会获取keyA对应的锁这样后续线程A在查找keyA时,其它线程会被阻塞。
假设 线程A 在 BlockingCache 中未查找到 keyA 对应的缓存项时线程A 会获取 keyA 对应的锁这样线程A 在后续查找 keyA 时,其它线程会被阻塞。
```java
// 根据key获取锁对象然后上锁
// 根据 key 获取锁对象,然后上锁
private void acquireLock(Object key) {
// 获取key对应的锁对象
// 获取 key 对应的锁对象
Lock lock = getLockForKey(key);
// 获取锁,带超时时长
if (timeout > 0) {
@ -165,18 +166,18 @@ public class BlockingCache implements Cache {
}
private ReentrantLock getLockForKey(Object key) {
// Java8新特性Map系列类中新增的方法
// Java8 新特性Map系列类 中新增的方法
// V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
// 表示若key对应的value为空则将第二个参数的返回值存入该Map集合并返回
// 表示,若 key 对应的 value 为空,则将第二个参数的返回值存入该 Map集合 并返回
return locks.computeIfAbsent(key, k -> new ReentrantLock());
}
```
假设线程A从数据库中查找到keyA对应的结果对象后将结果对象放入到BlockingCache中此时线程A会释放keyA对应的锁唤醒阻塞在该锁上的线程。其它线程即可从BlockingCache中获取keyA对应的数据而不是再次访问数据库。
假设 线程A 从数据库中查找到 keyA 对应的结果对象后,将结果对象放入到 BlockingCache 中,此时 线程A 会释放 keyA 对应的锁,唤醒阻塞在该锁上的线程。其它线程即可从 BlockingCache 中获取 keyA 对应的数据,而不是再次访问数据库。
```java
@Override
public void putObject(Object key, Object value) {
try {
// 存入key和其对应的缓存项
// 存入 key 和其对应的缓存项
delegate.putObject(key, value);
} finally {
// 最后释放锁
@ -194,18 +195,18 @@ public class BlockingCache implements Cache {
}
```
### 1.3 FifoCache和LruCache
在很多场景中为了控制缓存的大小系统需要按照一定的规则清理缓存。FifoCache是先入先出版本的装饰器当向缓存添加数据时如果缓存项的个数已经达到上限则会将缓存中最老即最早进入缓存的缓存项删除。
在很多场景中为了控制缓存的大小系统需要按照一定的规则清理缓存。FifoCache 是先入先出版本的装饰器,当向缓存添加数据时,如果缓存项的个数已经达到上限,则会将缓存中最老(即最早进入缓存)的缓存项删除。
```java
public class FifoCache implements Cache {
// 被装饰对象
private final Cache delegate;
// 用一个FIFO的队列记录key的顺序其具体实现为LinkedList
// 用一个 FIFO 的队列记录 key 的顺序,其具体实现为 LinkedList
private final Deque<Object> keyList;
// 决定了缓存的容量上限
private int size;
// 国际惯例通过构造方法初始化自己的属性缓存容量上限默认为1024个
// 国际惯例,通过构造方法初始化自己的属性,缓存容量上限默认为 1024个
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<>();
@ -228,16 +229,16 @@ public class FifoCache implements Cache {
@Override
public void putObject(Object key, Object value) {
// 存储缓存项之前先在keyList中注册
// 存储缓存项之前,先在 keyList 中注册
cycleKeyList(key);
// 存储缓存项
delegate.putObject(key, value);
}
private void cycleKeyList(Object key) {
// 在keyList队列中注册要添加的key
// 在 keyList队列 中注册要添加的 key
keyList.addLast(key);
// 如果注册这个key会超出容积上限则把最老的一个缓存项清除掉
// 如果注册这个 key 会超出容积上限,则把最老的一个缓存项清除掉
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
@ -254,7 +255,7 @@ public class FifoCache implements Cache {
return delegate.removeObject(key);
}
// 除了清理缓存项还要清理key的注册列表
// 除了清理缓存项,还要清理 key 的注册列表
@Override
public void clear() {
delegate.clear();
@ -263,36 +264,36 @@ public class FifoCache implements Cache {
}
```
LruCache是按照"近期最少使用算法"Least Recently Used, LRU进行缓存清理的装饰器在需要清理缓存时它会清除最近最少使用的缓存项。
LruCache 是按照"近期最少使用算法"Least Recently Used, LRU进行缓存清理的装饰器在需要清理缓存时它会清除最近最少使用的缓存项。
```java
public class LruCache implements Cache {
// 被装饰者
private final Cache delegate;
// 这里使用的是LinkedHashMap它继承了HashMap但它的元素是有序的
// 这里使用的是 LinkedHashMap它继承了 HashMap但它的元素是有序的
private Map<Object, Object> keyMap;
// 最近最少被使用的缓存项的key
// 最近最少被使用的缓存项的 key
private Object eldestKey;
// 国际惯例,构造方法中进行属性初始化
public LruCache(Cache delegate) {
this.delegate = delegate;
// 这里初始化了keyMap并定义了eldestKey的取值规则
// 这里初始化了 keyMap并定义了 eldestKey 的取值规则
setSize(1024);
}
public void setSize(final int size) {
// 初始化keyMap同时指定该Map的初始容积及加载因子第三个参数true表示该LinkedHashMap
// 记录的顺序是accessOrderLinkedHashMap.get()方法会改变其中元素的顺序
// 初始化 keyMap同时指定该 Map 的初始容积及加载因子第三个参数true 表示 该LinkedHashMap
// 记录的顺序是 accessOrderLinkedHashMap.get()方法 会改变其中元素的顺序
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
// 当调用LinkedHashMap.put()方法时,该方法会被调用
// 当调用 LinkedHashMap.put()方法 时,该方法会被调用
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
// 当已达到缓存上限更新eldestKey字段后面将其删除
// 当已达到缓存上限,更新 eldestKey字段后面将其删除
eldestKey = eldest.getKey();
}
return tooBig;
@ -304,13 +305,13 @@ public class LruCache implements Cache {
@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
// 记录缓存项的key超出容量则清除最久未使用的缓存项
// 记录缓存项的 key超出容量则清除最久未使用的缓存项
cycleKeyList(key);
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
// eldestKey不为空则表示已经达到缓存上限
// eldestKey 不为空,则表示已经达到缓存上限
if (eldestKey != null) {
// 清除最久未使用的缓存
delegate.removeObject(eldestKey);
@ -321,7 +322,7 @@ public class LruCache implements Cache {
@Override
public Object getObject(Object key) {
// 访问key元素 会改变该元素在LinkedHashMap中的顺序
// 访问 key元素 会改变该元素在 LinkedHashMap 中的顺序
keyMap.get(key); //touch
return delegate.getObject(key);
}
@ -350,33 +351,33 @@ public class LruCache implements Cache {
}
```
### 1.4 SoftCache和WeakCache
在分析SoftCache和WeakCache实现之前我们再温习一下Java提供的4种引用类型强引用StrongReference、软引用SoftReference、弱引用WeakReference和虚引用PhantomReference。
在分析 SoftCache WeakCache 实现之前,我们再温习一下 Java 提供的4种引用类型强引用StrongReference、软引用SoftReference、弱引用WeakReference和虚引用PhantomReference。
- 强引用
平时用的最多的如Object obj new Object()新建的Object对象就是被强引用的。如果一个对象被强引用即使是JVM内存空间不足要抛出OutOfMemoryError异常GC也绝不会回收该对象。
平时用的最多的,如 Object obj new Object(),新建的 Object对象 就是被强引用的。如果一个对象被强引用,即使是 JVM内存空间不足要抛出 OutOfMemoryError异常GC 也绝不会回收该对象。
- 软引用
仅次于强引用的一种引用它使用类SoftReference来表示。当JVM内存不足时GC会回收那些只被软引用指向的对象从而避免内存溢出。软引用适合引用那些可以通过其他方式恢复的对象例如 数据库缓存中的对象就可以从数据库中恢复所以软引用可以用来实现缓存下面要介绍的SoftCache就是通过软引用实现的。
另外由于在程序使用软引用之前的某个时刻其所指向的对象可能己经被GC回收掉了所以通过 Reference.get()方法来获取软引用所指向的对象时,总是要通过检查该方法返回值是否为 null来判断被软引用的对象是否还存活。
仅次于强引用的一种引用,它使用类 SoftReference 来表示。当 JVM内存不足时GC 会回收那些只被软引用指向的对象,从而避免内存溢出。软引用适合引用那些可以通过其他方式恢复的对象,例如, 数据库缓存中的对象就可以从数据库中恢复,所以软引用可以用来实现缓存,下面要介绍的 SoftCache 就是通过软引用实现的。
另外,由于在程序使用软引用之前的某个时刻,其所指向的对象可能己经被 GC 回收掉了,所以通过 Reference.get()方法 来获取软引用所指向的对象时,总是要通过检查该方法返回值是否为 null来判断被软引用的对象是否还存活。
- 弱引用
弱引用使用WeakReference表示它不会阻止所引用的对象被GC回收。在JVM进行垃圾回收时如果指向一个对象的所有引用都是弱引用那么该对象会被回收。
所以只被弱引用所指向的对象其生存周期是两次GC之间的这段时间而只被软引用所指向的对象可以经历多次GC直到出现内存紧张的情况才被回收。
弱引用使用 WeakReference表示它不会阻止所引用的对象被 GC回收。在 JVM 进行垃圾回收时,如果指向一个对象的所有引用都是弱引用,那么该对象会被回收。
所以,只被弱引用所指向的对象,其生存周期是 两次GC之间 的这段时间,而只被软引用所指向的对象可以经历多次 GC直到出现内存紧张的情况才被回收。
- 虚引用
最弱的一种引用类型由类PhantomReference表示。虚引用可以用来实现比较精细的内存使用控制但很少使用。
最弱的一种引用类型,由类 PhantomReference 表示。虚引用可以用来实现比较精细的内存使用控制,但很少使用。
- 引用队列ReferenceQueue )
很多场景下我们的程序需要在一个对象被GC时得到通知引用队列就是用于收集这些信息的队列。在创建SoftReference对象时可以为其关联一个引用队列当SoftReference所引用的对象被GC时 JVM就会将该SoftReference对象添加到与之关联的引用队列中。当需要检测这些通知信息时就可以从引用队列中获取这些SoftReference对象。不仅是SoftReference弱引用和虚引用都可以关联相应的队列。
很多场景下,我们的程序需要在一个对象被 GC 时得到通知,引用队列就是用于收集这些信息的队列。在创建 SoftReference对象 时,可以为其关联一个引用队列,当 SoftReference 所引用的对象被 GC 时, JVM 就会将该 SoftReference对象 添加到与之关联的引用队列中。当需要检测这些通知信息时,就可以从引用队列中获取这些 SoftReference对象。不仅是 SoftReference弱引用和虚引用都可以关联相应的队列。
现在来看一下SoftCache的具体实现。
现在来看一下 SoftCache 的具体实现。
```java
public class SoftCache implements Cache {
// 这里使用了LinkedList作为容器在SoftCache中最近使用的一部分缓存项不会被GC
// 这是通过将其value添加到hardLinksToAvoidGarbageCollection集合实现的有强引用指向其value
// 这里使用了 LinkedList 作为容器,在 SoftCache 中,最近使用的一部分缓存项不会被 GC
// 这是通过将其 value 添加到 hardLinksToAvoidGarbageCollection集合 实现的有强引用指向其value
private final Deque<Object> hardLinksToAvoidGarbageCollection;
// 引用队列用于记录已经被GC的缓存项所对应的SoftEntry对象
// 引用队列,用于记录已经被 GC 的缓存项所对应的 SoftEntry对象
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
// 持有的被装饰者
private final Cache delegate;
// 强连接的个数默认为256
// 强连接的个数,默认为 256
private int numberOfHardLinks;
// 构造方法进行属性的初始化
@ -391,7 +392,7 @@ public class SoftCache implements Cache {
private final Object key;
SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
// 指向value的引用是软引用并且关联了 引用队列
// 指向 value 的引用是软引用,并且关联了 引用队列
super(value, garbageCollectionQueue);
// 强引用
this.key = key;
@ -400,7 +401,7 @@ public class SoftCache implements Cache {
@Override
public void putObject(Object key, Object value) {
// 清除已经被GC的缓存项
// 清除已经被 GC 的缓存项
removeGarbageCollectedItems();
// 添加缓存
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
@ -408,7 +409,7 @@ public class SoftCache implements Cache {
private void removeGarbageCollectedItems() {
SoftEntry sv;
// 遍历queueOfGarbageCollectedEntries集合清除已经被GC的缓存项value
// 遍历 queueOfGarbageCollectedEntries集合清除已经被 GC 的缓存项 value
while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
@ -418,21 +419,21 @@ public class SoftCache implements Cache {
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
// 用一个软引用指向 key对应的缓存项
// 用一个软引用指向 key 对应的缓存项
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
// 检测缓存中是否有对应的缓存项
if (softReference != null) {
// 获取softReference引用的value
// 获取 softReference 引用的 value
result = softReference.get();
// 如果softReference引用的对象已经被GC则从缓存中清除对应的缓存项
// 如果 softReference 引用的对象已经被 GC则从缓存中清除对应的缓存项
if (result == null) {
delegate.removeObject(key);
} else {
synchronized (hardLinksToAvoidGarbageCollection) {
// 将缓存项的value添加到hardLinksToAvoidGarbageCollection集合中保存
// 将缓存项的 value 添加到 hardLinksToAvoidGarbageCollection集合 中保存
hardLinksToAvoidGarbageCollection.addFirst(result);
// 如果hardLinksToAvoidGarbageCollection的容积已经超过numberOfHardLinks
// 则将最老的缓存项从hardLinksToAvoidGarbageCollection中清除FIFO
// 如果 hardLinksToAvoidGarbageCollection 的容积已经超过 numberOfHardLinks
// 则将最老的缓存项从 hardLinksToAvoidGarbageCollection 中清除FIFO
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
hardLinksToAvoidGarbageCollection.removeLast();
}
@ -444,7 +445,7 @@ public class SoftCache implements Cache {
@Override
public Object removeObject(Object key) {
// 清除指定的缓存项之前也会先清理被GC的缓存项
// 清除指定的缓存项之前,也会先清理被 GC 的缓存项
removeGarbageCollectedItems();
return delegate.removeObject(key);
}
@ -456,7 +457,7 @@ public class SoftCache implements Cache {
// 清理强引用集合
hardLinksToAvoidGarbageCollection.clear();
}
// 清理被GC的缓存项
// 清理被 GC 的缓存项
removeGarbageCollectedItems();
// 清理最底层的缓存项
delegate.clear();
@ -479,17 +480,17 @@ public class SoftCache implements Cache {
}
```
WeakCache的实现与SoftCache基本类似唯一的区别在于其中使用WeakEntry继承了WeakReference封装真正的 value 对象,其他实现完全一样。
WeakCache 的实现与 SoftCache 基本类似,唯一的区别在于其中使用 WeakEntry继承了WeakReference封装真正的 value对象其他实现完全一样。
另外还有ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache等。ScheduledCache是周期性清理缓存的装饰器它的clearInterval字段记录了两次缓存清理之间的时间间隔默认是一小时lastClear字段记录了最近一次清理的时间戳。ScheduledCache 的getObject()、putObject()、removeObject()等核心方法,在执行时都会根据这两个字段检测是否需要进行清理操作,清理操作会清空缓存中所有缓存项。
另外,还有 ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache 等。ScheduledCache 是周期性清理缓存的装饰器,它的 clearInterval字段 记录了两次缓存清理之间的时间间隔默认是一小时lastClear字段 记录了最近一次清理的时间戳。 ScheduledCache 的 getObject()、putObject()、removeObject() 等核心方法,在执行时都会根据这两个字段检测是否需要进行清理操作,清理操作会清空缓存中所有缓存项。
LoggingCache 在 Cache 的基础上提供了日志功能,它通过 hit 字段和 request 字段记录了 Cache 的命中次数和访问次数。在 LoggingCache.getObject()方法中,会统计命中次数和访问次数 这两个指标,井按照指定的日志输出方式输出命中率。
LoggingCache 在 Cache 的基础上提供了日志功能,它通过 hit字段 和 request字段 记录了 Cache 的命中次数和访问次数。在 LoggingCache.getObject()方法 中,会统计命中次数和访问次数 这两个指标,井按照指定的日志输出方式输出命中率。
SynchronizedCache通过在每个方法上添加 synchronized关键字为Cache添加了同步功能有点类似于 JDK 中 Collections 的 SynchronizedCollection 内部类。
SynchronizedCache 通过在每个方法上添加 synchronized关键字 Cache 添加了同步功能,有点类似于 JDK 中 Collections 的 SynchronizedCollection内部类。
SerializedCache 提供了将 value 对象序列化的功能。SerializedCache 在添加缓存项时,会将 value 对应的 Java 对象进行序列化,井将序列化后的 byte[] 数组作为 value 存入缓存 。 SerializedCache 在获取缓存项时,会将缓存项中的 byte[] 数组反序列化成 Java 对象。不使用 SerializedCache 装饰器进行装饰的话,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程,以及缓存中的对象。而 使用SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。 SerializedCache 使用的序列化方式是 Java 原生序列化。
SerializedCache 提供了将 value对象 序列化的功能。SerializedCache 在添加缓存项时,会将 value 对应的 Java对象 进行序列化,井将序列化后的 byte[]数组 作为 value 存入缓存 。 SerializedCache 在获取缓存项时,会将缓存项中的 byte[]数组 反序列化成 Java对象。不使用 SerializedCache装饰器 进行装饰的话,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程,以及缓存中的对象。而使用 SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。 SerializedCache 使用的序列化方式是 Java原生序列化。
## 2 CacheKey
在 Cache 中唯一确定一个缓存项,需要使用缓存项的 key进行比较MyBatis 中因为涉及动态 SQL 等 多方面因素, 其缓存项的 key 不能仅仅通过一个 String 表示,所以 MyBatis 提供了 CacheKey 类来表示缓存项的 key在一个 CacheKey 对象中可以封装多个影响缓存项的因素。 CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey 对象是否相同。
在 Cache 中唯一确定一个缓存项,需要使用缓存项的 key 进行比较MyBatis 中因为涉及 动态SQL 等多方面因素, 其缓存项的 key 不能仅仅通过一个 String 表示,所以 MyBatis 提供了 CacheKey类 来表示缓存项的 key在一个 CacheKey对象 中可以封装多个影响缓存项的因素。 CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey对象 是否相同。
```java
public class CacheKey implements Cloneable, Serializable {
@ -548,16 +549,16 @@ public class CacheKey implements Cloneable, Serializable {
}
/**
* CacheKey重写了equals()和hashCode()方法,这两个方法使用上面介绍
* 的count、checksum、hashcode、updateList比较两个CacheKey对象是否相同
* CacheKey重写了 equals() hashCode()方法,这两个方法使用上面介绍
* 的 count、checksum、hashcode、updateList 比较两个 CacheKey对象 是否相同
*/
@Override
public boolean equals(Object object) {
// 如果为同一对象直接返回true
// 如果为同一对象,直接返回 true
if (this == object) {
return true;
}
// 如果object都不是CacheKey类型直接返回false
// 如果 object 都不是 CacheKey类型直接返回 false
if (!(object instanceof CacheKey)) {
return false;
}
@ -565,7 +566,7 @@ public class CacheKey implements Cloneable, Serializable {
// 类型转换一下
final CacheKey cacheKey = (CacheKey) object;
// 依次比较hashcode、checksum、count如果不等直接返回false
// 依次比较 hashcode、checksum、count如果不等直接返回 false
if (hashcode != cacheKey.hashcode) {
return false;
}
@ -576,7 +577,7 @@ public class CacheKey implements Cloneable, Serializable {
return false;
}
// 比较updateList中的元素是否相同不同直接返回false
// 比较 updateList 中的元素是否相同,不同直接返回 false
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
@ -612,6 +613,6 @@ public class CacheKey implements Cloneable, Serializable {
```
## 3 小结
至此 Mybatis 的基础支持层的主要模块就分析完了。本模块首先介绍了 MyBatis 对 Java 反射机制的封装;然后分析了类型转换 TypeHandler 组件,了解了 MyBatis 如何实现数据在 Java 类型与 JDBC 类型之间的转换。
至此 Mybatis 的基础支持层的主要模块就分析完了。本模块首先介绍了 MyBatis 对 Java反射机制的封装然后分析了类型转换 TypeHandler组件了解了 MyBatis 如何实现数据在 Java类型 与 JDBC类型 之间的转换。
之后分析了MyBatis 提供的 DataSource 模块的实现和原理,深入解析了 MyBatis 自带的连接池PooledDataSource 的详细实现;后面紧接着介绍了 Transaction 模块的功能。然后分析了 binding 模块如何将 Mapper 接口与映射配置信息相关联,以及其中的原理。最后介绍了 MyBatis 的缓存模块,分析了 Cache 接口以及多个实现类的具体实现它们是Mybatis中一级缓存和二级缓存的基础。
之后分析了 MyBatis 提供的 DataSource模块 的实现和原理,深入解析了 MyBatis 自带的连接池 PooledDataSource 的详细实现;后面紧接着介绍了 Transaction模块 的功能。然后分析了 binding模块 如何将 Mapper接口 与映射配置信息相关联,以及其中的原理。最后介绍了 MyBatis 的缓存模块,分析了 Cache接口 以及多个实现类的具体实现,它们是 Mybatis 中一级缓存和二级缓存的基础。

@ -1,6 +1,6 @@
spring框架的IoC容器初始化一样mybatis也会通过定位、解析相应的配置文件完成自己的初始化。mybatis的配置文件主要有mybatis-config.xml核心配置文件及一系列映射配置文件另外mybatis也会根据注解进行配置。
Spring框架 的 IoC容器初始化 一样Mybatis 也会通过定位、解析相应的配置文件完成自己的初始化。Mybatis 的配置文件主要有 mybatis-config.xml核心配置文件 及一系列映射配置文件另外Mybatis 也会根据注解进行配置。
## 1 BaseBuilder
mybatis初始化的主要内容是加载并解析mybatis-config.xml配置文件、映射配置文件以及相关的注解信息。mybatis的初始化入口是SqlSessionFactoryBuilder的build()方法。
Mybatis初始化 的主要内容是加载并解析 mybatis-config.xml配置文件、映射配置文件以及相关的注解信息。Mybatis 的初始化入口是 SqlSessionFactoryBuilder 的 build()方法。
```java
public class SqlSessionFactoryBuilder {
@ -17,15 +17,15 @@ public class SqlSessionFactoryBuilder {
}
/**
* build方法的主要实现
* build()方法 的主要实现
*/
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// SqlSessionFactory会创建XMLConfigBuilder对象来解析mybatis-config.xml配置文件
// XMLConfigBuilder继承自BaseBuilder抽象类顾名思义这一系的类使用了 建造者设计模式
// SqlSessionFactory 会创建 XMLConfigBuilder对象 来解析 mybatis-config.xml配置文件
// XMLConfigBuilder 继承自 BaseBuilder抽象类顾名思义这一系的类使用了 建造者设计模式
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析配置文件的内容 到Configuration对象根据到Configuration对象
// 创建DefaultSqlSessionFactory对象然后返回
// 解析配置文件的内容 到 Configuration对象根据 Configuration对象
// 创建 DefaultSqlSessionFactory对象然后返回
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
@ -44,17 +44,17 @@ public class SqlSessionFactoryBuilder {
return new DefaultSqlSessionFactory(config);
}
```
BaseBuilder中的核心字段如下
BaseBuilder 中的核心字段如下:
```java
public abstract class BaseBuilder {
// 保存了mybatis的几乎所以核心配置信息,全局唯一
// 保存了 Mybatis 的几乎所以核心配置信息,全局唯一
protected final Configuration configuration;
// 在mybatis-config.xml中可以通过<typeAliases>标签定义别名
// 在 mybatis-config.xml 中可以通过 <typeAliases>标签 定义别名
protected final TypeAliasRegistry typeAliasRegistry;
// 在mybatis-config.xml中可以通过<typeHandlers>标签添加自定义TypeHandler
// TypeHandler用于完成JDBC数据类型与Java类型的相互转换所有的TypeHandler
// 都保存在typeHandlerRegistry中
// 在 mybatis-config.xml 中可以通过 <typeHandlers>标签 添加 自定义TypeHandler
// TypeHandler 用于完成 JDBC数据类型 Java类型 的相互转换,所有的 TypeHandler
// 都保存在 typeHandlerRegistry
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
@ -64,38 +64,38 @@ public abstract class BaseBuilder {
}
}
```
BaseBuilder中的typeAliasRegistry和typeHandlerRegistry字段均来自于configuration通过BaseBuilder的构造方法可以看到详细内容。
BaseBuilder 中的 typeAliasRegistry typeHandlerRegistry字段 均来自于 configuration通过 BaseBuilder 的构造方法可以看到详细内容。
## 2 XMLConfigBuilder
XMLConfigBuilder是BaseBuilder的众多子类之一主要负责解析mybatis-config.xml配置文件。它通过调用parseConfiguration()方法实现整个解析过程其中mybatis-config.xml配置文件中的每个节点都被封装成了一个个相应的解析方法parseConfiguration()方法只是依次调用了这些解析方法而已。
XMLConfigBuilder BaseBuilder 的众多子类之一,主要负责解析 mybatis-config.xml配置文件。它通过调用 parseConfiguration()方法 实现整个解析过程其中mybatis-config.xml配置文件 中的每个节点都被封装成了一个个相应的解析方法parseConfiguration()方法 只是依次调用了这些解析方法而已。
```java
public class XMLConfigBuilder extends BaseBuilder {
// 标记是否解析过mybatis-config.xml文件
// 标记是否解析过 mybatis-config.xml文件
private boolean parsed;
// 用于解析mybatis-config.xml的解析器
// 用于解析 mybatis-config.xml 的解析器
private final XPathParser parser;
// 标识<environment>配置的名称,默认读取<environment>标签的default属性
// 标识 <environment>配置 的名称,默认读取 <environment>标签 default属性
private String environment;
// 创建并缓存Reflector对象
// 创建并缓存 Reflector对象
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
/**
* 解析的入口调用了parseConfiguration()进行后续的解析
* 解析的入口,调用了 parseConfiguration() 进行后续的解析
*/
public Configuration parse() {
// parsed标志位的处理
// parsed标志位 的处理
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 在mybatis-config.xml配置文件中查找<configuration>节点,并开始解析
// 在 mybatis-config.xml配置文件 中查找 <configuration>节点,并开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 根据root.evalNode("properties")中的值就可以知道具体是解析哪个标签的方法咯
// 根据 root.evalNode("properties") 中的值就可以知道具体是解析哪个标签的方法咯
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
@ -115,19 +115,19 @@ public class XMLConfigBuilder extends BaseBuilder {
}
}
```
mybatis中的标签很多,所以相对应的解析方法也很多,这里挑几个比较重要的标签进行分析。
Mybatis 中的标签很多,所以相对应的解析方法也很多,这里挑几个比较重要的标签进行分析。
### 2.1 解析&lt;typeHandlers&gt;标签
```java
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
// 处理<typeHandlers>下的所有子标签
// 处理 <typeHandlers> 下的所有子标签
for (XNode child : parent.getChildren()) {
// 处理<package>标签
// 处理 <package> 标签
if ("package".equals(child.getName())) {
// 获取指定的包名
String typeHandlerPackage = child.getStringAttribute("name");
// 通过typeHandlerRegistry的register(packageName)方法
// 扫描指定包中的所有TypeHandler类并进行注册
// 通过 typeHandlerRegistry register(packageName)方法
// 扫描指定包中的所有 TypeHandler类并进行注册
typeHandlerRegistry.register(typeHandlerPackage);
} else {
// Java数据类型
@ -156,29 +156,29 @@ mybatis中的标签很多所以相对应的解析方法也很多这里挑
### 2.2 解析&lt;environments&gt;标签
```java
/**
* mybatis可以配置多个<environment>环境,分别用于开发、测试及生产等,
* 但每个SqlSessionFactory实例只能选择其一
* Mybatis 可以配置多个 <environment>环境,分别用于开发、测试及生产等,
* 但每个 SqlSessionFactory实例 只能选择其一
*/
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// 如果未指定XMLConfigBuilder的environment字段则使用default属性指定的<environment>环境
// 如果未指定 XMLConfigBuilder environment字段则使用 default属性 指定的 <environment>环境
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 遍历<environment>节点
// 遍历 <environment>节点
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 实例化TransactionFactory
// 实例化 TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 创建DataSourceFactory和DataSource
// 创建 DataSourceFactory DataSource
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 创建的Environment对象中封装了上面的TransactionFactory对象和DataSource对象
// 创建的 Environment对象 中封装了上面的 TransactionFactory对象 DataSource对象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 为configuration注入environment属性值
// 为 configuration 注入 environment属性值
configuration.setEnvironment(environmentBuilder.build());
}
}
@ -186,38 +186,38 @@ mybatis中的标签很多所以相对应的解析方法也很多这里挑
}
```
### 2.3 解析&lt;databaseIdProvider&gt;标签
mybatis不像hibernate那样通过hql的方式直接帮助开发人员屏蔽不同数据库产品在sql语法上的差异针对不同的数据库产品mybatis往往要编写不同的sql语句。但在mybatis-config.xml配置文件中可以通过&lt;databaseIdProvider&gt;定义所有支持的数据库产品的databaseId然后在映射配置文件中定义sql语句节点时通过databaseId指定该sql语句应用的数据库产品,也可以达到类似的屏蔽数据库产品的功能。
Mybatis 不像 Hibernate 那样,通过 HQL 的方式直接帮助开发人员屏蔽不同数据库产品在 sql语法 上的差异,针对不同的数据库产品, Mybatis 往往要编写不同的 sql语句。但在 mybatis-config.xml配置文件 中,可以通过 &lt;databaseIdProvider&gt; 定义所有支持的数据库产品的 databaseId然后在映射配置文件中定义 sql语句节点 时,通过 databaseId 指定该 sql语句 应用的数据库产品,也可以达到类似的屏蔽数据库产品的功能。
mybatis初始化时会根据前面解析到的DataSource来确认当前使用的数据库产品然后在解析映射文件时加载不带databaseId属性的sql语句 及 带有databaseId属性的sql语句其中带有databaseId属性的sql语句优先级更高会被优先选中。
Mybatis 初始化时,会根据前面解析到的 DataSource 来确认当前使用的数据库产品,然后在解析映射文件时,加载不带 databaseId属性 sql语句 及带有 databaseId属性 sql语句其中带有 databaseId属性 sql语句 优先级更高,会被优先选中。
```java
/**
* 解析<databaseIdProvider>节点并创建指定的DatabaseIdProvider对象
* 该对象会返回databaseId的值mybatis会根据databaseId选择对应的sql语句去执行
* 解析 <databaseIdProvider>节点,并创建指定的 DatabaseIdProvider对象
* 该对象会返回 databaseId的值Mybatis 会根据 databaseId 选择对应的 sql语句 去执行
*/
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// 为了保证兼容性修改type取值
// 为了保证兼容性,修改 type取值
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
// 解析相关配置信息
Properties properties = context.getChildrenAsProperties();
// 创建DatabaseIdProvider对象
// 创建 DatabaseIdProvider对象
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
// 配置DatabaseIdProvider完成初始化
// 配置 DatabaseIdProvider完成初始化
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
// 根据前面解析到的DataSource获取databaseId并记录到configuration的configuration属性上
// 根据前面解析到的 DataSource 获取 databaseId并记录到 configuration configuration属性
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
```
mybatis提供了DatabaseIdProvider接口该接口的核心方法为getDatabaseId(DataSource dataSource)主要根据dataSource查找对应的databaseId并返回。该接口的主要实现类为VendorDatabaseIdProvider。
Mybatis 提供了 DatabaseIdProvider接口该接口的核心方法为 getDatabaseId(DataSource dataSource),主要根据 dataSource 查找对应的 databaseId 并返回。该接口的主要实现类为 VendorDatabaseIdProvider。
```java
public class VendorDatabaseIdProvider implements DatabaseIdProvider {
@ -247,20 +247,20 @@ public class VendorDatabaseIdProvider implements DatabaseIdProvider {
// 解析到数据库产品名
String productName = getDatabaseProductName(dataSource);
if (this.properties != null) {
// 根据<databaseIdProvider>子节点配置的数据库产品 和 databaseId之间的对应关系
// 确定最终使用的databaseId
// 根据 <databaseIdProvider>子节点 配置的数据库产品和 databaseId 之间的对应关系,
// 确定最终使用的 databaseId
for (Map.Entry<Object, Object> property : properties.entrySet()) {
if (productName.contains((String) property.getKey())) {
return (String) property.getValue();
}
}
// 没有合适的databaseId则返回null
// 没有合适的 databaseId则返回 null
return null;
}
return productName;
}
// 根据dataSource获取 数据库产品名的具体实现
// 根据 dataSource 获取 数据库产品名的具体实现
private String getDatabaseProductName(DataSource dataSource) throws SQLException {
Connection con = null;
try {
@ -280,31 +280,31 @@ public class VendorDatabaseIdProvider implements DatabaseIdProvider {
}
```
### 2.4 解析&lt;mappers&gt;标签
mybatis初始化时除了加载mybatis-config.xml文件还会加载全部的映射配置文件mybatis-config.xml文件的&lt;mapper&gt;节点会告诉mybatis去哪里查找映射配置文件,及使用了配置注解标识的接口。
Mybatis 初始化时,除了加载 mybatis-config.xml文件还会加载全部的映射配置文件mybatis-config.xml 文件的 &lt;mapper&gt;节点 会告诉 Mybatis 去哪里查找映射配置文件,及使用了配置注解标识的接口。
```java
/**
* 解析<mappers>节点本方法会创建XMLMapperBuilder对象加载映射文件如果映射配置文件存在
* 相应的Mapper接口也会加载相应的Mapper接口解析其中的注解 并完成向MapperRegistry的注册
* 解析 <mappers>节点,本方法会创建 XMLMapperBuilder对象 加载映射文件,如果映射配置文件存在
* 相应的 Mapper接口也会加载相应的 Mapper接口解析其中的注解 并完成向 MapperRegistry 的注册
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 处理<mappers>的子节点
// 处理 <mappers> 的子节点
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 获取<package>子节点中的包名
// 获取 <package>子节点 中的包名
String mapperPackage = child.getStringAttribute("name");
// 扫描指定的包目录然后向MapperRegistry注册Mapper接口
// 扫描指定的包目录,然后向 MapperRegistry 注册 Mapper接口
configuration.addMappers(mapperPackage);
} else {
// 获取<mapper>节点的resource、url、mapperClass属性这三个属性互斥只能有一个不为空
// mybatis提供了通过包名、映射文件路径、类全名、URL四种方式引入映射器。
// 映射器由一个接口和一个XML配置文件组成XML文件中定义了一个命名空间namespace
// 获取 <mapper>节点 resource、url、mapperClass属性这三个属性互斥只能有一个不为空
// Mybatis 提供了通过包名、映射文件路径、类全名、URL 四种方式引入映射器。
// 映射器由一个接口和一个 XML配置文件 组成XML文件 中定义了一个 命名空间namespace
// 它的值就是接口对应的全路径。
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 如果<mapper>节点指定了resource或是url属性则创建XMLMapperBuilder对象解析
// resource或是url属性指定的Mapper配置文件
// 如果 <mapper>节点 指定了 resource 或是 url属性则创建 XMLMapperBuilder对象 解析
// resource 或是 url属性 指定的 Mapper配置文件
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
@ -316,7 +316,7 @@ mybatis初始化时除了加载mybatis-config.xml文件还会加载全部
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 如果<mapper>节点指定了class属性则向MapperRegistry注册该Mapper接口
// 如果 <mapper>节点 指定了 class属性则向 MapperRegistry 注册 该Mapper接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
@ -328,38 +328,38 @@ mybatis初始化时除了加载mybatis-config.xml文件还会加载全部
}
```
## 3 XMLMapperBuilder
和XMLConfigBuilder一样XMLMapperBuilder也继承了BaseBuilder其主要负责解析映射配置文件其解析配置文件的入口方法也是parse()另外XMLMapperBuilder也将各个节点的解析过程拆分成了一个个小方法然后由configurationElement()方法统一调用。
XMLConfigBuilder 一样XMLMapperBuilder 也继承了 BaseBuilder其主要负责解析映射配置文件其解析配置文件的入口方法也是 parse()另外XMLMapperBuilder 也将各个节点的解析过程拆分成了一个个小方法,然后由 configurationElement()方法 统一调用。
```java
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
// 是否已经加载过该配置文件
if (!configuration.isResourceLoaded(resource)) {
// 解析<mapper>节点
// 解析 <mapper>节点
configurationElement(parser.evalNode("/mapper"));
// 将resource添加到configuration的loadedResources属性中
// 该属性是一个HashSet<String>类型的集合,其中记录了已经加载过的映射文件
// 将 resource 添加到 configuration loadedResources属性 中,
// 该属性是一个 HashSet<String>类型的集合,其中记录了已经加载过的映射文件
configuration.addLoadedResource(resource);
// 注册Mapper接口
// 注册 Mapper接口
bindMapperForNamespace();
}
// 处理configurationElement()方法中解析失败的<resultMap>节点
// 处理 configurationElement()方法 中解析失败的 <resultMap>节点
parsePendingResultMaps();
// 处理configurationElement()方法中解析失败的<cacheRef>节点
// 处理 configurationElement()方法 中解析失败的 <cacheRef>节点
parsePendingCacheRefs();
// 处理configurationElement()方法中解析失败的<statement>节点
// 处理 configurationElement()方法 中解析失败的 <statement>节点
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
// 获取<mapper>节点的namespace属性
// 获取 <mapper>节点 namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 使用MapperBuilderAssistant对象的currentNamespace属性 记录namespace命名空间
// 使用 MapperBuilderAssistant对象 currentNamespace属性 记录 namespace命名空间
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref>节点,后面的解析方法 也都见名知意
// 解析 <cache-ref>节点,后面的解析方法 也都见名知意
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
@ -372,37 +372,37 @@ public class XMLMapperBuilder extends BaseBuilder {
}
}
```
XMLMapperBuilder也根据配置文件进行了一系列节点解析我们着重分析一下比较重要且常见的&lt;resultMap&gt;节点和&lt;sql&gt;节点
XMLMapperBuilder 也根据配置文件进行了一系列节点解析,我们着重分析一下比较重要且常见的 &lt;resultMap&gt;节点 &lt;sql&gt;节点
### 3.1 解析&lt;resultMap&gt;节点
select语句查询得到的结果是一张二维表水平方向上是一个个字段垂直方向上是一条条记录。而Java是面向对象的程序设计语言对象是根据类的定义创建的类之间的引用关系可以认为是嵌套结构。JDBC编程中为了将结果集中的数据映射成VO对象我们需要自己写代码从结果集中获取数据然后将数据封装成对应的VO对象并设置好对象之间的关系这种ORM的过程中存在大量重复的代码。
select语句 查询得到的结果是一张二维表,水平方向上是一个个字段,垂直方向上是一条条记录。而 Java 是面向对象的程序设计语言对象是根据类的定义创建的类之间的引用关系可以认为是嵌套结构。JDBC编程 中,为了将结果集中的数据映射成 VO对象我们需要自己写代码从结果集中获取数据然后将数据封装成对应的 VO对象并设置好对象之间的关系这种 ORM 的过程中存在大量重复的代码。
mybatis通过&lt;resultMap&gt;节点定义了ORM规则可以满足大部分的映射需求减少重复代码提高开发效率。
Mybatis 通过 &lt;resultMap&gt;节点 定义了 ORM规则可以满足大部分的映射需求减少重复代码提高开发效率。
在分析&lt;resultMap&gt;节点的解析过程之前先看一下该过程使用的数据结构。每个ResultMapping对象记录了结果集中的一列与JavaBean中一个属性之间的映射关系。&lt;resultMap&gt;节点下除了&lt;discriminator&gt;子节点的其它子节点 都会被解析成对应的ResultMapping对象。
在分析 &lt;resultMap&gt;节点 的解析过程之前,先看一下该过程使用的数据结构。每个 ResultMapping对象 记录了结果集中的一列与 JavaBean 中一个属性之间的映射关系。&lt;resultMap&gt;节点 下除了 &lt;discriminator&gt;子节点 的其它子节点,都会被解析成对应的 ResultMapping对象。
```java
public class ResultMapping {
private Configuration configuration;
// 对应节点的property属性表示 该列进行映射的属性
// 对应节点的 property属性表示 该列进行映射的属性
private String property;
// 对应节点的column属性表示 从数据库中得到的列名或列名的别名
// 对应节点的 column属性表示 从数据库中得到的列名或列名的别名
private String column;
// 表示 一个JavaBean的完全限定名或一个类型别名
// 表示 一个 JavaBean 的完全限定名,或一个类型别名
private Class<?> javaType;
// 进行映射列的JDBC类型
// 进行映射列的 JDBC类型
private JdbcType jdbcType;
// 类型处理器
private TypeHandler<?> typeHandler;
// 该属性通过id引用了另一个<resultMap>节点,它负责将结果集中的一部分列映射成
// 它所关联的结果对象。这样我们就可以通过join方式进行关联查询然后直接映射成
// 该属性通过 id 引用了另一个 <resultMap>节点,它负责将结果集中的一部分列映射成
// 它所关联的结果对象。这样我们就可以通过 join方式 进行关联查询,然后直接映射成
// 多个对象,并同时设置这些对象之间的组合关系(nested嵌套的)
private String nestedResultMapId;
// 该属性通过id引用了另一个<select>节点它会把指定的列值传入select属性指定的
// select语句 作为参数进行查询。使用该属性可能会导致ORM中的N+1问题请谨慎使用
// 该属性通过 id 引用了另一个 <select>节点,它会把指定的列值传入 select属性 指定的
// select语句 作为参数进行查询。使用该属性可能会导致 ORM 中的 N+1问题请谨慎使用
private String nestedQueryId;
private Set<String> notNullColumns;
private String columnPrefix;
// 处理后的标志共有两个id和constructor
// 处理后的标志共有两个id constructor
private List<ResultFlag> flags;
private List<ResultMapping> composites;
private String resultSet;
@ -411,38 +411,38 @@ public class ResultMapping {
private boolean lazy;
}
```
另一个比较重要的类是ResultMap每个&lt;resultMap&gt;节点都会被解析成一个ResultMap对象其中每个节点所定义的映射关系则使用ResultMapping对象表示。
另一个比较重要的类是 ResultMap每个 &lt;resultMap&gt;节点 都会被解析成一个 ResultMap对象其中每个节点所定义的映射关系则使用 ResultMapping对象 表示。
```java
public class ResultMap {
private Configuration configuration;
// 这些属性一一对应了<resultMap>中的属性
// 这些属性一一对应了 <resultMap> 中的属性
private String id;
private Class<?> type;
// 记录了除<discriminator>节点之外的其它映射关系(即ResultMapping对象集合)
// 记录了除 <discriminator>节点 之外的其它映射关系(即ResultMapping对象集合)
private List<ResultMapping> resultMappings;
// 记录了映射关系中带有ID标志的映射关系<id>节点和<constructor>节点的<idArg>子节点
// 记录了映射关系中带有 ID标志 的映射关系,如:<id>节点 <constructor>节点 <idArg>子节点
private List<ResultMapping> idResultMappings;
// 记录了映射关系中带有Constructor标志的映射关系<constructor>所有子元素
// 记录了映射关系中带有 Constructor标志 的映射关系,如:<constructor>所有子元素
private List<ResultMapping> constructorResultMappings;
// 记录了映射关系中不带有Constructor标志的映射关系
// 记录了映射关系中不带有 Constructor标志 的映射关系
private List<ResultMapping> propertyResultMappings;
// 记录了所有映射关系中涉及的column属性的集合
// 记录了所有映射关系中涉及的 column属性 的集合
private Set<String> mappedColumns;
// 记录了所有映射关系中涉及的property属性的集合
// 记录了所有映射关系中涉及的 property属性 的集合
private Set<String> mappedProperties;
// 鉴别器,对应<discriminator>节点
// 鉴别器,对应 <discriminator>节点
private Discriminator discriminator;
// 是否含有嵌套的结果映射如果某个映射关系中存在resultMap属性
// 且不存在resultSet属性则为true
// 是否含有嵌套的结果映射,如果某个映射关系中存在 resultMap属性
// 且不存在 resultSet属性则为true
private boolean hasNestedResultMaps;
// 是否含有嵌套查询如果某个属性映射存在select属性则为true
// 是否含有嵌套查询,如果某个属性映射存在 select属性则为true
private boolean hasNestedQueries;
// 是否开启自动映射
private Boolean autoMapping;
}
```
了解了ResultMapping 和ResultMap 记录的信息之后,下面开始介绍&lt;resultMap&gt;节点的解析过程。在XMLMapperBuilder中通过resultMapElements()方法解析映射配置文件中的全部&lt;resultMap&gt;节点,该方法会循环调用resultMapElement()方法处理每个resultMap节点。
了解了 ResultMapping 和 ResultMap 记录的信息之后,下面开始介绍 &lt;resultMap&gt;节点 的解析过程。在 XMLMapperBuilder 中通过 resultMapElements()方法 解析映射配置文件中的全部 &lt;resultMap&gt;节点,该方法会循环调用 resultMapElement()方法 处理每个 &lt;resultMap&gt; 节点。
```java
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
@ -450,42 +450,42 @@ public class ResultMap {
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// <resultMap>的id属性默认值会拼装所有父节点的id 或value或property属性值
// <resultMap> id属性默认值会拼装所有父节点的 id 或 value property属性值
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// <resultMap>的type属性表示结果集将被映射成type指定类型的对象
// <resultMap> type属性表示结果集将被映射成 type 指定类型的对象
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 该属性指定了该<resultMap>节点的继承关系
// 该属性指定了该 <resultMap>节点 的继承关系
String extend = resultMapNode.getStringAttribute("extends");
// 为true则启动自动映射功能该功能会自动查找与列明相同的属性名并调用setter方法
// 为false则需要在<resultMap>节点内注明映射关系才会调用对应的setter方法
// 为 true 则启动自动映射功能,该功能会自动查找与列明相同的属性名,并调用 setter方法
// 为 false则需要在 <resultMap>节点 内注明映射关系才会调用对应的 setter方法
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 解析type类型
// 解析 type类型
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
// 该集合用来记录解析结果
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
// 获取并处理<resultMap>的子节点
// 获取并处理 <resultMap> 的子节点
List<XNode> resultChildren = resultMapNode.getChildren();
// child单数形式children复数形式
// child 单数形式children 复数形式
for (XNode resultChild : resultChildren) {
// 处理<constructor>节点
// 处理 <constructor>节点
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
// 处理<discriminator>节点
// 处理 <discriminator>节点
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// 处理<id>,<result>,<association>,<collection>等节点
// 处理 <id>, <result>, <association>, <collection> 等节点
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 创建ResultMapping对象并添加到resultMappings集合
// 创建 ResultMapping对象并添加到 resultMappings集合
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
@ -498,10 +498,10 @@ public class ResultMap {
}
}
```
从上面的代码我们可以看到,mybatis从&lt;resultMap&gt;节点获取到id属性和type属性值之后就会通过XMLMapperBuilder的buildResultMappingFromContext()方法为&lt;result&gt;节点创建对应的ResultMapping 对象。
从上面的代码我们可以看到,Mybatis 从 &lt;resultMap&gt;节点 获取到 id属性 和 type属性值 之后,就会通过 XMLMapperBuilder 的 buildResultMappingFromContext()方法 为 &lt;result&gt;节点 创建对应的 ResultMapping对象。
```java
/**
* 根据上下文环境构建ResultMapping
* 根据上下文环境构建 ResultMapping
*/
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
// 获取各个节点的属性,见文知意
@ -527,30 +527,30 @@ public class ResultMap {
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 创建ResultMapping对象并返回
// 创建 ResultMapping对象 并返回
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
```
得到ResultMapping对象集合之后会调用ResultMapResolver的resolve()方法该方法会调用MapperBuilderAssistant的addResultMap()方法创建ResultMap对象并将ResultMap对象添加到Configuration的resultMaps集合中保存。
得到 ResultMapping对象集合 之后,会调用 ResultMapResolver resolve()方法,该方法会调用 MapperBuilderAssistant addResultMap()方法 创建 ResultMap对象并将 ResultMap对象 添加到 Configuration resultMaps集合 中保存。
```java
public class MapperBuilderAssistant extends BaseBuilder {
public ResultMap addResultMap(String id, Class<?> type, String extend,
Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
// ResultMap的完整id是"namespace.id"的格式
// ResultMap 完整id "namespace.id" 的格式
id = applyCurrentNamespace(id, false);
// 获取 父ResultMap的完整id
// 获取 父ResultMap 完整id
extend = applyCurrentNamespace(extend, true);
// 针对extend属性进行的处理
// 针对 extend属性 进行的处理
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
// 父ResultMap对象
ResultMap resultMap = configuration.getResultMap(extend);
// 父ResultMap对象的ResultMapping集合
// 父ResultMap对象 ResultMapping集合
List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
// 删除需要覆盖的ResultMapping集合
// 删除需要覆盖的 ResultMapping集合
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
@ -568,7 +568,7 @@ public class MapperBuilderAssistant extends BaseBuilder {
}
}
}
// 添加需要被继承下来的ResultMapping集合
// 添加需要被继承下来的 ResultMapping集合
resultMappings.addAll(extendedResultMappings);
}
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
@ -580,7 +580,7 @@ public class MapperBuilderAssistant extends BaseBuilder {
}
```
### 3.2 解析&lt;sql&gt;节点
在映射配置文件中,可以使用&lt;sql&gt;节点定义可重用的SQL语句片段当需要重用&lt;sql&gt;节点中定义的SQL语句片段时只需要使用&lt;include&gt;节点引入相应的片段即可这样在编写SQL语句以及维护这些SQL语句时都会比较方便。XMLMapperBuilder的sqlElement()方法负责解析映射配置文件中定义的全部&lt;sql&gt;节点。
在映射配置文件中,可以使用 &lt;sql&gt;节点 定义可重用的 SQL语句片段当需要重用 &lt;sql&gt;节点 中定义的 SQL语句片段 时,只需要使用 &lt;include&gt;节点 引入相应的片段即可,这样,在编写 SQL语句 以及维护这些 SQL语句 都会比较方便。XMLMapperBuilder sqlElement()方法 负责解析映射配置文件中定义的 全部&lt;sql&gt;节点。
```java
private void sqlElement(List<XNode> list) throws Exception {
if (configuration.getDatabaseId() != null) {
@ -590,27 +590,27 @@ public class MapperBuilderAssistant extends BaseBuilder {
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
// 遍历<sql>节点
// 遍历 <sql>节点
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
// 为id添加命名空间
// 为 id 添加命名空间
id = builderAssistant.applyCurrentNamespace(id, false);
// 检测<sql>的databaseId与当前Configuration中记录的databaseId是否一致
// 检测 <sql> databaseId 与当前 Configuration 中记录的 databaseId 是否一致
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 记录到sqlFragments(Map<String, XNode>)中保存
// 记录到 sqlFragments(Map<String, XNode>) 中保存
sqlFragments.put(id, context);
}
}
}
```
## 4 XMLStatementBuilder
这一部分看的不是很懂,暂时保留,日后深入理解了再写。
## 5 绑定Mapper接口
通过之前对binding模块的解析可知每个映射配置文件的命名空间可以绑定一个Mapper接口并注册到MapperRegistry中。XMLMapperBuilder的bindMapperForNamespace()方法中完成了映射配置文件与对应Mapper 接
口的绑定。
通过之前对 binding模块 的解析可知,每个映射配置文件的命名空间可以绑定一个 Mapper接口并注册到 MapperRegistry中。XMLMapperBuilder 的 bindMapperForNamespace()方法 中,完成了映射配置文件与对应 Mapper接口 的绑定。
```java
public class XMLMapperBuilder extends BaseBuilder {
private void bindMapperForNamespace() {
@ -625,12 +625,12 @@ public class XMLMapperBuilder extends BaseBuilder {
//ignore, bound type is not required
}
if (boundType != null) {
// 是否已加载boundType接口
// 是否已加载 boundType接口
if (!configuration.hasMapper(boundType)) {
// 追加个"namespace:"的前缀并添加到Configuration的loadedResources集合中
// 追加个 "namespace:" 的前缀,并添加到 Configuration loadedResources集合
configuration.addLoadedResource("namespace:" + namespace);
// 添加到Configuration的mapperRegistry集合中另外往这个方法栈的更深处看 会发现
// 其创建了MapperAnnotationBuilder对象并调用了该对象的parse()方法解析Mapper接口
// 添加到 Configuration的mapperRegistry集合 中,另外,往这个方法栈的更深处看 会发现
// 其创建了 MapperAnnotationBuilder对象并调用了该对象的 parse()方法 解析 Mapper接口
configuration.addMapper(boundType);
}
}
@ -647,7 +647,7 @@ public class MapperRegistry {
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// 解析Mapper接口type中的信息
// 解析 Mapper接口 type 中的信息
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
@ -665,34 +665,34 @@ public class MapperAnnotationBuilder {
String resource = type.toString();
// 是否已经加载过该接口
if (!configuration.isResourceLoaded(resource)) {
// 检查是否加载过该接口对应的映射文件如果未加载则创建XMLMapperBuilder对象
// 检查是否加载过该接口对应的映射文件,如果未加载,则创建 XMLMapperBuilder对象
// 解析对应的映射文件,该过程就是前面介绍的映射配置文件解析过程
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析@CacheNamespace注解
// 解析 @CacheNamespace注解
parseCache();
// 解析@CacheNamespaceRef注解
// 解析 @CacheNamespaceRef注解
parseCacheRef();
// type接口的所有方法
// type接口 的所有方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
if (!method.isBridge()) {
// 解析SelectKey、ResultMap等注解并创建MappedStatement对象
// 解析 SelectKey、ResultMap 等注解,并创建 MappedStatement对象
parseStatement(method);
}
} catch (IncompleteElementException e) {
// 如果解析过程出现IncompleteElementException异常可能是因为引用了
// 如果解析过程出现 IncompleteElementException异常可能是因为引用了
// 未解析的注解,这里将出现异常的方法记录下来,后面提供补偿机制,重新进行解析
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 遍历configuration中的incompleteMethods集合集合中记录了未解析的方法
// 遍历 configuration 中的 incompleteMethods集合集合中记录了未解析的方法
// 重新调用这些方法进行解析
parsePendingMethods();
}
}
```
另外在MapperAnnotationBuilder的parse()方法中解析的注解都能在映射配置文件中找到与之对应的XML节点且两者的解析过程也非常相似。
另外,在 MapperAnnotationBuilder parse()方法 中解析的注解,都能在映射配置文件中找到与之对应的 XML节点且两者的解析过程也非常相似。

@ -0,0 +1,54 @@
## Linux 网络 IO 模型简介
Linux 的内核将所有外部设备都看做一个文件来操作对一个文件的读写操作会调用内核提供的系统命令返回一个fd (file descriptor文件描述符)。而对一个 socket 的读写也会有相应的描述符,称为 socket fd (socket 描述符),描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等一些属性)。根据UNIX网络编程对 I/O模型 的分类UNIX 提供了5种 I/O模型分别如下。
#### 1、阻塞IO模型
在内核将数据准备好之前系统调用会一直等待所有的套接字Socket传来数据默认的是阻塞方式。
![avatar](/images/Netty/阻塞IO模型.png)
Java 中的 socket.read()方法 最终会调用底层操作系统的 recvfrom方法OS 会判断来自网络的数据报是否准备好当数据报准备好了之后OS 就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后 socket.read() 就会解除阻塞,并得到网络数据的结果。
BIO中的阻塞就是阻塞在2个地方
1. OS 等待数据报通过网络发送过来,如果建立连接后数据一直没过来,就会白白浪费线程的资源;
2. 将数据从内核空间拷贝到用户空间。
在这2个时候我们的线程会一直被阻塞啥事情都不干。
#### 2、非阻塞IO模型
![avatar](/images/Netty/非阻塞IO模型.png)
每次应用程序询问内核是否有数据报准备好当有数据报准备好时就进行拷贝数据报的操作从内核拷贝到用户空间和拷贝完成返回的这段时间应用进程是阻塞的。但在没有数据报准备好时并不会阻塞程序内核直接返回未准备好的信号等待应用进程的下一次询问。但是轮寻对于CPU来说是较大的浪费一般只有在特定的场景下才使用。
从图中可以看到非阻塞IO 的 recvfrom调用 会立即得到一个返回结果(数据报是否准备好)我们可以根据返回结果继续执行不同的逻辑。而阻塞IO 的recvfrom调用如果无数据报准备好一定会被阻塞住。虽然 非阻塞IO 比 阻塞IO 少了一段阻塞的过程,但事实上 非阻塞IO 这种方式也是低效的,因为我们不得不使用轮询方法区一直问 OS“我的数据好了没啊”。
**BIO 不会在 拷贝数据之前 阻塞但会在将数据从内核空间拷贝到用户空间时阻塞。一定要注意这个地方Non-Blocking 还是会阻塞的。**
#### 3、IO复用模型
Linux 提供 select/poll进程通过将一个或多个 fd 传递给 select 或 poll系统 调用,阻塞发生在 select/poll 操作上。select/poll 可以帮我们侦测多个 fd 是否处于就绪状态,它们顺序扫描 fd 是否就绪,但支持的 fd 数量有限因此它的使用也受到了一些制约。Linux 还提供了一个 epoll系统调用epoll 使用 基于事件驱动方式 代替 顺序扫描,因此性能更高,当有 fd 就绪时,立即回调函数 rollback。
![avatar](/images/Netty/IO复用模型.png)
#### 4、信号驱动IO模型
首先开启套接口信号驱动IO功能并通过系统调用 sigaction 执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为该进程生成一个 SIGIO信号通过信号回调通知应用程序调用 recvfrom 来读取数据,并通知主循环函数处理数据。
![avatar](/images/Netty/信号驱动IO模型.png)
#### 5、异步IO模型
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别是信号驱动IO 由内核通知我们何时可以开始一个 IO 操作异步IO模型 由内核通知我们 IO操作何时已经完成。
![avatar](/images/Netty/异步IO模型.png)
从这五种 IO模型的结构 也可以看出阻塞程度阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO效率是由低到高的。
## IO 多路复用技术
Java NIO 的核心类库中 多路复用器Selector 就是基于 epoll 的多路复用技术实现。
在 IO编程 过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 IO多路复用技术 进行处理。IO多路复用技术 通过把多个 IO 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比IO多路复用 的最大优势是系统开销小系统不需要创建新的额外进程或线程也不需要维护这些进程和线程的运行降低了系统的维护工作量节省了系统资源IO多路复用 的主要应用场景如下。
- 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字;
- 服务器需要同时处理多种网络协议的套接字。
目前支持 IO多路复用 的系统调用有 select、pselect、poll、epoll在 Linux网络编程 过程中,很长一段时间都使用 select 做轮询和网络事件通知,然而 select 的一些固有缺陷导致了它的应用受到了很大的限制,最终 Linux 选择了 epoll。epoll 与 select 的原理比较类似,为了克服 select 的缺点epoll 作了很多重大改进,现总结如下。
1. 支持一个进程打开的 socket描述符 (fd) 不受限制(仅受限于操作系统的最大文件句柄数)
2. IO效率 不会随着 FD 数目的增加而线性下降;
3. epoll的API更加简单。
值得说明的是,用来克服 select/poll 缺点的方法不只有 epoll, epoll 只是一种 Linux 的实现方案。

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Loading…
Cancel
Save