diff --git a/README.md b/README.md index e34d698..e988390 100644 --- a/README.md +++ b/README.md @@ -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 - 努力编写中... diff --git a/docs/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列.md b/docs/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列.md index 9b792df..f57a270 100644 --- a/docs/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列.md +++ b/docs/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列.md @@ -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 setMethods = new HashMap<>(); private final Map getMethods = new HashMap<>(); - /** key属性名称,value该属性setter方法的返回值类型 */ + /** key 属性名称,value 该属性 setter方法的返回值类型 */ private final Map> setTypes = new HashMap<>(); private final Map> getTypes = new HashMap<>(); - /** type的默认构造方法 */ + /** type 的默认构造方法 */ private Constructor defaultConstructor; /** 所有属性名称的集合 */ private Map 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, 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 instantiateClass(Class type, List> constructorArgTypes, List 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 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 { - /** 通过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 { } /** - * 可用于实现自定义的TypeHandler + * 可用于实现自定义的 TypeHandler */ public abstract class BaseTypeHandler extends TypeReference implements TypeHandler { @@ -344,13 +344,13 @@ public abstract class BaseTypeHandler extends TypeReference implements Typ public class IntegerTypeHandler extends BaseTypeHandler { /** - 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 { } } ``` -TypeHandler主要用于单个参数的类型转换,如果要将多个列的值转换成一个Java对象,可以在映射文件中定义合适的映射规则<resultMap> 完成映射。 +TypeHandler 主要用于单个参数的类型转换,如果要将多个列的值转换成一个 Java对象,可以在映射文件中定义合适的映射规则 <resultMap> 完成映射。 ### 2.3 TypeHandlerRegistry -TypeHandlerRegistry主要负责管理所有已知的TypeHandler,mybatis在初始化过程中会为所有已知的TypeHandler创建对象,并注册到TypeHandlerRegistry。 +TypeHandlerRegistry 主要负责管理所有已知的 TypeHandler,Mybatis 在初始化过程中会为所有已知的 TypeHandler 创建对象,并注册到 TypeHandlerRegistry。 ```java - //TypeHandlerRegistry中的核心字段 + // TypeHandlerRegistry 中的核心字段如下 - /** 该集合主要用于从结果集读取数据时,将数据从JDBC类型转换成Java类型 */ + /** 该集合主要用于从结果集读取数据时,将数据从 JDBC类型 转换成 Java类型 */ private final Map> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class); /** - * 记录了Java类型向指定JdbcType转换时,需要使用的TypeHandler对象。 - * 如:String可能转换成数据库的char、varchar等多种类型,所以存在一对多的关系 + * 记录了 Java类型 向指定 JdbcType 转换时,需要使用的 TypeHandler对象。 + * 如:String 可能转换成数据库的 char、varchar 等多种类型,所以存在一对多的关系 */ private final Map>> typeHandlerMap = new ConcurrentHashMap<>(); - /** key TypeHandler的类型;value 该TypeHandler类型对应的TypeHandler对象 */ + /** key:TypeHandler 的类型;value:该 TypeHandler类型 对应的 TypeHandler对象 */ private final Map, 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> resolverUtil = new ResolverUtil<>(); - // 查找指定包下的TypeHandler接口实现类 + // 查找指定包下的 TypeHandler接口实现类 resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName); Set>> 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配置文件中节点下 添加相应的 - * 节点配置,并指定自定义的TypeHandler实现类。mybatis在初始化时 - * 会解析该节点,并将TypeHandler类型的对象注册到TypeHandlerRegistry中 供mybatis后续使用 + * 进行 Java 及 JDBC基本数据类型 的 TypeHandler 注册 + * 除了注册 Mybatis 提供的 基本TypeHandler 外,我们也可以添加自定义的 TypeHandler + * 接口实现,在 mybatis-config.xml配置文件 中 节点 下添加相应的 + * 节点配置,并指定自定义的 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 TypeHandler getTypeHandler(Type type, JdbcType jdbcType) { if (ParamMap.class.equals(type)) { return null; } - // Java数据类型与JDBC数据类型的关系往往是一对多, - // 所以一般会先根据Java数据类型获取Map> - // 再根据JDBC数据类型获取对应的TypeHandler + // Java数据类型 与 JDBC数据类型 的关系往往是一对多, + // 所以一般会先根据 Java数据类型 获取 Map>对象 + // 再根据 JDBC数据类型 获取对应的 TypeHandler对象 Map> jdbcHandlerMap = getJdbcHandlerMap(type); TypeHandler handler = null; if (jdbcHandlerMap != null) { @@ -545,4 +545,4 @@ TypeHandlerRegistry其实就是一个容器,前面注册了一堆东西,也 return (TypeHandler) handler; } ``` -除了mabatis本身自带的TypeHandler实现,我们还可以添加自定义的TypeHandler实现类,在配置文件mybatis-config.xml中的<typeHandler>标签下配置好自定义TypeHandler,mybatis就会在初始化时解析该标签内容,完成自定义TypeHandler的注册。 \ No newline at end of file +除了 Mabatis 本身自带的 TypeHandler实现,我们还可以添加自定义的 TypeHandler实现类,在配置文件 mybatis-config.xml 中的 <typeHandler> 标签下配置好 自定义TypeHandler,Mybatis 就会在初始化时解析该标签内容,完成 自定义TypeHandler 的注册。 \ No newline at end of file diff --git a/docs/Mybatis/基础支持层/2、DataSource及Transaction模块.md b/docs/Mybatis/基础支持层/2、DataSource及Transaction模块.md index 56e346d..a14c52e 100644 --- a/docs/Mybatis/基础支持层/2、DataSource及Transaction模块.md +++ b/docs/Mybatis/基础支持层/2、DataSource及Transaction模块.md @@ -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 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 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(); - // 注册驱动到DriverManager,DriverProxy是UnpooledDataSource的内部类 - // 也是Driver的静态代理类 + // 注册驱动到 DriverManager,DriverProxy 是 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<PooledConnection>集合分别管理空闲状态的连接 和 活跃状态的连接。另外,PoolState还定义了一系列用于统计的字段。 +PoolState 主要用于管理 PooledConnection 对象状态,其通过持有两个 List<PooledConnection>集合 分别管理空闲状态的连接 和 活跃状态的连接。另外,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); } diff --git a/docs/Mybatis/基础支持层/3、binding模块.md b/docs/Mybatis/基础支持层/3、binding模块.md index 18b2518..11b01a0 100644 --- a/docs/Mybatis/基础支持层/3、binding模块.md +++ b/docs/Mybatis/基础支持层/3、binding模块.md @@ -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 { - // 映射文件中会存在一个节点,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; - // key:Mapper接口,value:MapperProxyFactory为Mapper接口创建代理对象的工厂 + // key:Mapper接口,value:MapperProxyFactory 为 Mapper接口 创建代理对象的工厂 private final Map, MapperProxyFactory> knownMappers = new HashMap<>(); - // 下面的两个重载方法 通过扫描指定的包目录,获取所有的Mapper接口 + // 下面的两个重载方法 通过扫描指定的包目录,获取所有的 Mapper接口 public void addMappers(String packageName) { addMappers(packageName, Object.class); } @@ -38,7 +39,7 @@ public class MapperRegistry { } public void addMapper(Class 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 getMapper(Class type, SqlSession sqlSession) { - // 获取type对应的MapperProxyFactory对象 + // 获取 type 对应的 MapperProxyFactory对象 final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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> getMappers() { return Collections.unmodifiableCollection(knownMappers.keySet()); } - // 初始化的时候会持有Configuration对象 + // 初始化的时候会持有 Configuration对象 public MapperRegistry(Configuration config) { this.config = config; } - // 是否存在指定的MapperProxyFactory + // 是否存在指定的 MapperProxyFactory public boolean hasMapper(Class type) { return knownMappers.containsKey(type); } } ``` -MapperProxyFactory主要负责创建代理对象。 +MapperProxyFactory 主要负责创建代理对象。 ```java public class MapperProxyFactory { // 要创建的动态代理对象 所实现的接口 private final Class mapperInterface; - // 缓存mapperInterface接口中Method对象和其对应的MapperMethod对象 + // 缓存 mapperInterface接口 中 Method对象 和其对应的 MapperMethod对象 private final Map methodCache = new ConcurrentHashMap<>(); - // 初始化时为mapperInterface注入值 + // 初始化时为 mapperInterface 注入值 public MapperProxyFactory(Class mapperInterface) { this.mapperInterface = mapperInterface; } @@ -114,15 +115,15 @@ public class MapperProxyFactory { } public T newInstance(SqlSession sqlSession) { - // 每都会创建一个新的MapperProxy对象 + // 每次都会创建一个新的 MapperProxy对象 final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } /** - * 非常眼熟的JDK动态代理 代码,创建了实现mapperInterface接口的代理对象 - * 根据国际惯例,mapperProxy对应的类 肯定实现了InvocationHandler接口, - * 为mapperInterface接口方法的调用织入统一处理逻辑 + * 非常眼熟的 JDK动态代理 代码,创建了实现 mapperInterface接口 的代理对象 + * 根据国际惯例,mapperProxy对应的类 肯定实现了 InvocationHandler接口, + * 为 mapperInterface接口方法的调用 织入统一处理逻辑 */ protected T newInstance(MapperProxy mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); @@ -130,17 +131,17 @@ public class MapperProxyFactory { } ``` ## 2 MapperProxy -MapperProxy实现了InvocationHandler接口,为Mapper接口的方法调用织入了统一处理。 +MapperProxy 实现了 InvocationHandler接口,为 Mapper接口 的方法调用织入了统一处理。 ```java public class MapperProxy implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; - // 记录关联的sqlSession对象 + // 记录关联的 sqlSession对象 private final SqlSession sqlSession; - // 对应的Mapper接口的Class对象 + // 对应的 Mapper接口 的 Class对象 private final Class mapperInterface; - // 用于缓存MapperMethod对象,key:Mapper接口中方法对应的Method对象, - // value:MapperMethod对象(该对象会完成参数转换 及 sql语句的执行功能) + // 用于缓存 MapperMethod对象,key:Mapper接口 中方法对应的 Method对象, + // value:MapperMethod对象(该对象会完成参数转换 及 sql语句 的执行功能) private final Map methodCache; public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) { @@ -152,7 +153,7 @@ public class MapperProxy 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 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 - * 类型的容器 记录了参数在参数列表中的位置索引 与 参数名之间的对应关系,key参数在参数列表中的索引位置, + * 顾名思义,这是一个处理 Mapper接口 中 方法参数列表的解析器,它使用了一个 SortedMap + * 类型的容器,记录了参数在参数列表中的位置索引 与 参数名之间的对应关系,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 Object executeForMany(SqlSession sqlSession, Object[] args) { List 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 Object convertToDeclaredCollection(Configuration config, List 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 Object convertToArray(List 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 Map executeForMap(SqlSession sqlSession, Object[] args) { Map 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 Cursor executeForCursor(SqlSession sqlSession, Object[] args) { Cursor result; @@ -544,4 +546,4 @@ public class MapperMethod { return result; } } -``` +``` \ No newline at end of file diff --git a/docs/Mybatis/基础支持层/4、缓存模块.md b/docs/Mybatis/基础支持层/4、缓存模块.md index b5375b9..c997281 100644 --- a/docs/Mybatis/基础支持层/4、缓存模块.md +++ b/docs/Mybatis/基础支持层/4、缓存模块.md @@ -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和value,key一般为CacheKey对象 + * 存入缓存的 key 和 value,key 一般为 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 -PerpetualCache(Perpetual:永恒的,持续的)在缓存模块中扮演着被装饰的角色,其实现比较简单,底层使用HashMap记录缓存项,也是通过该HashMap对象的方法实现的Cache接口中定义的相应方法。 +PerpetualCache(Perpetual:永恒的,持续的)在缓存模块中扮演着被装饰的角色,其实现比较简单,底层使用 HashMap 记录缓存项,也是通过该 HashMap对象 的方法实现的 Cache接口 中定义的相应方法。 ```java public class PerpetualCache implements Cache { - // Cache对象的唯一标识 + // Cache对象 的唯一标识 private final String id; - // 其所有的缓存功能实现,都是基于JDK的HashMap提供的方法 + // 其所有的缓存功能实现,都是基于 JDK 的 HashMap 提供的方法 private Map 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 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 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 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 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 - // 记录的顺序是accessOrder,即,LinkedHashMap.get()方法会改变其中元素的顺序 + // 初始化 keyMap,同时指定该 Map 的初始容积及加载因子,第三个参数true 表示 该LinkedHashMap + // 记录的顺序是 accessOrder,即,LinkedHashMap.get()方法 会改变其中元素的顺序 keyMap = new LinkedHashMap(size, .75F, true) { private static final long serialVersionUID = 4267176411845948333L; - // 当调用LinkedHashMap.put()方法时,该方法会被调用 + // 当调用 LinkedHashMap.put()方法 时,该方法会被调用 @Override protected boolean removeEldestEntry(Map.Entry 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 hardLinksToAvoidGarbageCollection; - // 引用队列,用于记录已经被GC的缓存项所对应的SoftEntry对象 + // 引用队列,用于记录已经被 GC 的缓存项所对应的 SoftEntry对象 private final ReferenceQueue 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 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 softReference = (SoftReference) 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中一级缓存和二级缓存的基础。 \ No newline at end of file +之后分析了 MyBatis 提供的 DataSource模块 的实现和原理,深入解析了 MyBatis 自带的连接池 PooledDataSource 的详细实现;后面紧接着介绍了 Transaction模块 的功能。然后分析了 binding模块 如何将 Mapper接口 与映射配置信息相关联,以及其中的原理。最后介绍了 MyBatis 的缓存模块,分析了 Cache接口 以及多个实现类的具体实现,它们是 Mybatis 中一级缓存和二级缓存的基础。 \ No newline at end of file diff --git a/docs/Mybatis/核心处理层/1、MyBatis初始化.md b/docs/Mybatis/核心处理层/1、MyBatis初始化.md index 84e79ed..16f8c97 100644 --- a/docs/Mybatis/核心处理层/1、MyBatis初始化.md +++ b/docs/Mybatis/核心处理层/1、MyBatis初始化.md @@ -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中可以通过标签定义别名 + // 在 mybatis-config.xml 中可以通过 标签 定义别名 protected final TypeAliasRegistry typeAliasRegistry; - // 在mybatis-config.xml中可以通过标签添加自定义TypeHandler - // TypeHandler用于完成JDBC数据类型与Java类型的相互转换,所有的TypeHandler - // 都保存在typeHandlerRegistry中 + // 在 mybatis-config.xml 中可以通过 标签 添加 自定义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; - // 标识配置的名称,默认读取标签的default属性 + // 标识 配置 的名称,默认读取 标签 的 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配置文件中查找节点,并开始解析 + // 在 mybatis-config.xml配置文件 中查找 节点,并开始解析 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 解析<typeHandlers>标签 ```java private void typeHandlerElement(XNode parent) throws Exception { if (parent != null) { - // 处理下的所有子标签 + // 处理 下的所有子标签 for (XNode child : parent.getChildren()) { - // 处理标签 + // 处理 标签 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 解析<environments>标签 ```java /** - * mybatis可以配置多个环境,分别用于开发、测试及生产等, - * 但每个SqlSessionFactory实例只能选择其一 + * Mybatis 可以配置多个 环境,分别用于开发、测试及生产等, + * 但每个 SqlSessionFactory实例 只能选择其一 */ private void environmentsElement(XNode context) throws Exception { if (context != null) { - // 如果未指定XMLConfigBuilder的environment字段,则使用default属性指定的环境 + // 如果未指定 XMLConfigBuilder 的 environment字段,则使用 default属性 指定的 环境 if (environment == null) { environment = context.getStringAttribute("default"); } - // 遍历节点 + // 遍历 节点 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 解析<databaseIdProvider>标签 -mybatis不像hibernate那样,通过hql的方式直接帮助开发人员屏蔽不同数据库产品在sql语法上的差异,针对不同的数据库产品,mybatis往往要编写不同的sql语句。但在mybatis-config.xml配置文件中,可以通过<databaseIdProvider>定义所有支持的数据库产品的databaseId,然后在映射配置文件中定义sql语句节点时,通过databaseId指定该sql语句应用的数据库产品,也可以达到类似的屏蔽数据库产品的功能。 +Mybatis 不像 Hibernate 那样,通过 HQL 的方式直接帮助开发人员屏蔽不同数据库产品在 sql语法 上的差异,针对不同的数据库产品, Mybatis 往往要编写不同的 sql语句。但在 mybatis-config.xml配置文件 中,可以通过 <databaseIdProvider> 定义所有支持的数据库产品的 databaseId,然后在映射配置文件中定义 sql语句节点 时,通过 databaseId 指定该 sql语句 应用的数据库产品,也可以达到类似的屏蔽数据库产品的功能。 -mybatis初始化时,会根据前面解析到的DataSource来确认当前使用的数据库产品,然后在解析映射文件时,加载不带databaseId属性的sql语句 及 带有databaseId属性的sql语句,其中,带有databaseId属性的sql语句优先级更高,会被优先选中。 +Mybatis 初始化时,会根据前面解析到的 DataSource 来确认当前使用的数据库产品,然后在解析映射文件时,加载不带 databaseId属性 的 sql语句 及带有 databaseId属性 的 sql语句,其中,带有 databaseId属性 的 sql语句 优先级更高,会被优先选中。 ```java /** - * 解析节点,并创建指定的DatabaseIdProvider对象, - * 该对象会返回databaseId的值,mybatis会根据databaseId选择对应的sql语句去执行 + * 解析 节点,并创建指定的 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) { - // 根据子节点配置的数据库产品 和 databaseId之间的对应关系, - // 确定最终使用的databaseId + // 根据 子节点 配置的数据库产品和 databaseId 之间的对应关系, + // 确定最终使用的 databaseId for (Map.Entry 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 解析<mappers>标签 -mybatis初始化时,除了加载mybatis-config.xml文件,还会加载全部的映射配置文件,mybatis-config.xml文件的<mapper>节点会告诉mybatis去哪里查找映射配置文件,及使用了配置注解标识的接口。 +Mybatis 初始化时,除了加载 mybatis-config.xml文件,还会加载全部的映射配置文件,mybatis-config.xml 文件的 <mapper>节点 会告诉 Mybatis 去哪里查找映射配置文件,及使用了配置注解标识的接口。 ```java /** - * 解析节点,本方法会创建XMLMapperBuilder对象加载映射文件,如果映射配置文件存在 - * 相应的Mapper接口,也会加载相应的Mapper接口,解析其中的注解 并完成向MapperRegistry的注册 + * 解析 节点,本方法会创建 XMLMapperBuilder对象 加载映射文件,如果映射配置文件存在 + * 相应的 Mapper接口,也会加载相应的 Mapper接口,解析其中的注解 并完成向 MapperRegistry 的注册 */ private void mapperElement(XNode parent) throws Exception { if (parent != null) { - // 处理的子节点 + // 处理 的子节点 for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { - // 获取子节点中的包名 + // 获取 子节点 中的包名 String mapperPackage = child.getStringAttribute("name"); - // 扫描指定的包目录,然后向MapperRegistry注册Mapper接口 + // 扫描指定的包目录,然后向 MapperRegistry 注册 Mapper接口 configuration.addMappers(mapperPackage); } else { - // 获取节点的resource、url、mapperClass属性,这三个属性互斥,只能有一个不为空 - // mybatis提供了通过包名、映射文件路径、类全名、URL四种方式引入映射器。 - // 映射器由一个接口和一个XML配置文件组成,XML文件中定义了一个命名空间namespace, + // 获取 节点 的 resource、url、mapperClass属性,这三个属性互斥,只能有一个不为空 + // Mybatis 提供了通过包名、映射文件路径、类全名、URL 四种方式引入映射器。 + // 映射器由一个接口和一个 XML配置文件 组成,XML文件 中定义了一个 命名空间namespace, // 它的值就是接口对应的全路径。 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); - // 如果节点指定了resource或是url属性,则创建XMLMapperBuilder对象解析 - // resource或是url属性指定的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) { - // 如果节点指定了class属性,则向MapperRegistry注册该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)) { - // 解析节点 + // 解析 节点 configurationElement(parser.evalNode("/mapper")); - // 将resource添加到configuration的loadedResources属性中, - // 该属性是一个HashSet类型的集合,其中记录了已经加载过的映射文件 + // 将 resource 添加到 configuration 的 loadedResources属性 中, + // 该属性是一个 HashSet类型的集合,其中记录了已经加载过的映射文件 configuration.addLoadedResource(resource); - // 注册Mapper接口 + // 注册 Mapper接口 bindMapperForNamespace(); } - // 处理configurationElement()方法中解析失败的节点 + // 处理 configurationElement()方法 中解析失败的 节点 parsePendingResultMaps(); - // 处理configurationElement()方法中解析失败的节点 + // 处理 configurationElement()方法 中解析失败的 节点 parsePendingCacheRefs(); - // 处理configurationElement()方法中解析失败的节点 + // 处理 configurationElement()方法 中解析失败的 节点 parsePendingStatements(); } private void configurationElement(XNode context) { try { - // 获取节点的namespace属性 + // 获取 节点 的 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); - // 解析节点,后面的解析方法 也都见名知意 + // 解析 节点,后面的解析方法 也都见名知意 cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); @@ -372,37 +372,37 @@ public class XMLMapperBuilder extends BaseBuilder { } } ``` -XMLMapperBuilder也根据配置文件进行了一系列节点解析,我们着重分析一下比较重要且常见的<resultMap>节点和<sql>节点 +XMLMapperBuilder 也根据配置文件进行了一系列节点解析,我们着重分析一下比较重要且常见的 <resultMap>节点 和 <sql>节点 ### 3.1 解析<resultMap>节点 -select语句查询得到的结果是一张二维表,水平方向上是一个个字段,垂直方向上是一条条记录。而Java是面向对象的程序设计语言,对象是根据类的定义创建的,类之间的引用关系可以认为是嵌套结构。JDBC编程中,为了将结果集中的数据映射成VO对象,我们需要自己写代码从结果集中获取数据,然后将数据封装成对应的VO对象,并设置好对象之间的关系,这种ORM的过程中存在大量重复的代码。 +select语句 查询得到的结果是一张二维表,水平方向上是一个个字段,垂直方向上是一条条记录。而 Java 是面向对象的程序设计语言,对象是根据类的定义创建的,类之间的引用关系可以认为是嵌套结构。JDBC编程 中,为了将结果集中的数据映射成 VO对象,我们需要自己写代码从结果集中获取数据,然后将数据封装成对应的 VO对象,并设置好对象之间的关系,这种 ORM 的过程中存在大量重复的代码。 -mybatis通过<resultMap>节点定义了ORM规则,可以满足大部分的映射需求,减少重复代码,提高开发效率。 +Mybatis 通过 <resultMap>节点 定义了 ORM规则,可以满足大部分的映射需求,减少重复代码,提高开发效率。 -在分析<resultMap>节点的解析过程之前,先看一下该过程使用的数据结构。每个ResultMapping对象记录了结果集中的一列与JavaBean中一个属性之间的映射关系。<resultMap>节点下除了<discriminator>子节点的其它子节点 都会被解析成对应的ResultMapping对象。 +在分析 <resultMap>节点 的解析过程之前,先看一下该过程使用的数据结构。每个 ResultMapping对象 记录了结果集中的一列与 JavaBean 中一个属性之间的映射关系。<resultMap>节点 下除了 <discriminator>子节点 的其它子节点,都会被解析成对应的 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引用了另一个节点,它负责将结果集中的一部分列映射成 - // 它所关联的结果对象。这样我们就可以通过join方式进行关联查询,然后直接映射成 + // 该属性通过 id 引用了另一个 节点,它负责将结果集中的一部分列映射成 + // 它所关联的结果对象。这样我们就可以通过 join方式 进行关联查询,然后直接映射成 // 多个对象,并同时设置这些对象之间的组合关系(nested嵌套的) private String nestedResultMapId; - // 该属性通过id引用了另一个节点,它会把指定的列值传入 select属性 指定的 + // select语句 中作为参数进行查询。使用该属性可能会导致 ORM 中的 N+1问题,请谨慎使用 private String nestedQueryId; private Set notNullColumns; private String columnPrefix; - // 处理后的标志,共有两个:id和constructor + // 处理后的标志,共有两个:id 和 constructor private List flags; private List composites; private String resultSet; @@ -411,38 +411,38 @@ public class ResultMapping { private boolean lazy; } ``` -另一个比较重要的类是ResultMap,每个<resultMap>节点都会被解析成一个ResultMap对象,其中每个节点所定义的映射关系,则使用ResultMapping对象表示。 +另一个比较重要的类是 ResultMap,每个 <resultMap>节点 都会被解析成一个 ResultMap对象,其中每个节点所定义的映射关系,则使用 ResultMapping对象 表示。 ```java public class ResultMap { private Configuration configuration; - // 这些属性一一对应了中的属性 + // 这些属性一一对应了 中的属性 private String id; private Class type; - // 记录了除节点之外的其它映射关系(即,ResultMapping对象集合) + // 记录了除 节点 之外的其它映射关系(即,ResultMapping对象集合) private List resultMappings; - // 记录了映射关系中带有ID标志的映射关系,如:节点和节点的子节点 + // 记录了映射关系中带有 ID标志 的映射关系,如:节点 和 节点 的 子节点 private List idResultMappings; - // 记录了映射关系中带有Constructor标志的映射关系,如:所有子元素 + // 记录了映射关系中带有 Constructor标志 的映射关系,如:所有子元素 private List constructorResultMappings; - // 记录了映射关系中不带有Constructor标志的映射关系 + // 记录了映射关系中不带有 Constructor标志 的映射关系 private List propertyResultMappings; - // 记录了所有映射关系中涉及的column属性的集合 + // 记录了所有映射关系中涉及的 column属性 的集合 private Set mappedColumns; - // 记录了所有映射关系中涉及的property属性的集合 + // 记录了所有映射关系中涉及的 property属性 的集合 private Set mappedProperties; - // 鉴别器,对应节点 + // 鉴别器,对应 节点 private Discriminator discriminator; - // 是否含有嵌套的结果映射,如果某个映射关系中存在resultMap属性, - // 且不存在resultSet属性,则为true + // 是否含有嵌套的结果映射,如果某个映射关系中存在 resultMap属性, + // 且不存在 resultSet属性,则为true private boolean hasNestedResultMaps; - // 是否含有嵌套查询,如果某个属性映射存在select属性,则为true + // 是否含有嵌套查询,如果某个属性映射存在 select属性,则为true private boolean hasNestedQueries; // 是否开启自动映射 private Boolean autoMapping; } ``` -了解了ResultMapping 和ResultMap 记录的信息之后,下面开始介绍<resultMap>节点的解析过程。在XMLMapperBuilder中通过resultMapElements()方法解析映射配置文件中的全部<resultMap>节点,该方法会循环调用resultMapElement()方法处理每个<resultMap>节点。 +了解了 ResultMapping 和 ResultMap 记录的信息之后,下面开始介绍 <resultMap>节点 的解析过程。在 XMLMapperBuilder 中通过 resultMapElements()方法 解析映射配置文件中的全部 <resultMap>节点,该方法会循环调用 resultMapElement()方法 处理每个 <resultMap> 节点。 ```java private ResultMap resultMapElement(XNode resultMapNode) throws Exception { return resultMapElement(resultMapNode, Collections. emptyList()); @@ -450,42 +450,42 @@ public class ResultMap { private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); - // 的id属性,默认值会拼装所有父节点的id 或value或property属性值 + // 的 id属性,默认值会拼装所有父节点的 id 或 value 或 property属性值 String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); - // 的type属性,表示结果集将被映射成type指定类型的对象 + // 的 type属性,表示结果集将被映射成 type 指定类型的对象 String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); - // 该属性指定了该节点的继承关系 + // 该属性指定了该 节点 的继承关系 String extend = resultMapNode.getStringAttribute("extends"); - // 为true则启动自动映射功能,该功能会自动查找与列明相同的属性名,并调用setter方法, - // 为false,则需要在节点内注明映射关系才会调用对应的setter方法 + // 为 true 则启动自动映射功能,该功能会自动查找与列明相同的属性名,并调用 setter方法, + // 为 false,则需要在 节点 内注明映射关系才会调用对应的 setter方法 Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); - // 解析type类型 + // 解析 type类型 Class typeClass = resolveClass(type); Discriminator discriminator = null; // 该集合用来记录解析结果 List resultMappings = new ArrayList(); resultMappings.addAll(additionalResultMappings); - // 获取并处理的子节点 + // 获取并处理 的子节点 List resultChildren = resultMapNode.getChildren(); - // child单数形式,children复数形式 + // child 单数形式,children 复数形式 for (XNode resultChild : resultChildren) { - // 处理节点 + // 处理 节点 if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); - // 处理节点 + // 处理 节点 } else if ("discriminator".equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { - // 处理,,,等节点 + // 处理 , , , 等节点 List flags = new ArrayList(); 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从<resultMap>节点获取到id属性和type属性值之后,就会通过XMLMapperBuilder的buildResultMappingFromContext()方法为<result>节点创建对应的ResultMapping 对象。 +从上面的代码我们可以看到,Mybatis 从 <resultMap>节点 获取到 id属性 和 type属性值 之后,就会通过 XMLMapperBuilder 的 buildResultMappingFromContext()方法 为 <result>节点 创建对应的 ResultMapping对象。 ```java /** - * 根据上下文环境构建ResultMapping + * 根据上下文环境构建 ResultMapping */ private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, List flags) throws Exception { // 获取各个节点的属性,见文知意 @@ -527,30 +527,30 @@ public class ResultMap { @SuppressWarnings("unchecked") Class> typeHandlerClass = (Class>) 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 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 extendedResultMappings = new ArrayList(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 解析<sql>节点 -在映射配置文件中,可以使用<sql>节点定义可重用的SQL语句片段,当需要重用<sql>节点中定义的SQL语句片段时,只需要使用<include>节点引入相应的片段即可,这样,在编写SQL语句以及维护这些SQL语句时,都会比较方便。XMLMapperBuilder的sqlElement()方法负责解析映射配置文件中定义的全部<sql>节点。 +在映射配置文件中,可以使用 <sql>节点 定义可重用的 SQL语句片段,当需要重用 <sql>节点 中定义的 SQL语句片段 时,只需要使用 <include>节点 引入相应的片段即可,这样,在编写 SQL语句 以及维护这些 SQL语句 时,都会比较方便。XMLMapperBuilder 的 sqlElement()方法 负责解析映射配置文件中定义的 全部<sql>节点。 ```java private void sqlElement(List list) throws Exception { if (configuration.getDatabaseId() != null) { @@ -590,27 +590,27 @@ public class MapperBuilderAssistant extends BaseBuilder { } private void sqlElement(List list, String requiredDatabaseId) throws Exception { - // 遍历节点 + // 遍历 节点 for (XNode context : list) { String databaseId = context.getStringAttribute("databaseId"); String id = context.getStringAttribute("id"); - // 为id添加命名空间 + // 为 id 添加命名空间 id = builderAssistant.applyCurrentNamespace(id, false); - // 检测的databaseId与当前Configuration中记录的databaseId是否一致 + // 检测 的 databaseId 与当前 Configuration 中记录的 databaseId 是否一致 if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) { - // 记录到sqlFragments(Map)中保存 + // 记录到 sqlFragments(Map) 中保存 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(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节点,且两者的解析过程也非常相似。 \ No newline at end of file +另外,在 MapperAnnotationBuilder 的 parse()方法 中解析的注解,都能在映射配置文件中找到与之对应的 XML节点,且两者的解析过程也非常相似。 \ No newline at end of file diff --git a/docs/Netty/IO/NIO原理详解.md b/docs/Netty/IO/NIO原理详解.md deleted file mode 100644 index 5f28270..0000000 --- a/docs/Netty/IO/NIO原理详解.md +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/Netty/IOTechnologyBase/IO模型.md b/docs/Netty/IOTechnologyBase/IO模型.md new file mode 100644 index 0000000..f55a153 --- /dev/null +++ b/docs/Netty/IOTechnologyBase/IO模型.md @@ -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 的实现方案。 \ No newline at end of file diff --git a/docs/Netty/IOTechnologyBase/四种IO编程及对比.md b/docs/Netty/IOTechnologyBase/四种IO编程及对比.md new file mode 100644 index 0000000..96c22b4 --- /dev/null +++ b/docs/Netty/IOTechnologyBase/四种IO编程及对比.md @@ -0,0 +1 @@ +努力更新中... \ No newline at end of file diff --git a/docs/Netty/IO/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md b/docs/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md similarity index 100% rename from docs/Netty/IO/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md rename to docs/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md diff --git a/images/Netty/IO复用模型.png b/images/Netty/IO复用模型.png new file mode 100644 index 0000000..a023c8f Binary files /dev/null and b/images/Netty/IO复用模型.png differ diff --git a/images/Netty/信号驱动IO模型.png b/images/Netty/信号驱动IO模型.png new file mode 100644 index 0000000..9f4592e Binary files /dev/null and b/images/Netty/信号驱动IO模型.png differ diff --git a/images/Netty/异步IO模型.png b/images/Netty/异步IO模型.png new file mode 100644 index 0000000..053f64b Binary files /dev/null and b/images/Netty/异步IO模型.png differ diff --git a/images/Netty/阻塞IO模型.png b/images/Netty/阻塞IO模型.png new file mode 100644 index 0000000..5154b89 Binary files /dev/null and b/images/Netty/阻塞IO模型.png differ diff --git a/images/Netty/非阻塞IO模型.png b/images/Netty/非阻塞IO模型.png new file mode 100644 index 0000000..208f7ab Binary files /dev/null and b/images/Netty/非阻塞IO模型.png differ