一、前言
1.持久层
Java数据持久层,其本身是为了实现与数据源进行数据交互的存在,其目的是通过分层架构风格,进行应用&数据的解耦。
我从整体角度,依次阐述JDBC、Mybatis、MybatisPlus。
前者总是后者的依赖。只有在了解前者,才可以更好地学习后者。
2.技术选型
ciwai ,还有Hibernate、SpringData、JPA等。
至于Hibernate作为知名框架,其最大的特点,是支持面向对象的数据管理。但成也萧何,败也萧何。Hibernate的该功能,导致其太重了。而大多数场景下,我们是不需要这个功能的。另外,Hibernate的该功能,使用起来过于复杂。其设计关联关系&映射关系,带来了太多复杂度。
SpringData,则是我看好的另一个Spring原生支持。但是目前主流还是Mybatis,其发展&主流的切换,还需要时间。就让子弹飞一会儿吧。
至于MybatisPlus,是我在工业物联网公司时所采用的一个技术方案。其符合"约定大于配置"的技术趋势,减少了Mybatis那样的配置成本,但是比JPA更加灵活。更棒的是,它支持stream这样的编码方式进行Sql支持(错误可以在编译期透出)。但如果是大型公司,个人的建议是,谨慎考虑,再进行使用。抛开技术方面的考量,MybatisPlus虽然是优秀的开源软件,但其开源社区&软件管理确实相对过于薄弱。对于大公司的技术生态而言,这是一个不得不重视的风险点。
3.文章脉络
不过,Mybatis作为现在最流行的ORM框架,还是值得大家相信的。所以经过考虑,这边文章虽然包含三块内容,但是JDBC更多作为一个依赖,进行了解。而MybatisPlus主要侧重于其核心功能-BaseMapper的实现,以及其扩展Mybatis得到的扩展实现方式。整篇文章的重点,还是落在Mybatis,对其投入较大的精力进行描述。
4.文章优势
又到了王婆卖瓜的阶段。
文章最大的两个优点:图&结构。
本篇文章采用了数十张图,用于展现对应关系。毕竟一图胜千言嘛。
而结构方面,文章采用MECE原则。文章分为JDBC、Mybatis、MybatisPlus。核心的Mybatis分为静态结构&运行流程。静态结构对Mybatis的架构,以及模块进行了展开。运行流程则是针对Mybatis的初始化&运行两个重要生命周期节点,进行展开。最后,通过Mybatis的核心Configuration的核心字段解析(作用、来源、去向)进行总结收纳。
5.文章遗憾
遗憾主要集中在两个方面:
- 由于是一个长文(接近6W字),最近事情又多(财年底,大家懂的),所以难免有一些疏漏。欢迎大家指出来哈。
- 战线拖得太长(写了快两个月)。虽然还有很多地方可以展开&深入,但是经过考虑后,还是放弃了。
文章中有很多补充部分,大家可以自行查阅,扩展知识面。虽然我查询了一些资料,但是有点整理不动(又不知大家是否感兴趣)。当然,如果大家对某部分感兴趣,可以提出来,我出个单章。
二、JDBC
1.简介
JDBC是一个规范,其分为两个部分:
- 厂商:完成数据库驱动
- Java开发者:调用统一接口
2.整体结构
对应组件:
- DriverManager:数据库驱动管理器
- Driver:数据库驱动的抽象接口,用于与数据库服务进行通信
- Connection:与数据库的连接
- Statement:用于提交SQL语句
- Statement:通用接口,继承自Wrapper。普通的不带参的查询SQL;支持批量更新,批量删除;
- PreparedStatement:预编译接口,继承自Statement。可变参数的SQL,编译一次,执行多次,效率高; 安全性好,有效防止Sql注入等问题;
- CallableStatement:继承自PreparedStatement。支持调用存储过程,提供了对输出和输入/输出参数(INOUT)的支持;
- ResultSet:用于保存数据库结果
- SQLException:数据库异常
3.生命周期
a.初始化过程
驱动注册&配置注入
b.执行过程
4.代码示例
原生JDBC较为原始,架构上的设计也是非常薄的。
所以,说得太多,还不如看看应用代码。
// 1. 注册驱动 // 使用java.sql.DriverManager类的静态方法registerDriver(Driver driver) // Driver是一个接口,参数传递:MySQL驱动程序的实现类 // DriverManager.registerDriver(new Driver()); // 查看驱动类源码,注册两次驱动,浪费资源 Class.forName("com.mysql.jdbc.Driver"); // 2. 获得连接 // uri:数据库地址 jdbc:mysql://连接主机ip:端口号//数据库名字 String url = "jdbc:mysql://localhost:3306/TEST"; // static Connection getConnection(String url, String user, String password) // 返回值是java.sql.Connection接口的实现类,在MySQL驱动程序中 Connection conn = DriverManager.getConnection(url, "root", "123456"); // conn.setAutoCommit(false); // 用于事务提交conn.commit(),conn.rollback() System.out.println(conn);// com.mysql.jdbc.JDBC4Connection@10d1f30 // 3. 获得语句执行平台,通过数据库连接对象,获取到SQL语句的执行者对象 //conn对象,调用方法 Statement createStatement() 获取Statement对象,将SQL语句发送到数据库 //返回的是Statement接口的实现类对象,在MySQL驱动程序中 Statement statement = conn.createStatement(); System.out.println(statement);//com.mysql.jdbc.StatementImpl@137bc9 // 4. 执行sql语句 //通过执行者对象调用方法执行SQL语句,获取结果 //int executeUpdate(String sql) 执行数据库中的SQL语句,仅限于insert,update,delete //返回值int,操作成功数据库的行数 ResultSet resultSet = statement.executeQuery("SELECT * from user where id = 1"); System.out.println(resultSet); // 5. 释放资源 statement.close(); conn.close();
5.总结
关键词:简单、原始、看不到
现在基本没有人直接使用了。大多使用框架。我在生产级的使用,还是刚工作的时候,在前端使用了类似的东东。
三、Mybatis
1.整体框架
对应模块:
- 接口层
- SqlSession:应用程序与Mybatis的交互接口
- 核心处理层
- 配置解析:对Mybatis配置文件、映射文件,dao接口注解等进行配置解析,生成Configuration对象
- SQL解析:MyBatis 实现动态SQL 语句的功能,并提供了诸如where等SQL语句节点
- 参数映射:根据实参,解析动态SQLL节点,生成可执行SQL语句,处理占位符,绑定实参
- SQL执行:负责缓存,事务,JDBC等的调度。详见执行过程图
- 结果集映射:通过ResultSetHandler等,完成结果集的映射,得到结果对象并返回
- 插件:提供插件接口,便于用户扩展,甚至修改Mybatis默认行为
- 基础支持层
- 数据源模块:通过配置生成(可委托第三方数据源框架),包含目标数据库信息,向上支持连接生成等
- 事务管理模块:对数据库事务进行抽象,并提供简单实现。可与Spring集成,由Spring实现事务管理
- 缓存模块:为Mybatis的一二级缓存提供支持,从而优化数据库性能
- Binding模块:实现DAO接口文件与对应映射文件的关联
- 反射模块:对Java原生反射进行了封装与优化
- 类型转换:一方面实现JavaType与JDBCType的转换,另一方面支撑Mybatis的别名机制
- 日志模块:提供详细日志输出信息,并能够集成第三方日志框架(log4j,sel4j等)
- 资源加载:封装Java原生类加载器,提供类与其他资源文件的有序加载能力
- 解析器模块:一方面封装XPath,提供
数据源模块补充:即常用组件-DataSource。MyBatis 自身提供了相应的数据源实现(Pooled,UnPooled,Jndi),MyBatis 也提供第三方数据源集成的接口()。现在开源的数据源都提供了比较丰富的功能,如连接池功能、检测连接状态等
@Select注解,就可以省略对应的映射文件节点
DAO接口的实现类,是由Mybatis自动创建的动态代理对象(依赖于对应的映射文件节点)
Mybatis初始化阶段:加载Mybatis配置文件、映射文件,dao接口注解->保存到configuration对象中->创建SqlSessionFactory对象。Mybatis初始化阶段后,开发者可以通过SqlSessionFactory,获取对应的SqlSession。wdk-som的数据库配置就是直接配置生成DataSource与SqlSessionFactory。
a.解析模块
Mybatis的配置,有三种途径:
- 注解:如DAO接口方法上的@Select
- 注入:如MybatisConfiguration类
其中,
- DOM:前端小伙伴,不要太熟悉。DOM 是基于树形结构的
- SAX:SAX 是基于事件模型的
- StAX:StAX将
而Mybatis则是采用DOM解析方式,并结合XPath进行
DOM 会将整个
org.apache.ibatis.parsing.XPathParser#variables:mybatis-config.XPathParser中提供了一系列的eval*方法用于解析boolean、short、long、int、String、Node等类型的信息,它通过调用XPath.evaluate方法查找指定路径的节点或属性,并进行相应的类型装换。
剩余部分,此处不再详解。
b.反射模块
Mybatis运行过程中,大量使用了反射(如生成DAO对应代理实现类)。Mybatis对Java原生的反射操作进行了进一步的封装,从而提供更加简洁的API。
Reflector 是MyBatis 中反射模块的基础,每个Reflector 对象都对应一个类,在Reflector 中缓存了反射操作需要使用的类的元信息。
从上图中,可以看出
- 核心类:
- Reflector:对于每个类,都有一个对应的Reflector,用于保存其类元信息。可以类比Spring中的Bean。但是其内部没有类之间的关联&依赖关系
- MetaClass:MetaClass 是MyBatis 对类级别的元信息的封装和处理。MetaClass 通过Reflector 和PropertyTokenizer 组合使用, 实现了对复杂的属性表达式的解析,并实现了获取指定属性描述信息的功能。
- MetaObject:ObjectWrapper实现的属性表达式解析功能,是委托给MetaObject实现的。
- 包:
- invoker:包含MethodInvoker、SetFieldInvoker等,用于实现目标方法反射调用,属性读取与设置等
- factory:包含ObjectFactory&DefaultObjectFactory,对象创建工厂。ObjectFactory提供实例创建接口,其默认实现为DefaultObjectFactory。在Mybatis源码的测试类中,存在对应测试。
- property:包含PropertyCopier、PropertyNamer、PropertyTokenizer,是类字段工具,提供如字段复制、字段是否为属性、字段与index转化(属性表达式&Sql占位符应用)等功能。
- wrapper:包含ObjectWrapperFactory、ObjectWrapper、BaseWrapper等。ObjectWrapper接口是对对象的包装,抽象了对象的属性信息,定义了一系列查询对象属性信息的方法,以及更新属性的方法。
TypeParameterResolver:进行类型解析。如TypeParameterResolver#resolveReturnType会返回对应类&方法的返回类型。在Mybatis源码的测试类中,存在对应测试。
Reflector
每个类,都有其对应等Reflector,用于保存其对应的类元信息(属性,字段等)
public class Reflector { // 对应的Class 类型 private final Class<?> type; // 可读属性的名称集合 private final String[] readablePropertyNames; // 可写属性的名称集合 private final String[] writablePropertyNames; // 属性相应的setter方法,key是属性名称,value是Invoker对象 private final Map<String, Invoker> setMethods = new HashMap<>(); // 属性相应的getter方法集合,key是属性名称,value是Invoker对象 private final Map<String, Invoker> getMethods = new HashMap<>(); // 属性相应的setter方法的参数值类型,key是属性名称,value是setter方法的参数类型 private final Map<String, Class<?>> setTypes = new HashMap<>(); // 属性相应的getter方法的返回位类型,key是属性名称,value是getter方法的返回位类型 private final Map<String, Class<?>> getTypes = new HashMap<>(); // 默认构造方法 private Constructor<?> defaultConstructor; // 所有属性名称的集合,key是属性名称的大写形式,value是属性名称 private Map<String, String> caseInsensitivePropertyMap = new HashMap<>(); // 构造方法 public Reflector(Class<?> clazz) { type = clazz; addDefaultConstructor(clazz); addGetMethods(clazz); addSetMethods(clazz); addFields(clazz); // 学习一下:Collection.toArray()返回的是Object[],而Collection.toArray(T[] a)返回的是T[] readablePropertyNames = getMethods.keySet().toArray(new String[0]); writablePropertyNames = setMethods.keySet().toArray(new String[0]); for (String propName : readablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } for (String propName : writablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } } // 其他方法 }
上述提到的都是"属性",而不是字段。按照JavaBean的规范,类中定义的成员变量称为" 宇段" ,属性则是通过ge阳r/setter 方法得到的,属性只与类中的方法有关,与是否存在对应成员变量没有关系。
所以,Mybatis与对应DO进行交互的依据是getter/setter方法。所以,可以通过自定义getter/setter方法进行字段转换。另外,DO中有字段,但没有对应getter/setter方法,则无法在对应mapper进行映射,最终导致报错。
MetaClass
MetaClass 是MyBatis 对类级别的元信息的封装和处理。
MetaClass 通过Reflector 和PropertyTokenizer 组合使用, 实现了对复杂的属性表达式的解析,并实现了获取指定属性描述信息的功能。
/** * @author Clinton Begin */public class MetaClass { private final ReflectorFactory reflectorFactory; // class对应等Reflector private final Reflector reflector; private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) { this.reflectorFactory = reflectorFactory; this.reflector = reflectorFactory.findForClass(type); } // 核心方法:解析属性表达式。委托给buildProperty方法实现 public String findProperty(String name) { StringBuilder prop = buildProperty(name, new StringBuilder()); return prop.length() > 0 ? prop.toString() : null; } private StringBuilder buildProperty(String name, StringBuilder builder) { // name即是属性表达式。如<result property= "orders[0].items[1].name" column= "item2" /> // PropertyTokenizer包含name、indexName、index、children PropertyTokenizer prop = new PropertyTokenizer(name); // 判断是否还有子表达式 if (prop.hasNext()) { String propertyName = reflector.findPropertyName(prop.getName()); if (propertyName != null) { // 返回结果,追加属性名(.name形式) builder.append(propertyName); builder.append("."); // 为该属性,建立对应的MetaClass MetaClass metaProp = metaClassForProperty(propertyName); // 深度优先递归。创建所有MetaClass,并通过builder形成一个深度优先遍历的关系链 metaProp.buildProperty(prop.getChildren(), builder); } } else { String propertyName = reflector.findPropertyName(name); if (propertyName != null) { builder.append(propertyName); } } return builder; } }
ObjectWrapper
ObjectWrapper接口是对对象的包装,抽象了对象的属性信息,定义了一系列查询对象属性信息的方法,以及更新属性的方法。
其功能实现,是通过实现基础类-BaseObjectWrapper,委托给MetaObject实现。
/** * @author Clinton Begin */public interface ObjectWrapper { // 如采Object Wrapper 中封装的是普通的Bean对象,则调用相应属性的相应getter 方法 // 如采封装的是集合类,则获取指定key或下标对应的value值 Object get(PropertyTokenizer prop); void set(PropertyTokenizer prop, Object value); // 查找属性表达式指定的属性,第二个参数表示是否忽略属性表达式中的下画线 String findProperty(String name, boolean useCamelCaseMapping); String[] getGetterNames(); String[] getSetterNames(); // 解析属性表达式指定属性的setter 方法的参数类型。name为请求的属性表达式 Class<?> getSetterType(String name); Class<?> getGetterType(String name); boolean hasSetter(String name); boolean hasGetter(String name); // 为属性表达式指定的属性创建相应的MetaObject对象 MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory); boolean isCollection(); void add(Object element); <E> void addAll(List<E> element);}
MetaObject
ObjectWrapper实现的属性表达式解析功能,是委托给MetaObject实现的。
/** * @author Clinton Begin */public class MetaObject { // 原生对象,即MetaObject所表示的对象 private final Object originalObject; private final ObjectWrapper objectWrapper; private final ObjectFactory objectFactory; private final ObjectWrapperFactory objectWrapperFactory; private final ReflectorFactory reflectorFactory; private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) { this.originalObject = object; this.objectFactory = objectFactory; this.objectWrapperFactory = objectWrapperFactory; this.reflectorFactory = reflectorFactory; // 根据对象类型,使用不同的wrapper方法 if (object instanceof ObjectWrapper) { this.objectWrapper = (ObjectWrapper) object; } else if (objectWrapperFactory.hasWrapperFor(object)) { this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object); } else if (object instanceof Map) { this.objectWrapper = new MapWrapper(this, (Map) object); } else if (object instanceof Collection) { this.objectWrapper = new CollectionWrapper(this, (Collection) object); } else { this.objectWrapper = new BeanWrapper(this, object); } } public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) { if (object == null) { return SystemMetaObject.NULL_META_OBJECT; } else { return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory); } } // 从MetaObject中,获取某个字段的属性值 public Object getValue(String name) { PropertyTokenizer prop = new PropertyTokenizer(name); if (prop.hasNext()) { MetaObject metaValue = metaObjectForProperty(prop.getIndexedName()); if (metaValue == SystemMetaObject.NULL_META_OBJECT) { return null; } else { return metaValue.getValue(prop.getChildren()); } } else { return objectWrapper.get(prop); } } // 对MetaObject中某个字段进行赋值 public void setValue(String name, Object value) { PropertyTokenizer prop = new PropertyTokenizer(name); if (prop.hasNext()) { MetaObject metaValue = metaObjectForProperty(prop.getIndexedName()); if (metaValue == SystemMetaObject.NULL_META_OBJECT) { if (value == null) { // don't instantiate child path if value is null return; } else { metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory); } } metaValue.setValue(prop.getChildren(), value); } else { objectWrapper.set(prop, value); } }// 其他方法
c.类型转换
JDBC 数据类型与Java 语言中的数据类型井不是完全对应的,所以在PreparedStatement 为SQL 语句绑定参数时,需要从Java 类型转换成JDBC 类型,而从结果集中获取数据时,则需要从JDBC 类型转换成Java 类型。My Batis 使用类型模块完成上述两种转换。
TypeHandler
/** * @author Clinton Begin */public interface TypeHandler<T> { // 设置参数。在通过PreparedStatement 为SQL 语句绑定参数时,会将数据由Java 类型转换成JdbcType 类型 // 《Mybatis技术内幕》这部分的解释反了,详见入参与功能实现代码 void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; // 获取结果。 // 从ResultSet 中获取数据时会调用此方法,会将数据由Java 类型转换成JdbcType 类型 T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException;}
BaseTypeHandler
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> { @Deprecated protected Configuration configuration; @Deprecated public void setConfiguration(Configuration c) { this.configuration = c; } @Override public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { if (parameter == null) { if (jdbcType == null) { throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); } try { ps.setNull(i, jdbcType.TYPE_CODE); } catch (SQLException e) { throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " + "Cause: " + e, e); } } else { try { // 设置参数,该方法具体有子类实现 setNonNullParameter(ps, i, parameter, jdbcType); } catch (Exception e) { throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different configuration property. " + "Cause: " + e, e); } } } @Override public T getResult(ResultSet rs, String columnName) throws SQLException { try { return getNullableResult(rs, columnName); } catch (Exception e) { throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e); } } @Override public T getResult(ResultSet rs, int columnIndex) throws SQLException { try { return getNullableResult(rs, columnIndex); } catch (Exception e) { throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set. Cause: " + e, e); } } @Override public T getResult(CallableStatement cs, int columnIndex) throws SQLException { try { return getNullableResult(cs, columnIndex); } catch (Exception e) { throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement. Cause: " + e, e); } } public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException; public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException; public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;}
实现子类的类型转换,最终还是会落到JDBC的PreparedStatement/ResultSet中对应的类型转换方法。
而PreparedStatement/ResultSet,是由入参带入的。
TypeHandlerRegistry&TypeAliasRegistry,主要是进行类型处理器&类型别名的管理(类似IOC容器对Bean的管理)。
d.数据源模块
Mybatis的数据源模块,采用了工厂方法设计模式。
如其中DataSourceFactory是工厂接口,而PooledDataSourceFactory等则是其工厂实现类。
Mybatis提供了三个工厂类实现方式:
- PooledDataSourceFactory
- UnpooledDataSourceFactory
- JndiDataSourceFactory
调用方举例:org.apache.ibatis.builder.
DataSourceFactory
public interface DataSourceFactory { void setProperties(Properties var1); DataSource getDataSource();}
UnpooledDataSourceFactory
public class UnpooledDataSourceFactory implements DataSourceFactory { private static final String DRIVER_PROPERTY_PREFIX = "driver."; private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length(); protected DataSource dataSource; public UnpooledDataSourceFactory() { this.dataSource = new UnpooledDataSource(); } @Override public void setProperties(Properties properties) { Properties driverProperties = new Properties(); // 利用基础层的配置解析模块,创建DataSource 相应的MetaObject MetaObject metaDataSource = SystemMetaObject.forObject(dataSource); for (Object key : properties.keySet()) { // 遍历Properties,从而获取DataSource所需的配置信息 String propertyName = (String) key; // 以"driver."开头的配置项,是对DataSource的配置,记录到driverProperties中保存 if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) { String value = properties.getProperty(propertyName); driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value); } else if (metaDataSource.hasSetter(propertyName)) { String value = (String) properties.get(propertyName); Object convertedValue = convertValue(metaDataSource, propertyName, value); metaDataSource.setValue(propertyName, convertedValue); } else { throw new DataSourceException("Unknown DataSource property: " + propertyName); } } if (driverProperties.size() > 0) { metaDataSource.setValue("driverProperties", driverProperties); } } @Override public DataSource getDataSource() { return dataSource; } private Object convertValue(MetaObject metaDataSource, String propertyName, String value) { Object convertedValue = value; Class<?> targetType = metaDataSource.getSetterType(propertyName); if (targetType == Integer.class || targetType == int.class) { convertedValue = Integer.valueOf(value); } else if (targetType == Long.class || targetType == long.class) { convertedValue = Long.valueOf(value); } else if (targetType == Boolean.class || targetType == boolean.class) { convertedValue = Boolean.valueOf(value); } return convertedValue; }}
UnpooledDataSource
public class UnpooledDataSource implements DataSource { // 进行驱动加载的classLoader,可参照JDBC相关处理 private ClassLoader driverClassLoader; // 驱动配置 private Properties driverProperties; // 驱动注册表(全量) private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>(); // 当前DataSource所采用的驱动,如mysqlDriver private String driver; // 数据源地址 private String url; // 用户名 private String username; // 密码 private String password; // 是否自动提交(有关于事务),默认自动提交 private Boolean autoCommit; // 默认事务隔离级别 private Integer defaultTransactionIsolationLevel; // 默认网络超时时间 private Integer defaultNetworkTimeout; // 驱动注册 static { Enumeration<Driver> drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); } } // 方法略 }
e.事务管理模块
TransactionFactory
public interface TransactionFactory { default void setProperties(Properties props) { // NOP } Transaction newTransaction(Connection conn); Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);}
Transaction
public interface Transaction { Connection getConnection() throws SQLException; void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; // 获取事务超时时间(Spring的SpringManagedTransaction,存在对应实现) Integer getTimeout() throws SQLException;}
SpringManagedTransaction
public class SpringManagedTransaction implements Transaction { private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class); private final DataSource dataSource; private Connection connection; // 当前连接是否为事务连接 private boolean isConnectionTransactional; // 是否自动提交。如果是自动提交,也就不需要手动commit()了 private boolean autoCommit; public SpringManagedTransaction(DataSource dataSource) { Assert.notNull(dataSource, "No DataSource specified"); this.dataSource = dataSource; } public Connection getConnection() throws SQLException { if (this.connection == null) { this.openConnection(); } return this.connection; } private void openConnection() throws SQLException { this.connection = DataSourceUtils.getConnection(this.dataSource); this.autoCommit = this.connection.getAutoCommit(); // DataSourceUtils获取对应的事务性ConnectionHolder,然后比对当前连接与ConnectionHolder this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); if (LOGGER.isDebugEnabled()) { LOGGER.debug("JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring"); } } public void commit() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Committing JDBC Connection [" + this.connection + "]"); } // 事务提交 this.connection.commit(); } } public void rollback() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]"); } // 事务回滚 this.connection.rollback(); } } public void close() throws SQLException { // 通过DataSourceUtils,释放当前连接。依旧涉及ConnectionHolder DataSourceUtils.releaseConnection(this.connection, this.dataSource); } public Integer getTimeout() throws SQLException { // Connection没有对应的事务超时时间,这里直接调用底层实现,获取事务超时时间 ConnectionHolder holder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource); return holder != null && holder.hasTimeout() ? holder.getTimeToLiveInSeconds() : null; }}
这里的实现,涉及Connection的事务实现、DataSourceUtils、TransactionSynchronizationManager.getResource三个点。
f.缓存模块
Cache:多种实现。如FIFO、LRU
CacheKey:应对SQL的可变参数
TransactionalCacheManager&TransactionalCache:事务缓存
缓存模块,是直接关联执行模块-Executor模块
- Mybatis的缓存:
- 一级缓存:默认开启。属于SqlSession级别的缓存。利用BaseExecute -> PerpetualCache -> HashMap<Obj,Obj>实现。
- 二级缓存:默认关闭。属于全局级别的缓存。利用CacheExecute -> TransactionalCacheManager -> HashMap<Cache, TransactionalCache> -> TransactionalCache
缓存实现,采用装饰器模式
Cache
public interface Cache { String getId(); void putObject(Object key, Object value); Object getObject(Object key); Object removeObject(Object key); void clear(); int getSize(); default ReadWriteLock getReadWriteLock() { return null; }}
PerpetualCache
public class PerpetualCache implements Cache { private final String id; private final Map<Object, Object> cache = new HashMap<>(); public PerpetualCache(String id) { this.id = id; } @Override public String getId() { return id; } @Override public int getSize() { return cache.size(); } @Override public void putObject(Object key, Object value) { cache.put(key, value); } @Override public Object getObject(Object key) { return cache.get(key); } @Override public Object removeObject(Object key) { return cache.remove(key); } @Override public void clear() { cache.clear(); } @Override public boolean equals(Object o) { if (getId() == null) { throw new CacheException("Cache instances require an ID."); } if (this == o) { return true; } if (!(o instanceof Cache)) { return false; } Cache otherCache = (Cache) o; return getId().equals(otherCache.getId()); } @Override public int hashCode() { if (getId() == null) { throw new CacheException("Cache instances require an ID."); } return getId().hashCode(); }}
SynchronizedCache
public class SynchronizedCache implements Cache { private final Cache delegate; public SynchronizedCache(Cache delegate) { this.delegate = delegate; } @Override public String getId() { return delegate.getId(); } @Override public synchronized int getSize() { return delegate.getSize(); } @Override public synchronized void putObject(Object key, Object object) { delegate.putObject(key, object); } @Override public synchronized Object getObject(Object key) { return delegate.getObject(key); } @Override public synchronized Object removeObject(Object key) { return delegate.removeObject(key); } @Override public synchronized void clear() { delegate.clear(); } @Override public int hashCode() { return delegate.hashCode(); } @Override public boolean equals(Object obj) { return delegate.equals(obj); }}
CacheKey
public class CacheKey implements Cloneable, Serializable { private static final long serialVersionUID = 1146682552656046210L; public static final CacheKey NULL_CACHE_KEY = new CacheKey() { @Override public void update(Object object) { throw new CacheException("Not allowed to update a null cache key instance."); } @Override public void updateAll(Object[] objects) { throw new CacheException("Not allowed to update a null cache key instance."); } }; private static final int DEFAULT_MULTIPLIER = 37; private static final int DEFAULT_HASHCODE = 17; private final int multiplier; private int hashcode; private long checksum; private int count; private List<Object> updateList; public CacheKey() { this.hashcode = DEFAULT_HASHCODE; this.multiplier = DEFAULT_MULTIPLIER; this.count = 0; this.updateList = new ArrayList<>(); } public CacheKey(Object[] objects) { this(); updateAll(objects); } public int getUpdateCount() { return updateList.size(); } public void update(Object object) { int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); count++; checksum += baseHashCode; baseHashCode *= count; hashcode = multiplier * hashcode + baseHashCode; updateList.add(object); } public void updateAll(Object[] objects) { for (Object o : objects) { update(o); } } @Override public boolean equals(Object object) { if (this == object) { return true; } if (!(object instanceof CacheKey)) { return false; } final CacheKey cacheKey = (CacheKey) object; if (hashcode != cacheKey.hashcode) { return false; } if (checksum != cacheKey.checksum) { return false; } if (count != cacheKey.count) { return false; } for (int i = 0; i < updateList.size(); i++) { Object thisObject = updateList.get(i); Object thatObject = cacheKey.updateList.get(i); if (!ArrayUtil.equals(thisObject, thatObject)) { return false; } } return true; } @Override public int hashCode() { return hashcode; } @Override public String toString() { StringJoiner returnValue = new StringJoiner(":"); returnValue.add(String.valueOf(hashcode)); returnValue.add(String.valueOf(checksum)); updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add); return returnValue.toString(); } @Override public CacheKey clone() throws CloneNotSupportedException { CacheKey clonedCacheKey = (CacheKey) super.clone(); clonedCacheKey.updateList = new ArrayList<>(updateList); return clonedCacheKey; }}
g.Binding模块
在mybatis的前身-iBatis,数据插入是这样的:
sqlSession.insert("insert", userDO);
或者,抽象一下:
public interface UserDAO { void insertUser(UserDO userDO);}public class UserDAOImpl extends SqlMapDaoTemplate implements UserDAO { public UserDAOImpl(DaoManager daoManager) { super(daoManager); } public void insertUser(UserDO userDO) throws SQLException { insert("insert", userDO); } }
两个实现,都涉及一个问题,需要手写
insert("insert", userDO);
那么写错,也是完全可能的嘛。但iBatis这部分,与Mybatis一样,是通过运行时的反射实现的。那么就无法快速失败,从而在启动时检索出问题。
如果一个不常用的方法实现的入参方法名写错了。Boom,线上故障+紧急发布。
所以,这里需要一个解决方案,可以在启动时,就检索出对应错误。
Mybatis给出的答案是,不再需要写上述实现。Mybatis直接通过Binding模块,直接关联DAO&对应Mapper。如果映射存在问题,则在启动时抛出相应问题。
举个栗子,如果在DAO的入参中没有String shopCode,而对应Mapper有对应入参注入,则会在启动时报错,提示"无法找到对应入参"。
MapperRegistry
public class MapperRegistry { // Mybatis全局Configuration,通过构造器注入 private final Configuration config; // mapperInterface与相应MapperProxyFactory的映射表 // 如果是sqlSession.xxx的使用,则不经过这里。因为sqlSession在执行过程中,属于更底层的位置。详见后文:生命周期-执行过程 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); public MapperRegistry(Configuration config) { this.config = config; } // 通过mapperInterface,获取对应的MapperProxy(type为接口类型) @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } public <T> boolean hasMapper(Class<T> type) { return knownMappers.containsKey(type); } // 初始化过程中,用于添加mapperInterface。详见下述生命周期-初始化 public <T> void addMapper(Class<T> type) { // 检测type是否为接口类型,因为是针对mapperInterface if (type.isInterface()) { // 判断该接口是否已经注入到上面的映射表knownMappers中 if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // 进行对应mapper的解析,详见下述生命周期-初始化 knownMappers.put(type, new MapperProxyFactory<>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { // 失败,"回滚" if (!loadCompleted) { knownMappers.remove(type); } } } }// 其他方法}
MapperProxyFactory
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; // 该接口中,method与对应Invoker的映射表。 // MapperMethodInvoker与MapperMethod关系,详见org.apache.ibatis.binding.MapperProxy.PlainMethodInvoker private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethodInvoker> getMethodCache() { return methodCache; } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }}
MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable { // 核心字段 // 关联的SqlSession private final SqlSession sqlSession; // 当前Mapper,所对应的mapperInterface private final Class<T> mapperInterface; // 当前Mapper中,Method与Invoker对应的映射表,作为缓存。此是由MapperProxyFactory给出 private final Map<Method, MapperMethodInvoker> methodCache; // 核心方法 public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 如采目标方法继承自Object ,则直接调用目标方法。如toString()等方法 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { // 其他的方法,则是Mapper相关的方法(非Object方法),则需要通过MapperMethodInvoker。具体可参照下面的PlainMethodInvoker return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { MapperMethodInvoker invoker = methodCache.get(method); if (invoker != null) { return invoker; } return methodCache.computeIfAbsent(method, m -> { // 默认方法是公共非抽象的实例方法。也就是Interface的default方法 if (m.isDefault()) { try { if (privateLookupInMethod == null) { return new DefaultMethodInvoker(getMethodHandleJava8(method)); } else { return new DefaultMethodInvoker(getMethodHandleJava9(method)); } } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } } else { // 根据默认方法的判定,常用的MapperMethodInvoker是PlainMethodInvoker return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }); } catch (RuntimeException re) { Throwable cause = re.getCause(); throw cause == null ? re : cause; } } // 核心内部类 private static class PlainMethodInvoker implements MapperMethodInvoker { private final MapperMethod mapperMethod; public PlainMethodInvoker(MapperMethod mapperMethod) { super(); this.mapperMethod = mapperMethod; } @Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { // 通过MapperMethod.execute(),进行Sql语句的代理执行。详见MapperMethod return mapperMethod.execute(sqlSession, args); } } }
MapperMethod
MapperMethod 中封装了Mapper 接口中对应方法的信息,以及对应SQL 语句的信息
MapperMethod 对象。MapperMethod 对象会完成参数转换以及SQL语句的执行功能
MapperMethod 中并不记录任何状态相关的信息,所以可以在多个代理对象之间共享
public class MapperMethod { // 当前Mapper下Method的Sql信息(SqlCommand) // SqlCommand包含SQL语句的名称和类型 private final SqlCommand command; // 当前Mapper下Method的方法签名,包括入参与返回值(类型&位置信息等) private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } // 核心方法 public Object execute(SqlSession sqlSession, Object[] args) { Object result; // 根据SqlCommand的类型,执行不同的分支 switch (command.getType()) { case INSERT: { // 参数关联,将传入实参数与方法形参关联起来。通过MethodSIgnature下的convertArgsToSqlCommandParam(),间接调用ParamNameResolver.getNamedParams(),从而获取Map<paramterName, paramterValue> Object param = method.convertArgsToSqlCommandParam(args); // 通过sqlSession.insert(command.getName(), param)进行执行,并将其返回值(effectLines),按照当前Method的返回值,返回对应类型的值(int、long、boolean) result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: // 根据返回值类型不同,调用不同执行方法,并返回不同结果 // 但其中executexxx()本质,还是调用sqlSession.xxx(),获取执行结果 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { // 这部分,个人认为也可以采用一个私有方法进行处理。 // 这里为什么不作为私有方法处理。个人猜测:一方面是命名(命名与语义关联);另一方面是为了更直观展示other的处理方式,提高代码可读性? Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }
小结:
MapperRegistry.getMapper -> MapperProxyFactory.newInstance -> MapperProxy.invoke -> MapperMethod.execute -> Sqlsession.xxx(进入执行时)
h.资源加载模块
(暂略)
i.日志模块
(暂略)
2.生命周期
a.初始化过程
Mybatis初始化
Mybatis初始化-解析Mapper
mapper解析过程中,存在incompile 与 parsePending,很有意思。与
对MyBatis 初始化过程的分析可知, 映射配置文件中定义的SQL 节点会被解析成MappedStatement对象, 其中的SQL 语句会被解析成SqlSource 对象, SQL 语句中定义的动态SQL 节点、文本节点等,则由SqlNode 接口的相应实现表示。
MappedStatement包含SqlSource sqlSource。
SqlSource实现:DynamicSqlSource 负责处理动态SQL 语句,RawSqlSource 负责处理静态语句,两者最终都会将处理后的SQL 语句封装成StaticSqlSource返回。
DynamicSqlSource 与StaticSq!Source 的主要区别是:StaticSq!Source 中记录的SQL语句中可能含有"?"占位符,但是可以直接提交给数据库执行:DynamicSqlSourc e 中封装的SQL语句还需要进行一系列解析,才会最终形成数据库可执行的SQL 语句。
MyBatis 在处理动态SQL节点时,应用到了组合设计模式。MyBatis 会将动态SQL节点解析成对应的SqINode 实现,并形成树形结构。
DynamicContext 主要用于记录解析动态SQL 语句之后产生的SQL 语句片段,可以认为它是一个用于记录动态SQL 语句解析结果的容器。
SqlNode 接口有多个实现类,每个实现类对应一个动态SQL节点。如IfSqlNode,表示mapper映射文件中的if节点。
MapperBuilderAssistant
从上图,就可以看出MapperBuilderAssistant这个类的实际地位了。
BaseBuilder作为一个抽象类,提供了一个构建规约。
MapperBuilderAssistant则是实际提供构建能力的assistant。
而
这个部分,这里只是点一下。
b.执行过程
SQL 语句的执行涉及多个组件,其中比较重要的是Executor 、StatementHandler 、ParameterHandler 和ResultSetHandler 。
Executor主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给StatementHandler完成。
StatementHandler先通过ParameterHandler 完成SQL语句的实参绑定。然后通过java.sql.Statement 对象执行SQL 语句并得到结果集。最后通过ResultSetHandler 完成结果集的映射,得到结果对象并返回。
Mybatis最终是直接通过DataSource.getConnection(),获取对应Connnection,再进行联合Statement.execute进行操作。
Connection -> Transaction -> Executor -> StatementHandler
c.补充:初始化过程与执行过程的关联
3.Mybatis-Spring
这里不再详细展开Mybatis在SpringFramework集成时的配置。
主要针对Mybatis-Spring核心类的剖析,以及Mybatis在SpringBoot中的拆箱即用。
a.SqlSessionFactoryBean
在前面的Mybatis生命周期-初始化过程中,提到:SqlSessionFactoryBuilder会通过等对象读取mybatis-config.而在Mybatis-Spring中,SqlSessionFactoryBean取代了SqlSessionFactoryBuilder,进行SqlSessionFactory对象的生成。
这里对上述相关类,进行阐述:
- SqlSessionFactoryBean:Mybatis-Spring集成中,Spring初始化Mybatis的核心
- FactoryBean<>:声明该类为一个工厂类
- InitializingBean:利用Spring的生命周期接口,进行Mybatis对应Bean注入时间的时机控制(在配置注入完毕后)。详见Initialization Callbacks
- ApplicationListener<>:通过Spring下ApplicationContext的扩展能力,确保在上下文发生变化时,进行Mybatis配置的更新(主要针对Statement)。详见Additional Capabilities of the ApplicationContext
- SqlSessionFactoryBuilder:SqlSessionFactoryBean通过组合&委托的方式,调用SqlSessionFactoryBuilder,从而构建SqlSessionFactory。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class); private Resource configLocation; private Configuration configuration; private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); //EnvironmentAware requires spring 3.1 private String environment = SqlSessionFactoryBean.class.getSimpleName(); // 是否采取快速失败,用于在上下文刷新时,进行statement刷新 private boolean failFast; // Mybatis插件 private Interceptor[] plugins; // 其他Mybatis-Configuration的字段(略) // 各字段的getter/setter方法(略) /** * 核心方法,由Spring的生命周期进行控制(钩子函数-配置设置后,进行触发) * 进行数据校验,然后调用构建方法-buildSqlSessionFactory() */ @Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); } /** * */ protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration;
b.SpringManagedTransaction
有关Spring下Mybatis的事务,已经在Mybatis的事务模块,说明了。这里不再赘述。
提醒一下,只有在配置中未指定Mybatis事务管理,Spring才会采用默认事务管理-SpringManagedTransaction。
c.SqlSessionTemplate
SqlSessionTemplate实现了SqlSession接口,代替Mybatis原有的DefaultSqlSession,完成指定的数据库操作。
private final SqlSession sqlSessionProxy; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } @Override public <T> T selectOne(String statement) { return this.sqlSessionProxy.<T> selectOne(statement); }// 其他略
其通过委托的方式,调用其内部SqlSession sqlSessionProxy,从而完成对应功能。而此处的sqlSessionProxy,最终也是通过DefaultSqlSession实现,除非自定义实现SqlSessionFactory&SqlSession。
d.Mybatis-Starter
这部分涉及SpringBoot的自动注入,从而达到拆箱即用的效果。
首先,Spring根据配置,确定并注入DataSource等Bean。
然后,SpringBoot通过spring.factory,确定Mybatis的自动注入类MybatisAutoConfiguration。
最后,根据MybatisAutoConfiguration的@Bean方法,以及已有的配置Bean,进行Mybatis下SqlSessionFactory&SqlSessionTemplate的Bean注入。
spring.factory
# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
MybatisAutoConfiguration
@org.springframework.context.annotation.Configuration@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })@ConditionalOnBean(DataSource.class)@EnableConfigurationProperties(MybatisProperties.class)@AutoConfigureAfter(DataSourceAutoConfiguration.class)public class MybatisAutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class); private final MybatisProperties properties; private final Interceptor[] interceptors; private final ResourceLoader resourceLoader; private final DatabaseIdProvider databaseIdProvider; private final List<ConfigurationCustomizer> configurationCustomizers; public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) { this.properties = properties; this.interceptors = interceptorsProvider.getIfAvailable(); this.resourceLoader = resourceLoader; this.databaseIdProvider = databaseIdProvider.getIfAvailable(); this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); } @PostConstruct public void checkConfigFileExists() { if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)"); } } @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } Configuration configuration = this.properties.getConfiguration(); if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) { configuration = new Configuration(); } if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { for (ConfigurationCustomizer customizer : this.configurationCustomizers) { customizer.customize(configuration); } } factory.setConfiguration(configuration); if (this.properties.getConfigurationProperties() != null) { factory.setConfigurationProperties(this.properties.getConfigurationProperties()); } if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); } if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); } if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); } if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); } return factory.getObject(); } @Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); if (executorType != null) { return new SqlSessionTemplate(sqlSessionFactory, executorType); } else { return new SqlSessionTemplate(sqlSessionFactory); } } // 其他方法}
4.总结
a.Configuration
以Mybatis的Configuration的核心字段,进行总结
/** * @author Clinton Begin */public class Configuration { // 核心:环境(特指数据源环境,同一个Mybatis中,可以存在多个环境) // 来源:如mybatis-config.
四、Mybatis-Plus
1.简介
a.介绍
b.架构
c.特性:
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
2.实现
MP的实现,是基于Mybatis的。如果对Mybatis的源码有足够的认识,那么MP是很容易就入门的。
所以,这里不会对整个MP进行类似Mybatis的剖析。这里只针对其核心功能,进行实现的剖析。
a.BaseMapper默认模版
可以说,90%的小伙伴,都是冲着Mybatis-Plus基础mapper不用写的特点,入坑的。
那么Mybatis-Plus,是如何基于Mybatis,实现基础通用mapper呢?
答案,就是继承&覆写,Mybatis的MapperAnnotationBuilder。
Mybatis的MapperAnnotationBuilder,原先是为了提供对@Select等注解的解析。Mybatis-Plus通过继承&重写其中的parse方法,实现了Mybatis-Plus自身通用Mapper的注入。
有关MapperRegistry之前的流程,详见前文对Mybatis初始化流程中,mapper注入的部分。
MybatisMapperAnnotationBuilder
/** * 继承 * 只重写了 {@link MapperAnnotationBuilder#parse} 和 #getReturnType * 没有
Insert
Mybatis-Plus下BaseMapper的通用Mapper方法,实现都在com.baomidou.mybatisplus.core.injector.methods下。
这里,就以Insert为例,简单解析一下。
/** * 插入一条数据(选择字段插入) * * @author hubin * @since 2018-04-06 */@SuppressWarnings("serial")public class Insert extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { KeyGenerator keyGenerator = new NoKeyGenerator(); SqlMethod sqlMethod = SqlMethod.INSERT_ONE; // 构建Sql的形参 String columnScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlColumnMaybeIf(null), LEFT_BRACKET, RIGHT_BRACKET, null, COMMA); // 构建Sql的实参 String valuesScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlPropertyMaybeIf(null), LEFT_BRACKET, RIGHT_BRACKET, null, COMMA); String keyProperty = null; String keyColumn = null; // 表包含主键处理逻辑,如果不包含主键当普通字段处理 if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) { if (tableInfo.getIdType() == IdType.AUTO) { /** 自增主键 */ keyGenerator = new Jdbc3KeyGenerator(); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } else { if (null != tableInfo.getKeySequence()) { keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } } } // 按照格式要求,配合MethodType,构建对应的sql语句 String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript); // 获取对应SqlSource SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); // 通过AbstractMethod,添加MappedStatement到Mybatis容器-Configuration下Mapper容器:MapperRegistry return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn); }}
b.lambda表达式
有关Mybatis-Plus的lambda表达式的实现,涉及的点比较多。这里给一些建议:首先对函数式编程&流式编程有足够的了解,其次需要对wrapper的使用有一定认识,最后剖析Mybatis-Plus中对应部分。
具体实现,详见:
com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper
com.baomidou.mybatisplus.core.toolkit.LambdaUtils
com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda
com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper
com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper
最后,愿与诸君共进步。
五、附录
补充
- driver加载:jdbc下的driver加载分为三种方式:
- driverManager会在第一次调用时,会通过loadInitialDrivers()静态方法,对系统变量中的jdbc.drivers进行驱动注册
- 直接调用driverManager的register()方法,进行注册。
- 具体驱动,在初始化时,会调用driverManager的register()方法,进行注册。例:Class.forName("oracle.jdbc.driver.OracleDriver");
- vfs:虚拟文件系统,Mybatis用于加载服务器相关资源。具体作用,需要继续查看。其由一个抽象类VFS与两个实现类:JBoss6VFS与DefaultVFS,允许自定义实现(Stripes cannot scan classpath correctly with Spring-Boot通过自定义VFS实现,解决springBoot的嵌套jar扫描问题)。
- SpringBoot默认数据库连接池:早期采用tomcat的连接池,2.0后改为HikariCP(位于spring-starter-jdbc下)。现在使用SpringBoot2+时,mybatis自动连接SpringBoot的默认数据源HikariCP。相关注入,详见:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration。其通过org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.Hikari上的@ConditionalOnMissingBean(DataSource.class)注解,实现优先级队列
- 工厂方法:DataSourceFactory 接口扮演工厂接口的角色。UnpooledDataSourceFactory和PooledDataSourceFactory 则扮演着具体工厂类的角色。而DataSource(Java)接口扮演产品接口的角色。UnpooledDataSource和HikariDataSource都扮演着具体产品类的角色。
- MyBatis的初始化可以有两种方式:
- 基于
- 基于Java API:这种方式不使用
- MyBatis和数据库的交互有两种方式:
- 使用传统的MyBatis提供的API:如SqlSession.selectOne(String statementId, T parameter);
- 使用Mapper接口
- 只有Mapper接口方式,才会经过MapperProxyFactory,MapperProxy,MapperMethod,最终都是调用sqlSession.xxx()
参考资料
- JDBC指南(W3C版)
- 老调重弹:JDBC系列 之 <驱动加载原理全面解析>
- 老调重弹:JDBC系列 之 <JDBC层次结构和基本构成>
- 《深入理解mybatis原理》 Mybatis初始化机制详解
- 面试:你知道MyBatis执行过程之初始化是如何执行的吗?
- JDBC层次结构和基本构成
- 《Mybatis技术内幕》
- 《深入理解mybatis原理》 MyBatis的架构设计以及实例分析
- Mybatis数据源
- Mybatis实现@Select等注解动态组合SQL语句
- Stripes cannot scan classpath correctly with Spring-Boot
- Github_HikariCP
- mybatis缓存机制
- SpringFramework-Core
- Github_Mybatis-3
- Github_Mybatis-redis-cache
- Github_Mybatis-Plus
原文转载:http://www.shaoqun.com/a/612592.html
洋老板:https://www.ikjzd.com/w/2779
bonanza:https://www.ikjzd.com/w/275.html
一、前言1.持久层Java数据持久层,其本身是为了实现与数据源进行数据交互的存在,其目的是通过分层架构风格,进行应用&数据的解耦。我从整体角度,依次阐述JDBC、Mybatis、MybatisPlus。前者总是后者的依赖。只有在了解前者,才可以更好地学习后者。2.技术选型ciwai,还有Hibernate、SpringData、JPA等。至于Hibernate作为知名框架,其最大的特点,是
手机trademanager:https://www.ikjzd.com/w/730
epc:https://www.ikjzd.com/w/488
折扣网:https://www.ikjzd.com/w/74
亚马逊推出新应用商店,为卖家提供官方认证工具:https://www.ikjzd.com/home/1115
速卖通2019箱包类目攻略:透过行业趋势打造细分品类!:https://www.ikjzd.com/home/95910
思考:做亚马逊,最重要的技能是什么?:https://www.ikjzd.com/home/136571
没有评论:
发表评论