1、Mybatis 核心流程
mybatis 核心业务流程:
- 解析 mybatis 系统配置文件,封装成 Java 的 Configuration 配置类
- 解析 mapper.xml 封装成 MapperStatement 供后面使用
- 具体的业务代码 调用 dao 的代理类
- 参数的映射和处理
- 根据入参 和 MapperStatement 动态生成可执行的 sql 语句
- 通过 resultHandle 处理结果集合映射,并返回结果集
总体执行活动图
Mybatis四大对象
- ParameterHandler
- SqlSource
- Executor
- ResultSetHandler
2、流程源码分析
:::info
- 读取mybatis主配置文件 通过流来获取指定路径的文件
- InputStream is = Resources.getResourceAsStream(“MybatisConfig.xml”);
- 创建一个SqlSessionFactoryBuilder
- SqlSessionFactoryBuilder builder = new SQLSessionFactoryBuilder();
- 通过构建者模式来创建工厂
- SqlSessionFactory factory = builder.build(is);
- 通过工厂模式创建sqlSession
- SqlSession sqlSession = factory.openSession();
- 通过动态代理获取对应的mapper
- UserMapper mapper = sqlSession.getMapper(UserMapper.class);
- 调用方法
- List users = mapper.getUserList();
- 返回结果
:::
二级缓存
mybatis执行流程
StatementHandler执行流程
测试类
@Before
public void init() throws Exception {
// 指定全局配置文件路径
String resource = "mybatis-config.xml";
// 加载资源文件(全局配置文件和映射文件)
InputStream inputStream = Resources.getResourceAsStream(resource);
// 还有构建者模式,去创建SqlSessionFactory对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test1() throws Exception {
// 指定全局配置文件的类路径
SqlSession sqlSession = sqlSessionFactory.openSession();
Configuration configuration = sqlSession.getConfiguration();
TransactionFactory transactionFactory = configuration.getEnvironment().getTransactionFactory();
Connection connection = configuration.getEnvironment().getDataSource().getConnection();
}
2.1、mybatis-config.xml全局配置解析
2.1.1、使用建造者模式来创建对象工厂
**作用:**负责加载并且解析配置文件 返回SqlSessionFactory
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream);
return build(parser.parse());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
说明:使用建造者模式创建工厂 这里使用重载实现多种组合初始化 可以学习这种思想创建 和 使用 分离 ,同时: 建造类之间 相互独立 , 在 一定程度上解耦
需要一个输出流 将配置文件的内容读取出来
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) {
// try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource);
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}
说明:通过实现多个构造器来实现不同传入参数的组合。
2.1.2、解析XML Config配置的类
解析mybatis-config.xml 解析类 的建造者模式 的基类
public abstract class BaseBuilder {
// Configuration是MyBatis初始化过程的核心对象, MyBatis中几乎全部的配置信息会保存到Configuration对象中
// Configuration对象是在MyBatis初始化过程中创建且是全局唯一的,
protected final Configuration configuration;
// 在mybatis-config xml配置文件中可以使用<typeAliases>标签定义别名,这些定义的别名都会记录在TypeAliasRegistry对象中
protected final TypeAliasRegistry typeAliasRegistry;
// mybatis-config.xml 配置文件中可以使用<typeHandlers>标签添加自定义 TypeHandler器,
// 完成指定数据库类型与 Java 类型的转换,这些 TypeHandler 都会记录在 TypeHandlerRegistry
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
public Configuration getConfiguration() {
return configuration;
}
}
**说明:**这里的BaseBuilder抽象类就扮演着建造者模式的Builder角色。 通过及基类来实现控制属性 包括Configuration 全局配置类、TypeAliasRegistry 别名注册类、TypeHandlerRegistry 自定义处理器
2.1.3、解析mybatis-config.xml 的总解析类XMLConfigBuilder
public class XMLConfigBuilder extends BaseBuilder {
/* 标记是否已经解析过配置文件 */
private boolean parsed;
/* 解析xml文件的解析器 */
private final XPathParser parser;
/**
* 数据源,SqlSessionFactoryBuilder.build(InputStream in, String environment, Properties properties)
* 不指定为空
*/
private String environment;
public XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
/* 初始化 Configuration */
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
/* 设置格外的属性 */
this.configuration.setVariables(props);
/* 标记初始化为 false */
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
}
上面为解析全局配置文件的解析类 将config的信息全部解析解析 存储到对应的类中
XPathParser:是属于一个XML文件的解析类 结合XNode 来实现对配置文件解析通过Properties文件格式返回
2.1.4、全局配置的配置类Configuration
Configuration 类配置了需要配置的属性
/**
* 全局配置文件的实体类
*/
public class Configuration {
/**
* Mybatis配置类。初始化Mybatix的配置和工厂类,一些工具类的别名,语言驱动
*/
public Configuration() {
//事务工厂
//JDBC事务工厂
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
//ManagedTransaction:MyBatis自身不会去实现事务管理,而是让程序的容器如(JBOSS,Weblogic)来实现对事务的管理
//生成MangaedTransaction工厂
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
//数据源工厂
//生产具有简单,同步,线程安全数据库连接池的数据源工厂
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
//生产无连接池数据源(即每次获取请求都简单的打开和关闭连接)工厂
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
//缓存策略
//永久缓存 Cache接口实现类,里面就是维护着一个HashMap
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
//使用先进先出缓存策略的缓存装饰类
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
//使用最近最少使用的缓存策略的缓存装饰类
typeAliasRegistry.registerAlias("LRU", LruCache.class);
// 软引用回收策略 缓存装饰类
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
// 弱引用回收策略 缓存装饰类
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
/*
* XML语言驱动
* <p>
* Mybatis默认XML驱动类为XMLLanguageDriver,其主要作用于解析select|update|insert|delete节点为完整的SQL语句。
* </p>
*/
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
//这是一个简单的语言驱动,只能针对静态SQL的处里,如果出现动态SQL标签如
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
//下面是日志别名
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
//动态代理工厂,针对懒加载的
//CGLIB代理工厂
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
//Javassist代理工厂
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
//设置默认的语言驱动为XMLLanguagerDriver
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
//注册RawLanguageDriver语言驱动
languageRegistry.register(RawLanguageDriver.class);
}
}
说明:配置类中的属性比较多 就不一一列举 源码已注释,就是将初始化全局配置文件中的参数存储到Configuration类中备用或者设置,Configuration包含了该项目配置的全部参数。
2.1.5、配置解析流程
目前框架实现配置
- properties配置
- settings 配置
- TypeAliases 配置
- environments 配置
- typeHandlers 配置
- mappers 配置
对这些配置进行解析的流程:
第一步: 通过建造者模式将配置文件的输出流传入返回一个sqlSessionFactory工厂 来获取SqlSession。
// 1. 获取到了SqlSessionFactory传入properties, properties设置配置key和value
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, properties);
// 2. 调用方法: build(InputStream inputStream, String environment, Properties properties), 行为类是重载方法方式
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder:用来解析XML全局配置文件
// 使用构建者模式
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse():使用XPATH解析XML配置文件,将配置文件封装为Configuration对象
// 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
// 创建SqlSessionFactory接口的默认实现类
return new DefaultSqlSessionFactory(config);
}
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse():使用XPATH解析XML配置文件,将配置文件封装为Configuration对象
// 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
// 创建SqlSessionFactory接口的默认实现类
return new DefaultSqlSessionFactory(config);
}
}
public class XMLConfigBuilder extends BaseBuilder {
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
// 方法传入的参数存放入Configuration的variables
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
}
说明:
- 获取SqlSessionFactoryBuilder 通过建造者模式来builder一个SqlSessionFactory 通过工厂模式来实现生产SqlSession 接口 来维护一条Sql操作。
- builder 方法中实现了实例化XMLConfigBuilder 对象 解析Config配置文件 初始化项目配置。
- 调用XMLConfigBuilder中的解析方法对mybatis-config.xml 的全局配置文件解析解析。
使用到的工具类
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
//获取并遍历子节点
for (XNode child : getChildren()) {
//获取property节点的name和value属性
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
//设置属性到属性对象中
properties.setProperty(name, value);
}
}
return properties;
}
public List<XNode> getChildren() {
List<XNode> children = new ArrayList<>();
//获取子节点列表
NodeList nodeList = node.getChildNodes();
if (nodeList != null) {
for (int i = 0, n = nodeList.getLength(); i < n; i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
// 将节点对象封装到 XNode 中,并将 XNode 对象放入 children 列表中
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}
第二步:实例化一个XMLConfigBuilder 对象 传入InputStream参数 进行全局配置文件的解析 调用parse
2.1.2 那里介绍了XMLConfigBuilder 的基本参数 我们下面会使用到一个基本的解析xml的类
- 首先衔接一下上面XMLConfigBuilder类中的调用parse()方法来实现解析
XMLConfigBuilder中的parse解析xml配置文件调用类
// 是否解析过 只能解析一次配置集文件
private boolean parsed;
// XML 解析工具类
private final XPathParser parser;
// 默认数据环境配置
private String environment;
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
/**
* 解析XML配置文件
* @return
*/
public Configuration parse() {
// 这里作用是只能加载一次
if (parsed) {
throw new BuilderException("XMLConfigBuilder只能创建一次!!");
}
parsed = true;
// parser.evalNode("/configuration"):通过XPATH解析器,解析configuration根节点
// 从configuration根节点开始解析,最终将解析出的内容封装到Configuration对象中
// configuration 这个父标签开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
说明:解析xml配置文件只需要解析一次 同时 将解析配置文件到Configuration 对象中时先需要将 根标签下的子标签全部通过 XPathParser 解析器中的Xpath语法获取指定的节点存储 到 XNode 节点中(w3.dom 包下的XNode )
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
xpath 调用执行器来实现表达式获取指定标签的内容 返回一个Node 对象 最后将返回的Node对象 传参到XNode 节点中 返回一个XNode 对象。
这里为什么使用org.w3c.dom.Node 的Node呢?
DOM中每个XML的节点都是一个Node(org.w3c.dom.Node),Node通常跟XPath配合使用,提供了解析节点元素名、属性名、属性值、节点文本内容、嵌套节点等功能,
XNode 的基本属性:
- node:被包装的org.w3c.dom.Node对象
- name:节点名
- body:节点内容
- attributes:节点属性集合
- variables:mybatis-config.xml配置文件中节点下引入或定义的键值对
- xpathParser:封装了XPath解析器,XNode对象由XPathParser对象生成,并提供了解析
这里举一个栗子
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties>
<property name="database.driver" value="com.mysql.jdbc.Driver"/>
<property name="database.url" value="jdbc:mysql://localhost:3306/ssm?useSSL=false"/>
<property name="database.username" value="root"/>
<property name="database.password" value="root"/>
</properties>
<typeAliases> <!-- 别名 -->
<typeAlias alias="role" type="com.learn.ssm.chapter4.pojo.Role"/>
</typeAliases>
</configuration>
以节点为例子,生成一个对应的XNode节点后,
name:值为"typeAlias",由于没有文本内容也没有子节点所以body为空,
attributes:值为{alias=role, type=com.learn.ssm.chapter4.pojo.Role}的一个Properties对象,
variables:为{database.driver=com.mysql.jdbc.Driver, database.url=jdbc:mysql://localhost:3306/ssm?useSSL=false, database.username=root, database.password=root},
xpathParser:是构造函数传进来的一个参数,只需要知道它提供了解析XPath表达式的功能即可。
所以这里使用的Node 的实现 然后框架包装了Node 为 XNode 作为一个实体类
- 然后再了解XPathParser类的作用
XPathParser 解析类
/**
* XML解析器
*/
public class XPathParser {
// 要解析的xml文件被转化成的Document对象。
private final Document document;
// 获取document时是否要开启校验,开启校验的话会根据xml配置文件中定义的dtd文件校验xml格式,默认不开启校验。
private boolean validation;
// mybatis-config.xml配置文件中,<Properties>节点引入或定义的属性。
private Properties variables;
// 封装的XPath对象,用来解析表达式。
private XPath xpath;
/***
* 重载的构造器 可以学习 进行组合
* @param xml
*/
public XPathParser(String xml) {
commonConstructor(false, null);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public XPathParser(InputStream inputStream) {
commonConstructor(false, null);
this.document = createDocument(new InputSource(inputStream));
}
public XPathParser(Document document) {
commonConstructor(false, null);
this.document = document;
}
public XPathParser(String xml, boolean validation) {
commonConstructor(validation, null);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public XPathParser(InputStream inputStream, boolean validation) {
commonConstructor(validation, null);
this.document = createDocument(new InputSource(inputStream));
}
public XPathParser(Document document, boolean validation) {
commonConstructor(validation, null);
this.document = document;
}
public XPathParser(String xml, boolean validation, Properties variables) {
commonConstructor(validation, variables);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public XPathParser(Document document, boolean validation, Properties variables) {
commonConstructor(validation, variables);
this.document = document;
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables) {
commonConstructor(validation, variables);
// 解析XML文档为Document对象
this.document = createDocument(new InputSource(inputStream));
}
}
XPathParser的 属性值 和 构造方法
上面的工作方法都使用一个共同的构造方法 创建一个XPathParser 实例
/**
* 通用的构造方法
* 使用外部传入的参数,初始化成员变量;
* 构造XPathFactory对象,获得Xpath的对象
* @param validation 设置解析xml时是否对它进行校验。
* @param variables
*/
private void commonConstructor(boolean validation, Properties variables){
this.validation = validation;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
初始化 属性的参数后 将XNode 的参数解析 为 Document 返回 通过一个createDocument来实现创建Document对象
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 若true解析器将DTD验证被解析的文档
factory.setValidating(validation);
// 若true解析器将提供对 XML 名称空间的支持
factory.setNamespaceAware(false);
// 若true忽略注释
factory.setIgnoringComments(true);
// 若true,解析器在解析 XML 文档时,必须删除元素内容中的空格
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
// 若true,的解析器将扩展实体引用节点
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
// 通过dom解析,获取Document对象
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("不能正确创建Document文件,原因是: " + e, e);
}
}
说明:
- 通过调用 实例化XMLConfigBuilder 对象时 实例化了一个XPathParser 对象传入这个XMLConfigBuilder对象中 构造方法进行初始化操作
- 调用commonConstructor 方法实现XPathParser 对象的属性初始化 。
- 将输入的流包装成InputSource 对象传入createDocument 方法中解析XNode 的数据 保存到该对象的document属性中,这个操作就是解析之前将configuration标签下的内容全部解析成Document类中保存数据。createDocument方法中创建Document是通过工厂模式来创建出一个Builder方法 然后再通过设置该工厂参数传入输出流InputStream来创建一个Document对象。
- 实例化XPathParser 对象解析完之后 XMLConfigBuilder 类中的parse() 解析方法 调用了parser.evalNode(“/configuration”) 方法将configuration 标签下的所有子标签通过表达式将XPathParser中的Document 属性中的数据 全部解析 成XNode 然后返回。
- evalNode 方法中实现了对该父标签解析 将该document对象的子标签全部解析到了包装类的XNode 中 存储。同时将properties配置标签的数据存储到variables 属性中保存 。XPath 是实现对指定标签名进行解析到Node 节点的然后将Node 的节点全部包装到XNode 节点。
- 最后将XPath表达式解析的Node 对象 、XPathParser 和 variables 的properties参数包装成XNode 对象返回到 XMLConfigBuilder 解析方法中通过XNode解析。
- 最后将封装好的XNode对象返回到XMLConfigBuilder解析类。
第三步:对全局配置文件中的标签单独解析 配置
配置文件解析 分节点解析
/**
* 只提供 properties 标签
* settings 标签
* typeAliases标签
* environments标签
* typeHandlers 标签
* mappers 标签
* @param root
*/
private void parseConfiguration(XNode root) {
try {
// 解析</properties>标签
propertiesElement(root.evalNode("properties"));
// 解析</settings>标签
/*2、对<settings>标签中的<setting>标签中的内容进行解析,这里解析的是<setting name="vfsImpl" value=",">
* VFS是mybatis中用来表示虚拟文件系统的一个抽象类,用来查找指定路径下的资源。
* 上面的key为vfsImpl的value可以是VFS的具体实现,必须
* 是权限类名,多个使用逗号隔开,如果存在则设置到configuration中的vfsImpl属性中,如果存在多个,
* 则设置到configuration中的仅是最后一个
* */
Properties settings = settingsAsProperties(root.evalNode("settings"));
//
loadCustomVfs(settings);
// 解析</typeAliases>标签
typeAliasesElement(root.evalNode("typeAliases"));
settingsElement(settings);
// 解析</environments>标签
environmentsElement(root.evalNode("environments"));
// 解析</typeHandlers>标签
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析</mappers>标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("解析Configuration配置文件出现异常,原因为: " + e, e);
}
}
root.evalNode(“typeAliases”) 解析该节点的方法是一样的 都是包装成XNode节点之后返回
解析配置文件中的所有标签
解析properties 标签的配置
/**
* 解析<properties></properties>标签
* 我们这个框架只需要实现resource就行
* @param context
* @throws Exception
*/
private void propertiesElement(XNode context) throws Exception {
if (context != null){
// 获取子标签<property>标签的内容
Properties defaults = context.getChildrenAsProperties();
// 获取标签<properties>标签resource属性的值
String resource = context.getStringAttribute("resource");
// 如果resource或者url的值不为空,则加载对应的资源信息,然后合并到defaults中
if (resource != null) {
// 将通过resource导入进全局配置文件的properties
defaults.putAll(Resources.getResourceAsProperties(resource));
}
// 获取配置文件中 的需要替换的需要动态配置的属性值
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
// 将配置文件中的properties数据全替换掉
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
说明:
- 获取properties标签的子标签的properties 配置信息
- 获取resource属性的参数 信息 如果resource不为空 则将该路径的位置传入工具类返回Properties文件存入defaults 中
- 获取Configuration 对象的配置文件 再将当前解析的properties 文件加入全局配置类。
settings 配置标签解析
/**
* 解析setting文件
* 将settings中的标签内容解析到Properties对象中 一样的形式储存
* @param context
* @return
*/
private Properties settingsAsProperties(XNode context) {
// 说明setting已经配置过了
if (context == null) {
return new Properties();
}
// 把<setting name="" value="">标签解析为Properties对象
Properties props = context.getChildrenAsProperties();
// 校验每个属性,在 Configuration 中,有相应的 setter 方法,否则抛出 BuilderException 异常
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
// 如果获取的配置的<setting name="" value="">信息,name不在metaConfig中,则会抛出异常
// 这里metaConfig中的信息是从Configuration类中解析出来的,包含set方法的属性
// 所以在配置<setting>标签的时候,其name值可以参考configuration类中的属性,配置为小写
for (Object key : props.keySet()) {
// 从metaConfig的relector中的setMethods中判断是否存在该属性,setMethods中存储的是可写的属性,
// 所以这里要到setMethods中进行判断
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
说明:
- 将 settings_ _标签的子标签以 properties 文件格式解析返回。
- 传入 Configuration 的 class 文件 和 localReflectorFactory 参数传入 返回一个 Configuration 类继续的文件 包含set方法的属性 在使用 hasSetter 方法来判断 configuration 对象中是否存在该配置类的属性同时是否给予修改方法。
- 如果解析的全部 settings 标签在 configuration 配置中全部 能找到属性 则返回这个 properties 文件。
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
说明:我们根据标签里面的标签,生成了一个抽象类VFS 的子类,并且赋值到Configuration中。 获取系统的本地文件资源。
将这些配置设置到VfsImpl 属性当中
上面用到元信息对象创建
元信息类MetaClass的构造方法为私有类型,所以不能直接创建,必须使用其提供的forClass方法进行创建:
public class MetaClass {
private final ReflectorFactory reflectorFactory;
private final Reflector reflector;
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
//根据类型创建Reflector
this.reflector = reflectorFactory.findForClass(type);
}
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
//调用构造方法
return new MetaClass(type, reflectorFactory);
}
//...
}
上面代码出现了两个新的类ReflectorFactory和Reflector,MetaClass通过引入这些新类帮助它完成功能。关于这些类,可以先看下hasSetter方法
public boolean hasSetter(String name) {
//属性分词器,用于解析属性名
PropertyTokenizer prop = new PropertyTokenizer(name);
//hasNext返回true,则表明name是一个复合属性
if (prop.hasNext()) {
if (reflector.hasSetter(prop.getName())) {
//为属性创建创建MetaClass
MetaClass metaProp = metaClassForProperty(prop.getName());
//再次调用hasSetter
return metaProp.hasSetter(prop.getChildren());
} else {
return false;
}
} else {
// 调用 reflector 的 hasSetter 方法
return reflector.hasSetter(prop.getName());
}
}
typeAliases 别名标签 解析
/**
* typeAliases
* @param parent
*/
private void typeAliasesElement(XNode parent) {
// 如果parent 不为空
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 如果是包
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// 解析typeAlias标签
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
说明:
- 这个方法是解析别名标签 将参数别名 和 目的对象注册到 configuration 对象的别名列表中。
- 这里有一个优先级问题:包 > 其他 (如果存在包路径 )
- 如果是包路径则将包路径传入 registerAliases 注册别名方法中里面实现了通过反射。
/**
* 通过包名 来将包下所有的 java bean 来注册 别名信息
*
* @param packageName
* @param superType
*/
public void registerAliases(String packageName, Class<?> superType){
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
//把该包下的所有类进行加载,把其Class对象放到resolverUtil的matches中
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for(Class<?> type : typeSet){
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
// 将其全部解析注册
registerAlias(type);
}
}
}
上面那个方法就是将传入的包路径,传入到resolverUtil工具类中 进行所有类进行加载,然后将加载的Class对象存放到resolverUtil的matchs中 替换返回所有的Class对象解析判断每个类是否为接口、Anonymous类、member类。如果不是则注册到aliase集合中。
最后在注册方法中获取TypeAlisaRegistry 类中将注册信息存入TYPE_ALIASES属性中。
将setting解析后的Properties对象取值存入configuration对象中
/**
* 将上一个解析的Properties文件的key-value值进行设置
* 如果没有配置则设置为默认值
* @param props
*/
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
// configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
// configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
// configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
environments 环境标签解析
/**
* 解析<environments></environments>标签,
* 这个标签下面全部的数据
* XNode 会默认将${}解析掉 然后再获取值
* @param context
* @throws Exception
*/
private void environmentsElement(XNode context) throws Exception {
if (context != null){
if (environment == null){
// 获取默认的环境配置
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
// 判断当前id 是否为默认的是否为空
if (isSpecifiedEnvironment(id)){
// 解析事务
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 解析数据源
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 返回数据源
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
说明:
- 解析 environments 标签 配置环境包括事务的类别和数据源的类别
- 事务主要是包括 JDBC 和 MANAGED
- 数据源主要实现了 POOLED 和 UNPOOLED 池化 和 非池化
- 将解析的事务类型和数据源通过建造者模式将 Environment 的实例化出该建造者 然后再创建出Environment的对象。
解析事务的方法
/**
* 解析事务的处理方法
* @param context
* @return
* @throws Exception
*/
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null){
// 获取事务的类别
String type = context.getStringAttribute("type");
// 以properties格式返回子标签内容
Properties props = context.getChildrenAsProperties();
// 将事务的类型传入解析别名返回对应的Class 文件 再反射一个对象
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("配置文件需要配置一个事务类型.");
}
说明:
- 解析事务类型 获取这个标签的type参数中的数据 然后返回标签的所有内容 通过别名来反射当前别名的class 对象 然后再调用 newInstance 来返回一个事务的工厂。(别名列表注册了key value 的别名:class 类)
- 将反射处理的工厂的实例返回一个事务工厂父类 将子标签设置如工厂** 通过事务工厂返回设置别名的事务工厂**
这里为什么可以直接resolveAlias 到事务的类型? 原因是我们实例化Configuration类的时候已经将别名注册 硬编码注册了一些Class类
解析数据源的解析
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null){
// 获取DataSource标签的一个类型
String type = context.getStringAttribute("type");
// 将子标签的数据读取出来
Properties props = context.getChildrenAsProperties();
// 实现池化和非池化两种连接方式 返回一个实例
DataSourceFactory factory = (DataSourceFactory) resolveAlias(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
解析数据源方法也是一样的只是factory.setProperties 方法的实现不一样
/**
* 非池化设置Properties参数
* @param properties
*/
@Override
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
// 将DataSource传入 解析成一个对象 参数对象
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
for (Object key : properties.keySet()) {
String propertyName = (String) key;
// 驱动需要另外设置参数
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 {
String value = (String) properties.get(propertyName);
Object convertedValue = convertValue(metaDataSource, propertyName, value);
metaDataSource.setValue(propertyName, convertedValue);
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties", driverProperties);
}
}
说明:
- 使用MetaDataSource方法实现判断数据池中是否存在这个属性并且开放了设置该属性的setter 只有存在该属性才能赋值 不存在该属性值是不能赋值 metaObject 的作用大致就是这样了
- 这个有一个新的学习思路 就是我们给某个类存参数 需要先判断是否存在set方法 如果存在才能存储,如果不存在就需要抛出异常。
目前获取到了DataSource对象和Transaction对象 需要包装到Environment类中
解析typeHandlers 类处理器
什么叫typeHandlers 类处理器?
- 类处理器就是映射jdbc 中的数据类型 和 java中的数据类型 从而我们要有一个类型转换 同时也是类型映射 比如 插入数据的时候 Date ==> time 类型 就是映射作用 思想两个语言端的数据类型转换。
/**
* 解析类型处理器
* typeHandlers类型处理器的作用是在设置参数或取结果集时,映射DB和java类型。
* mybatis为我们提供了大多常见的默认类型处理器。
* @param parent
*/
private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 子节点为package时,获取其name属性的值,然后自动扫描package下的自定义typeHandler
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
// 子节点为typeHandler时, 可以指定javaType属性, 也可以指定jdbcType, 也可两者都指定
// javaType 是指定java类型
// jdbcType 是指定jdbc类型(数据库类型: 如varchar)
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
// handler就是我们配置的typeHandler
String handlerTypeName = child.getStringAttribute("handler");
// resolveClass方法就是我们上篇文章所讲的TypeAliasRegistry里面处理别名的方法
Class<?> javaTypeClass = resolveClass(javaTypeName);
// JdbcType是一个枚举类型,resolveJdbcType方法是在获取枚举类型的值
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
// 注册typeHandler, typeHandler通过TypeHandlerRegistry这个类管理
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
说明:
- 数据类型处理器 解析也有一个优先级 package > 其他
- 获取标签中的每个节点的数据类型 同时系统会默认将基本的数据类型 进行转换 用户可以使用自定义的 数据类型 进行转换映射。 将其注册到typeHandlersRegistry 映射集中。
这个TypeHandlersRegistry 和 TypeAliasRegistry 是相似的 先再type包下 创建需要转换的类转换 我这里就展示一个比较常见的转换
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}
@Override
public Integer getNullableResult(ResultSet rs, String columnName) throws SQLException {
int result = rs.getInt(columnName);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int result = rs.getInt(columnIndex);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int result = cs.getInt(columnIndex);
return result == 0 && cs.wasNull() ? null : result;
}
}
常见转换的JDBC和Java之间的转换
类型处理器 Java 类型 JDBC 类型
实际注册方法
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
if (map == null) {
map = new HashMap<JdbcType, TypeHandler<?>>();
TYPE_HANDLER_MAP.put(javaType, map);
}
map.put(jdbcType, handler);
}
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> javaTypeClass : mappedTypes.value()) {
register(javaTypeClass, typeHandlerClass);
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {
register(getInstance(null, typeHandlerClass));
}
}
/**
* 都配置了
* @param javaType
* @param jdbcType
* @param handler
*/
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
if (map == null) {
map = new HashMap<JdbcType, TypeHandler<?>>();
TYPE_HANDLER_MAP.put(javaType, map);
}
map.put(jdbcType, handler);
}
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
一个JDBCType对应一个TypeHandler类型
可以看到对于一个Java类型是可以有多个JDBC类型相对应的,所以会存在多个TypeHandler,在这种情况下需要根据传入参数的javaType以及其在数据库中对应JdbcType一起来选定一个TypeHandler进行处理(mybatis如何知道每个字段对应的数据库字段类型的?).下面来看一个具体的TypeHandler的实现:实现Java中Date类型和jdbc中JdbcType.Time类型转换的TimeOnlyTypeHandler.JDBC中的Time类型只记录时分秒,所以如果我们传入一个代表2017-11-21 11:55:59的Date对象,那么数据库中存储的时间是11:55:59.
public class TimeOnlyTypeHandler extends BaseTypeHandler<Date> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType)
throws SQLException {
ps.setTime(i, new Time(parameter.getTime()));
}
@Override
public Date getNullableResult(ResultSet rs, String columnName)
throws SQLException {
java.sql.Time sqlTime = rs.getTime(columnName);
if (sqlTime != null) {
return new Date(sqlTime.getTime());
}
return null;
}
@Override
public Date getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
java.sql.Time sqlTime = rs.getTime(columnIndex);
if (sqlTime != null) {
return new Date(sqlTime.getTime());
}
return null;
}
@Override
public Date getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
java.sql.Time sqlTime = cs.getTime(columnIndex);
if (sqlTime != null) {
return new Date(sqlTime.getTime());
}
return null;
}
}
这就实现了类型转换 。
Mapper 标签
使用mybatis的xml方式,我们需要在mybatis.xml的mappers标签下配置告诉mybatis映射的SqlMapper的xml文件的哪里,所以我们需要注册 mappers和Mapper的映射表
/**
* 解析<mappers>标签
* 使用mybatis的xml方式,我们需要在mybatis.xml的mappers标签下配置告诉mybatis映射的Sql的那里
* mappers有四种方式来加载映射的sql。
* 1.url
* 2.resource
* 3.class
* 4.package
* 优先级 package、resource、url、class
* @param parent mappers标签对应的XNode对象
* @throws Exception
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 获得全部标签
for (XNode child : parent.getChildren()) {
// 加载package的标签
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 下面只会使用一个的方式来解析
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// resource 和 url 需要使用解析类 来实现解析
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
// 通过mapperClass 的方法直接将该类直接返回Class对象
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
说明:这里需要Mapper的xml解析器XMLMapperBuilder 这里获取配置的路径 实例化一个XMLMapperBuilder 对象 来解析 mapper标签的 调用XMLMapperBuilder方法中的parse方法来实现解析mapper映射文件。解析Mapper文件我们下一个阶段解析。
这里我们只看resource的方式 来实现 为什么package的优先级比较高 实现是一个包 本身级别就高 再者我们项目里面的Mapper.xml文件就比较多。
mybatis-config.xml 配置文件解析的主要流程
2.1.6、解析全局配置文件图
mybatis-config.xml 的标签详细介绍流程
2.2、mapper.xml文件解析
映射文件的解析过程是配置文件解析过程的一部分,MyBatis会在解析配置文件的过程中对映射文件进行解析。解析逻辑封装在XMLConfigBuilder中的mapperElement方法中
代码的主要逻辑是遍历mappers的子节点,并根据节点属性值判断通过何种方式加载映射文件或映射信息。这里把配置在注解中的内容称为映射信息,以XML为载体的配置称为映射文件。
映射方式:
- 从文件系统中加载映射文件;
- 通过URL的方式加载映射文件;
- 通过mapper接口加载映射信息,映射信息可以配置在注解中,也可以配置在映射文件中;
- 通过包扫描的方式获取到某个包下的所有类,并使用第三种方式为每个类解析映射信息。
2.2.1、XMLMapperBuilder 类解析映射文件
public void parse() {
// 检测映射文件是否已经被解析过
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper节点
configurationElement(parser.evalNode("/mapper"));
// 添加资源路径到“已解析资源集合”中 将当前资源增加到已解析的集合中避免多次解析
configuration.addLoadedResource(resource);
// 通过命名空间绑定Mapper接口
bindMapperForNamespace();
}
// 处理未完成解析的节点
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
说明:
- 解析Mapper.xml 文件的第一步就是获取命名空间判断是否匹配当前的命名空间 如果匹配才会进行解析
- 然后解析cache-ref 标签
- 解析cache 标签
- 解析resultMap 结果集
- 解析sql的语句片段
- 解析CURD的4种方式
1、解析命名空间
MapperBuilderAssistant 类中的方法
public void setCurrentNamespace(String currentNamespace) {
// 如果命名空间为空
if (currentNamespace == null) {
throw new BuilderException("这个映射集 Mapper的命名空间为空!!");
}
if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
throw new BuilderException("错误的命名空间! '"
+ this.currentNamespace + "' but found '" + currentNamespace + "'.");
}
this.currentNamespace = currentNamespace;
}
2、解析cache-ref标签
说明:
- CacheRefElement解析cache-ref标签
- 获取当前命名空间 和命名空间的value 路径参数存储到configuration中的CacheRefMap中。
- 通过builderAssistant助手和当前命名空间来创建CacheRefResolver 对象来实现resolveCacheRef 解析
- 调用resolveCacheRef方法 - > userCahceRef 方法
通过命名空间来获取当前的 二级缓存Cache 然后将其设置为当前使用的缓存,并且将解析bool设置为false。
3、解析cache标签
/**
* 解析cache 标签的实现方法
* <cache
* eviction="FIFO"
* flushInterval="60000"
* size="512"
* readOnly="true"/>
* 一级缓存默认开启 二级缓存配置在映射文件中 使用者需要先开启二级缓存
* 同时可以不需要配置
* @param context
*/
private void cacheElement(XNode context) {
if (context != null) {
// 获取各种属性
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
// 获取子节点配置
Properties props = context.getChildrenAsProperties();
// 构建缓存对象
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
说明:
- 通过builderAssistant 对象调用useNewCache 方法来使用一个新的缓存对象 该方法是通过当前命名空间 来建造出一新的Cache对象 同时设置参数 并且build方法来将cache包装
一级缓存:
一级缓存是Session级别,一般一个SqlSession对象会使用一个Executor对象来完成会话操作,Executor对象会维护一个Cache缓存,以提高查询性能,减少对DB的压力。
二级缓存:
如果用户配置了cacheEnabled=true,那么在为SqlSession对象创建Executor对象时,会对Executor对象加上一个装饰者 CachingExecutor,这时SqlSession使用CachingExecutor对象完成操作请求。
CachingExecutor对查询请求,会先判断该查询请求在Application级别的二级缓存中是否有缓存结果
- 有查询结果
- 直接返回缓存结果
- 缓存未命中
再交给真正的Executor对象来完成查询操作,之后CachingExecutor会将真正Executor返回的查询结果放置到缓存中,然后再返回给用户
MyBatis二级缓存设计灵活,可以使用:
- MyBatis自己定义的二级缓存实现
- 实现org.apache.ibatis.cache.Cache接口自定义缓存
- 使用第三方内存缓存库,如Memcached
缓存是通过装饰器模式来实现的
通过delegate 来实现一个装饰者
Cache:Cache接口是缓存模块的核心接口,定义了缓存的基本操作。
public interface Cache {
String getId();//缓存实现类的id
void putObject(Object key, Object value);//往缓存中添加数据,key一般是CacheKey对象
Object getObject(Object key);//根据指定的key从缓存获取数据
Object removeObject(Object key);//根据指定的key从缓存删除数据
void clear();//清空缓存
int getSize();//获取缓存的个数
ReadWriteLock getReadWriteLock();//获取读写锁
}
PerpetualCache:缓存的基础实现类,使用HashMap来实现缓存功能。
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<>();
...
BlockingCache:阻塞版本的装饰器,保证只有一个线程到数据库去查指定key的数据。
public class BlockingCache implements Cache {
//阻塞的超时时长
private long timeout;
// 被装饰的底层对象,一般是PerpetualCache
private final Cache delegate;
// 锁对象集,粒度到key值
private final ConcurrentHashMap<Object, ReentrantLock> locks;
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<>();
}
@Override
public Object getObject(Object key) {
acquireLock(key);// 根据key获得锁对象,获取锁成功加锁,获取锁失败阻塞一段时间重试
Object value = delegate.getObject(key);
if (value != null) {// 获取数据成功的,要释放锁
releaseLock(key);
}
return value;
}
private ReentrantLock getLockForKey(Object key) {
ReentrantLock lock = new ReentrantLock();// 创建锁
ReentrantLock previous = locks.putIfAbsent(key, lock);// 把新锁添加到locks集合中,如果添加成功使用新锁,如果添加失败则使用locks集合中的锁
return previous == null ? lock : previous;
}
// 根据key获得锁对象,获取锁成功加锁,获取锁失败阻塞一段时间重试
private void acquireLock(Object key) {
// 获得锁对象
Lock lock = getLockForKey(key);
if (timeout > 0) {// 使用带超时时间的锁
try {
boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
if (!acquired) {// 如果超时抛出异常
throw new CacheException("Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
}
} catch (InterruptedException e) {
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
}
} else {// 使用不带超时时间的锁
lock.lock();
}
}
private void releaseLock(Object key) {
ReentrantLock lock = locks.get(key);
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
...
private final Cache delegate;
如果我们设置的是默认的cache缓存 就直接将缓存设置
最后返回cache 将该装饰者模式包装好的cache设置到configuration对象中 返回的cache对象就是头结点。
上面就是实现装饰者模式 :饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
4、解析resultMap标签
/**
* 解析结果集的参数 集合
* <resultMap id="authorResult" type="User">
* <id property="id" column="id"/>
* <result property="name" column="name"/>
* </resultMap>
* @param list
* @throws Exception
*/
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
一个一个进行解析
真正实现解析resultMap的方法 resultMapElement 方法
/**
* 解析 resultMap 方法
* <resultMap id="articleResult" type="Article">
* <id property="id" column="id"/>
* <result property="title" column="article_title"/>
* <association property="article_author" javaType="Author">
* <id property="id" column="author_id"/>
* <result property="name" column="author_name"/>
* </association>
* </resultMap>
* @param resultMapNode
* @return
*/
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception{
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 查找是否能 找到 不能则设置为默认
// 获取type属性
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 解析type属性对应的类型
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
// 结果集 的列表
List<ResultMapping> resultMappings = new ArrayList<>();
// 将以前的结果集全部添加进来
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
// 解析constructor节点,并生成相应的 ResultMapping
// 目前未实现该标签
// processConstructorElement(resultChild, typeClass, resultMappings);
throw new BuilderException("目前未实现constructor 构造器标签");
} else if ("discriminator".equals(resultChild.getName())) {
// 解析discriminator节点
// discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
throw new BuilderException("目前未实现discriminator 鉴别器标签");
} else {
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
//添加ID到flags集合中
flags.add(ResultFlag.ID);
}
// 解析id和property节点,并生成相应的ResultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
说明:
- 获取resultMap的Id 默认拼装所有父节点的id 或 value 或 property
// 获取 ID , 默认值会拼装所有父节点的 id 或 value 或 property。
String id=resultMapNode.getStringAttribute("id",resultMapNode.getValueBasedIdentifier());
这里涉及到 XNode 对象中的两个函数:getStringAttribute()以及getValueBasedIdentifier()
/**
* 生成元素节点的基础 id
* @return
*/
public String getValueBasedIdentifier() {
StringBuilder builder = new StringBuilder();
XNode current = this;
// 当前的节点不为空
while (current != null) {
// 如果节点不等于 this, 则在0之前插入 _ 符号, 因为是不断的获取父节点的, 因此是插在前面
if (current != this) {
builder.insert(0, "_");
}
// 获取 id, id不存在则获取value, value不存在则获取 property。
String value = current.getStringAttribute("id",
current.getStringAttribute("value",
current.getStringAttribute("property", null)));
// value 非空, 则将.替换为_, 并将value的值加上 []
if (value != null) {
value = value.replace('.', '_');
builder.insert(0, "]");
builder.insert(0,
value);
builder.insert(0, "[");
}
// 不管 value 是否存在, 前面都添加上节点的名称
builder.insert(0, current.getName());
// 获取父节点
current = current.getParent();
}
return builder.toString();
}
- 解析结果集中的类型,结果集的类型, 对应的是一个 JavaBean 对象。通过反射来获得该类型。
// 获取type, type 不存在则获取 ofType, ofType
// 不存在则获取 resultType, resultType 不存在则获取 javaType
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// ... ...
// 获取 type 对应的 Class 对象
Class<?> typeClass = resolveClass(type);
- 获取继承结果集和自动映射
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
这两个属性在配置XML的时候,显得有些可有可无。
5、解析sql语句片段
/**
* 如果有显式声明 databaseId ,那只有符合当前全局 databaseId 的 SQL 片段会提取;
* 如果没有声明 databaseId ,则会全部提取。
* 解析了两遍 SQL 片段,而且在每一次循环解析中,都会判断一次 SQL 片段是否匹配当前 databaseId ,
* 匹配的话就会放到一个 sqlFragments 的 Map 中
* @param list
* @param requiredDatabaseId
*/
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
// 鉴别当前SQL片段是否匹配
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
}
}
7、XMLMapperBuilder总体流程
XMLMapperBuilder标签解析总体流程
2.2.2、XMLStatementBuilder类解析
XMLStatementBuilder创建MappedStatement的大致流程:
- 获取SQL节点的属性值
- 使用XMLIncludeTransformer解析include节点
- 创建MappedStatement实例,保存到Configuration#mappedStatements中
- 默认使用XMLLanguageDriver解析SQL节点得到SqlSource,使用各种类型的NodeHandler、SqlNode解析SQL节点。SqlSource有两种:**DynamicSqlSource **SQL语句有${param0}(文本替换,可能是一段SQL)或者使用了if/where(运行时决定是否拼接SQL片段)等节点需要运行时才能解析出要执行的SQL。**RawSqlSource **使用了#{param1}占位符或者没有使用,就是一个完整SQL,不需要运行时解析得到SQL。
- 创建MappedStatement,保存到Configuration#mappedStatements中
/**
* 解析<select>\<insert>\<update>\<delete>子标签
* 步骤解析:
* parseStatementNode() 是 XMLStatementBuilder 解析的入口,首先,他会去找CURD标签上的唯一id,
* 然后就是databaseId,databaseId是数据库厂商标识,MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId
* 的语句,如果带和不带的语句都有,则不带的会被忽略,一般选择databaseId的时候是需要配置多数据库厂商的时候。
* 在解析完databaseId 之后就是解析flushCache、useCache等标签,默认情况下,如果是select语句,则是开启缓存的
*
* 说明:我们再来是没有设置databaseId的, 所以实现会将所有的语句全部扫描 然后进行解析。
*/
public void parseStatementNode() {
// 获取statement的id属性(特别关键的值)
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
// 将全部sql判断扫描
// 解析<select|update|delete|insert>标签属性
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
// 获取入参类型
String parameterType = context.getStringAttribute("parameterType");
// 别名处理,获取入参对应的Java类型
Class<?> parameterTypeClass = resolveClass(parameterType);
// 获取ResultMap
String resultMap = context.getStringAttribute("resultMap");
// 获取结果映射类型
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 别名处理,获取返回值对应的Java类型
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
// 设置默认StatementType为Prepared,该参数指定了后面的JDBC处理时,采用哪种Statement
// 这里默认大写
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
// 解析sql命令类型是什么 确定crud中的哪一类
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 是否查询语句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
// <include>标签解析
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
// 解析<selectKey>标签 当前版本框架还未实现selectKey标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// 创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
/**
* 我们不实现主键生成
*/
// String resultSets = context.getStringAttribute("resultSets");
// String keyProperty = context.getStringAttribute("keyProperty");
// String keyColumn = context.getStringAttribute("keyColumn");
// KeyGenerator keyGenerator;
// String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
// keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
// if (configuration.hasKeyGenerator(keyStatementId)) {
// keyGenerator = configuration.getKeyGenerator(keyStatementId);
// } else {
// keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
// configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
// ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
// }
// 通过构建者助手,创建MappedStatement对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,databaseId, langDriver);
}
说明:
- 获取当前节点标签下的全部属性的参数值
- 使用LanguageDriver创建SqlSource对象维护一个动态的sql。
1、Include标签解析
解析Include标签
创建XMLIncludeTransformer对象 来解析include标签
// 先获取全局的配置变量,也就是mybatis-config.xml配置文件中的<properties></properties>下配置的变量。
// 然后再调用重载方法进行处理
public void applyIncludes(Node source) {
Properties variablesContext = new Properties();
Properties configurationVariables = configuration.getVariables();
if (configurationVariables != null) {
variablesContext.putAll(configurationVariables);
}
applyIncludes(source, variablesContext, false);
}
/**
* <sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
* <select id="selectUsers" resultType="map">
* select
* <include refid="userColumns"><property name="alias" value="t1"/></include>,
* from some_table t1
* cross join some_table t2
* </select>
* @param source
* @param variablesContext
* @param included
*/
// 递归处理include标签中的内容
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
// 获取结点为include的节点
if (source.getNodeName().equals("include")) {
// 查找refid对应的sql片段,并返回sql片段对象的深拷贝
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
// 合并include中配置的局部变量和mybatis配置文件中生命的全局变量
Properties toIncludeContext = getVariablesContext(source, variablesContext);
applyIncludes(toInclude, toIncludeContext, true);
if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
toInclude = source.getOwnerDocument().importNode(toInclude, true);
}
// 将include替换为sql
source.getParentNode().replaceChild(toInclude, source);
while (toInclude.hasChildNodes()) {
toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
}
// 移除include标签
toInclude.getParentNode().removeChild(toInclude);
} else if (source.getNodeType() == Node.ELEMENT_NODE) {// 替换<sql></sql>标签属性的占位符
if (included && !variablesContext.isEmpty()) {
// 用真实值替换节点属性中的占位符
NamedNodeMap attributes = source.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node attr = attributes.item(i);
attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
}
}
// 递归处理标签下所有子标签的占位符
NodeList children = source.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
applyIncludes(children.item(i), variablesContext, included);
}
} else if (included && source.getNodeType() == Node.TEXT_NODE
&& !variablesContext.isEmpty()) {
// 用真实值替换文本或CDATA区中的占位符
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
}
}
说明:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
from some_table t1
cross join some_table t2
</select>
- 获取到configuration对象存储的properties标签中的数据
- 判断标签中的数据是否为空,如果不为null则复制全部数据待后面使用
- 判断当前节点是include标签还是element标签还是文本标签
- 如果是include标签则获取到refid属性的参数 解析到里面的参数 存在占位符则替换掉 并且拼接命名空间 到configuration对象存储的SQLFragments的Map中通过refid查找XNode节点 通过深拷贝返回Node节点、因为一个sql片段会被多次引用 所有一定是原始对象的拷贝。
- 获取include中的参数属性 name和value的属性获取 然后和configuration对象中的数据合并返回
- 递归处理include标签中的数据防止 include的标签中嵌套了标签
替换sql的意思是将refid的节点替换include中的数据 通过获取的sql片段来替换
- 将inclue标签移除 保留sql片段
- 如果_替换标签属性的占位符 因为引用sql标签的深克隆是会保留占位符的所以需要进行替换_
- 获取结点下的全部节点 通过遍历Node节点来替换节点属性中的占位符 同时处理完递归处理所有子标签的占位符
- 替换SQL标签
<select id="selectUsers" resultType="map">
select
<sql id="userColumns"> t1.id,t1.username,t1.password </sql>,
from some_table t1
cross join some_table t2
</select>
- 将sql的内容插入到select节点中对应的位置
<select id="selectUsers" resultType="map">
select
t1.id,t1.username,t1.password
<sql id="userColumns"> t1.id,t1.username,t1.password </sql>,
from some_table t1
cross join some_table t2
</select>
- 移除sql多余的节点标签
<select id="selectUsers" resultType="map">
select
t1.id,t1.username,t1.password,
from some_table t1
cross join some_table t2
</select>
执行流程:
- 解析include元素
- 递归解析include元素
- 查找SQL片段
- include子节点属性值替换占位符
2、SelectKey标签解析
解析selectKey标签
目前还未实现解析selectKey标签
3、LanguageDriver 创建
通过LanguageDriver驱动来创建SqlSource动态SQL
/**
* 解析动态sql语句 标签
* 解析select\insert\ update\delete标签中的SQL语句,
* 最终将解析到的SqlNode封装到MixedSqlNode中的List集合中
* 将带有${}号的SQL信息封装到TextSqlNode
* 将带有#{}号的SQL信息封装到StaticTextSqlNode
* 将动态SQL标签中的SQL信息分别封装到不同的SqlNode中
* @return
*/
public SqlSource parseScriptNode() {
//解析DML标签下的子节点,封装成MixedSqlNode
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
//只创建DynamicSqlSource对象。完整可执行的sql,需要在调用getBoundSql(Parameter)方法时才能组装完成。
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
/**
* 调用 SqlSourceBuilder类将"#{xxx}“ 替换为占位符”?",并绑定ParameterMapping,
* 最后返回的RawSqlSource中持有一个由SqlSourceBuilder构建的SqlSource对象。
*/
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
说明:
- 最终将解析到的SqlNode封装到MixedSqlNode中的List集合中 所以先判断是否为动态语句 通过parseDynamicTags方法判断 同时返回一个MixedSqlNode对象最后的结果对象
- 获取该节点下的全部节点遍历 获取子节点下的全部标签解析存储到指定的列表中存储 判断是处理文本节点还是元素节点
如果是文本节点 获取文本的内容存储到TextSQLNode对象中保存标签将该标签下的文本对象add到contents的List中存储 统一返回
如果是元素节点则通过节点名来获取SQL标签的处理器 同时设置动态标签是dynamic的 将List列表传入MIE的SQLNode对象存储并且返回。 MixedSqlNode对象是存储SqlNode结合的 就是存储哪些SQLNode集合
contents可能包含 StaticTextSqlNode和IfSqlNode或者WhereSqlNode或者其他。 当sql语句中存在
的话就是
d
y
n
a
m
i
c
的通过
T
e
x
t
S
Q
L
N
o
d
e
来判断是否为动态标签如果存在
{}的话就是dynamic 的 通过TextSQLNode来判断是否为动态标签 如果存在
的话就是dynamic的通过TextSQLNode来判断是否为动态标签如果存在{}则加入到contents动态标签中 以TextSQLNode对象存储到contents中 如果不存在
的话以
S
t
a
t
i
c
T
e
x
t
S
Q
L
N
o
d
e
对象存储到
c
o
n
t
e
n
t
s
中记住存在
{}的话以StaticTextSQLNode对象存储到contents 中 记住存在
的话以StaticTextSQLNode对象存储到contents中记住存在{}就是dynamic 的需要存储到TextSQLNode类中 如果是除${}的话就是static的sql语句只需要使用apply 字符串追加操作
- StaticTextSqlNode 最简单的SQLNode 功能只能拼接到context上下文
- TextSQLNode 表示包含 占位符的动态 S Q L 节点里面会使用 G e n e r i c T o k e n P a r s e r 解析“ {}占位符的动态SQL节点 里面会使用GenericTokenParser解析“ 占位符的动态SQL节点里面会使用GenericTokenParser解析“{}”占位符,并直接替换成传入的实际参数值。实际参数值获取逻辑在内部BindingTokenParser.handleToken中。
- IfSqlNode 看传入的参数值是否有满足if test里的表达式,满足将SQL片段合并到context上下文中。若不满足test则过滤掉这一部分SQL片段,不添加到context中。
如果存在 等标签 需要执行多次 存储到contents中 通过里面的IfHandler处理器来对if语法的处理 是否满足if 里的内容 满足则将if标签以IfSqlNode 对象put到contents 中
- WhereSqlNode 、SetSqlSet 节点 标签的 SqlNode 实现类,继承至WhereSqlNode。
标签的 SqlNode 实现类,继承至SetSqlNode。WhereSqlNode会传入prefix=WHERE和prefixesToOverride=["AND ","OR ",“AND\n”, “OR\n”, “AND\r”, “OR\r”, “AND\t”, “OR\t”] - TrimSqlNode 标签的 SqlNode 实现类
如果是动态标签就add到系统的contents的List中然后存储到MixedSqlNode对象中 以备后面使用。
- 通过判断是否为动态标签解析 如果是动态标签则实例化DynamicSQLSource 对象存储sql 调用getBoundSql才会使用
- 获取到MixedSqlNode 对象 判断当前节点是为dynamic节点还是#{}节点 如果是dynamic节点则需要都要DynamicSQLSource对象 #{}节点只需要调用RawSqlSource节点维护。
- 调用MapperBuilderAssistant构造助手创建MappedStatement 对象 使用建造者模式创建一个MappedStatement对象 同时获取ParameterMap对象设置到Map配对Statement对象中 最后在将创建的MappedStatement对象存储到configuration对象中。
- 将resource 添加到configuration对象中
bindMapperForNamespace 绑定Mapper接口
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
这几个方法就是解析还未解析的方法 内容不太多 重新解析
这里就已经将Mapper解析完成
4、XMLScriptBuilder类的流程
5、XMLStatementBuilder总体流程
2.3、SqlSession会话获取
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取数据源环境信息
final Environment environment = configuration.getEnvironment();
// 获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 获取JdbcTransaction或者ManagedTransaction 设置参数
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建Executor执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
说明:
- openSessionFromDataSource 方法需要获取数据源、事务工厂、JdbcTransaction管理 、创建Executor执行器 最后将configuration对象和执行器 是否自动提交传入DefaultSqlSession类中创建对象
- 返回事务
通过事务工厂 返回一Transaction 事务实例
- configuration.newExecutor(tx, execType); 创建Executor执行器。
- 最后创建一个DefaultSqlSession 实例对象,出现异常则需要关闭事务。
- 放回SqlSession 对象
2.4、Mapper代理对象获取
@SuppressWarnings("unchecked")
// getMapper方法返回mapper的动态代理生成的对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据Mapper接口的类型,从Map集合中获取Mapper代理对象工厂 存放了mapper标签解析的反射类
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 通过MapperProxyFactory生产MapperProxy,通过MapperProxy产生Mapper代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
说明:
- 调用MapperRegistry类中 getMapper方法来;返回mapper的动态代理生成的对象, 该类存放了代理对象的mapper 首先通过knowMappers 集合中通过class对象获取代理工厂 如果工厂为空则抛出异常 通过MapperProxyFactory来生成MapperProxy
- 通过MapperProxyFactory来生产MapperProxy
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 使用JDK动态代理方式,生成代理对象 使用代理反射 mapperProxy实现invoke类(实现了InvocationHandler接口的类)
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
// 创建基于JDK实现的Mapper代理对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 创建实例对象
return newInstance(mapperProxy);
}
- Proxy.newProxyInstance 通过传入MapperInterface接口的反射类(UserMapper.class)来实例化代理对象
2.5、执行方法
1、动态代理的实现类 invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 是Interface接口还是Object类
// 如果方法是Object类自带的方法,比如没有被重写的equals toString, hashcode 等,还是执行原来的方法 不需要拦截
// getDeclaringClass()返回表示声明由此 Method 对象表示的方法的类或接口的 Class 对象。
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
// 调用接口的默认方法
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 如果不是object的自带方法,先去 Map<Method, MapperMethod> methodCache中找是否已经存在这个method了,
// 没有就将method封装成MapperMethod存进methodCache中然后返回MapperMethod。
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行sqlSession的方法
return mapperMethod.execute(sqlSession, args);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result = null;
// 很明显我们的SqlCommandType是SELECT类型
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
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:
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 {
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());
}
//是否返回类型是void类型并且Method参数列表中包含resultHandler,具体的判断在文末分析
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;
}
说明:
- 获取command.getType() 的类别通过Switch 来分支
- 解析insert
将参数列表中的参数通过集合形式以注解中的value作为key 而形参作为value 存储到param的集合中以Object类型返回到前一次操作。
2、Statement设置参数
为Statement设置参数
/**
* 设置参数
* @param ps
* @throws SQLException
*/
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 获取要设置的参数映射信息
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 对每条语句进行传参 JDBC 一样的使用 通过序号设置参数
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// 只处理入参 pstmt.setString(1, "姓名" + i);类型
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
// 获取属性名称
String propertyName = parameterMapping.getProperty();
// 判断是否存在该传入参数
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 获取每个参数的类型处理器,去设置入参和获取返回值
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 获取每个参数的JdbcType
JdbcType jdbcType = parameterMapping.getJdbcType();
// 如果都为空则获取为空的值
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 给PreparedStatement设置参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException(
"Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException(
"Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
说明:
- 先获取boundSql语句中的参数列表
- 通过遍历对语句中的?占位符进行替换
- 通过判断parameterMapping.getMode() != ParameterMode.OUT 参数类型
3、选择操作解析
SELECT操作解析
case SELECT:
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 {
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;
说明:
- 对返回值为空同时存在返回集处理器 上面的this.method.returnsMap()是指@MapKey控制返回
这里涉及到了一个
onvertArgsToSqlCommandParam解析入参
public Object convertArgsToSqlCommandParam(Object[] args) {
return this.paramNameResolver.getNamedParams(args);
}
解析@Param注解
names是SortedMap,在构造函数中赋值的,判断入参有没有@Param注解。
key是入参的顺序,从0开始;value是@Param中的值
如果没有@Param注解,则value值为arg0、arg1…
将入参名和值匹配
public Object getNamedParams(Object[] args) {
int paramCount = this.names.size();
if (args != null && paramCount != 0) {
// 只有一个入参时,返回入参值
if (!this.hasParamAnnotation && paramCount == 1) {
return args[((Integer)this.names.firstKey()).intValue()];
} else {
// 多个入参时,返回一个Map
Map<String, Object> param = new ParamMap();
int i = 0;
for(Iterator i$ = this.names.entrySet().iterator(); i$.hasNext(); ++i) {
Entry<Integer, String> entry = (Entry)i$.next();
param.put(entry.getValue(), args[((Integer)entry.getKey()).intValue()]);
String genericParamName = "param" + String.valueOf(i + 1);
if (!this.names.containsValue(genericParamName)) {
param.put(genericParamName, args[((Integer)entry.getKey()).intValue()]);
}
}
return param;
}
} else {
return null;
}
}
示例1: 多个入参,没有加@Param注解
@Select("SELECT count(0) from es_inter_invokfaillog where rownum = #{num} and invok_type=#{type}")
public Integer selectAny(int num,String type);
样执行sql会报错,找不到num,可以改为#{arg1}或#{param1}
示例2:多个入参,加@Param注解
@Select("SELECT count(0) from es_inter_invokfaillog where rownum = #{num} and invok_type=#{type}")
public Integer selectAny(@Param("num")int num,@Param("type")String type);
result = sqlSession.selectOne(this.command.getName(), param);
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
sqlSessionProxy是动态代理生成的,每一次执行方法时都会重新去 new 一个DefaultSqlSession,可以看下invoke方法部分代码
// 获取session,这里有个事物的判断
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
// 真正执行sql语句的地方
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = result;
}
查询是调用select的查询方法
- 正在进行query的方法是在StatementHandler中的query方法中
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行PreparedStatement,也就是执行SQL语句
ps.execute();
// 处理结果集
return resultSetHandler.handleResultSets(ps);
}
先获取PreparedStatement对象在对当前的sql语句解析execute 执行。
同时将ps传入返回几个集处理方法中进行JDBC和Java的类型转换
4、返回结果集解析 转换
/**
* 返回集参数设置 处理 {@code stmt} 中的所有结果集,返回结果对象集合
*
* @param stmt
* @return
* @throws SQLException
*/
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
// 设置日志的上下文
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// <select> 标签的ResultMap属性,可以指定多个值,多个值之间用(,)分隔
final List<Object> multipleResults = new ArrayList<>();
// 初始化结果集数量为0
int resultSetCount = 0;
// 这里是获取第一个结果集,将传统JDBC的ResultSet包装成一个包含结果列元信息的ResultSetWrapper对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 获得ResultMap 即查询结果的转换关系
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
// 验证ResultMap标签对象数量,如果rsw不为null且ResultMapCount小于1,就会抛出异常
validateResultMapsCount(rsw, resultMapCount);
// 遍历多个查询结果集 一般只有一个 重复调用的是存储过程
// 如果结果集包装类对象不为null 且 配置的ResultMap标签对象数量大于结果集数量
while (rsw != null && resultMapCount > resultSetCount) {
// 获取当前结果集对应的ResultMap
ResultMap resultMap = resultMaps.get(resultSetCount);
/**
* 构建出来的结果对象,如果父级结果属性映射不为null,会将结果对象赋值到父级结果属性对应的结果对象中,
* 否则将结果对象加入到resultHandler中。最后从resultHandler中取的最终的结果对象加入到多个结果
* 对象集合中
*/
// 转换结果集,并且添加到multipleResults中 根据resultMap处理rsw生成java对象
handleResultSet(rsw, resultMap, multipleResults, null);
// 获取下一个查询结果集
rsw = getNextResultSet(stmt);
// 清空嵌套resultMap
cleanUpAfterHandlingResultSet();
// 每处理完一次,结果集数量+1
resultSetCount++;
}
// resultSets是针对多结果集的情况下,给每个结果集一个名称,多个名称之间使用,分割
String[] resultSets = mappedStatement.getResultSets();
// 如果结果集不为null
if (resultSets != null) {
// 如果结果包装对象不为null 而且 结果集数量小于配置的结果集名数量
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
// 一样的调用 这里只是对多个结果集进行处理
handleResultSet(rsw, resultMap, null, parentMapping);
}
// 获取下一个结果集
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
// 如果multipleResults只有一个元素,只会返回该元素对象;否则返回multipleResult
return collapseSingleResultList(multipleResults);
}
- 实现获取第一个结果集 来解析 判断ResultSet 结果集
如果getResultSet()不为空则resultSet != **null **? **new **ResultSetWrapper(resultSet, configuration) : null; 将其包装成ResultSetWrapper 对象返回 该类将ResultSet包装
- 判断包装的ResultSetWrapper 对象是否为空 同时判断ResultMapCount 是否大于ResultSetCount 如果大于的话ResultMap标签对象数量大于结果集数量,则可以存储到configuration。
- 获取当前结果的结果集 ResultMap 然后转换结果集 handleResultSet(rsw, resultMap, multipleResults, null);
/**
* 构建出来的结果对象,如果父级结果属性映射不为null,会将结果对象赋值到父级结果属性对应的结果对象中,
* 否则将结果对象加入到resultHandler中。最后从resultHandler中取的最终的结果对象加入到多个结果
* 对象集合中
* @param rsw 结果集包装对象
* @param resultMap resultMap标签对象
* @param multipleResults 多个结果对象集合
* @param parentMapping 父级结果属性映射
* @throws SQLException
*/
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) {
try {
// 如果 父级结果属性映射不为null
if (parentMapping != null) {
/**
* 构建出来的结果对象,如果父级结果属性映射不为null,会将结果对象赋值到父级结果属性对应的结果对象中,
* 否则将结果对象加入到resultHandler中
*/
//这里因为parentMapping已经确定不为null了,所以resultHandler传入null也不会有问题。
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
// 第一次一般来说resultHandler为空 则创建DefaultResultHandler 来处理
if (resultHandler == null) {
// 如果用户没有指定对结果的处理器ResultHandler,那么默认会使用DefaultResultHandler
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
// 对结果集进行转换
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
// 将解析后的结果添加到multiResults中
// 如果没有指定ResultHandler,那么默认会将解析之后的结果添加到multipleResults中
multipleResults.add(defaultResultHandler.getResultList());
} else {
// 用户定义了对结果集的处理方法,即ResultHandler
// 那么使用ResultSetHandler处理之后,会将结果再交给ResultHandler进行处理
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
这里上面的方法就是转换结果集 创建DefaultResultHandler 对象来处理结果集
通过嵌套解析 我们只管理不嵌套的结果集 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); 根据结果集包装类对象和简单的ResultMap标签对象,构建成结果对象
- 获取jdbc的结果集 然后先跳过分页的offset 同时判断是否超过指定的行数 如果还未超过指定的limit 指定的行数 就循环解析
- 对resultMap 和 对象的映射解析是getRowValue 方法才进行解析的
/**
* 映射结果集到pojo上
* (1)createResultObject 生成对象
* (2)applyAutomaticMappings 自动映射
* (3)applyPropertyMappings property映射
*
* @param rsw
* @param resultMap
* @param columnPrefix
* @return
* @throws SQLException
*/
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
// 延迟加载的映射信息
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 创建要映射的PO类对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
// 结果对象不为空且类型处理器也不为空
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 是否应用自动映射,也就是通过resultType进行映射
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 根据columnName和type属性名映射赋值
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 根据我们配置ResultMap的column和property映射赋值
// 如果映射存在nestedQueryId,会调用getNestedQueryMappingValue方法获取返回值
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
/**
* 取得构造函数所需的参数值去创建结果对象
* @param rsw 结果集包装类对象
* @param resultMap Mapper.xml的resultMap标签信息封装类对象
* @param constructorArgTypes 构造函数参数类型集合
* @param constructorArgs 构造函数参数集合
* @param columnPrefix 列名前缀
* @return {@link ResultMap#getType()} 类型的对象
*/
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
List<Object> constructorArgs, String columnPrefix) throws SQLException {
// 获取结果对象类型
final Class<?> resultType = resultMap.getType();
// 构建结果对象类型的元对象
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
// 获取构造函数映射关系
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
// 如果存在对应的TypeHandler
if (hasTypeHandlerForResultObject(rsw, resultType)) {
// 创建原始的结果对象
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
// 构造函数映射关系不为空
} else if (!constructorMappings.isEmpty()) {
// 根据构造函数映射构建的结果对象
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
// 如果resultType是接口或者resultType有默认的构造函数
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
// 让objectFactory创建通过默认构造函数resultType对象
return objectFactory.create(resultType);
// 是否可以应用自动映射
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
// 取得构造函数所需的参数值去创建结果对象(自动映射)
return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
说明:
- 获取resultMap中存储的resultType 的反射类 resultType 通过反射获取到MetaClass 对象来操作目的对象
通过ObjectFactory工具类来通过构造函数来创建一个实例对象 将一个rowValue的Object对象
- 进入方法applyPropertyMappings 来实现结果集和属性映射映射 applyAutomaticMappings
这里的Object自定义的实体类返回查询就已经写出来了
2.6、部分知识图
2
2.7、mybatis使用过的设计模式
- Builder模式 :例如 SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
- 工厂模式 :例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
- 单例模式 :例如ErrorContext和LogFactory;
- 代理模式 :Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;
- 组合模式 :例如SqlNode和各个子类ChooseSqlNode等;
- 模板方法模式 : 例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;
- 适配器模式 : 例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
- 装饰者模式 : 例如cache包中的cache.decorators子包中等各个装饰者的实现;
- 迭代器模式 : 例如迭代器模式PropertyTokenizer;
1、Builder模式
在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的MybatisMapConfig.xml和所有的Mapper.xml文件,构建Mybatis运行的核心对象Configuration对象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。
其中XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder用于读取.Mapper文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句。
在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法的解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此大量采用了Builder模式来解决。
对于builder的具体类,方法都大都用build*开头,比如SqlSessionFactoryBuilder为例,它包含以下方法:
SqlSessionFactoryBuilder
即根据不同的输入参数来构建SqlSessionFactory这个工厂对象。
2、工厂模式
简单工厂模式
SqlSession可以认为是一个Mybatis工作的核心的接口,通过这个接口可以执行执行SQL语句、获取Mappers、管理事务。类似于连接MySQL的Connection对象。
SqlSessionFactory
可以看到,该Factory的openSession()方法重载了很多个,分别支持autoCommit、Executor、Transaction 等参数的输入,来构建核心的SqlSession对象。
在DefaultSqlSessionFactory的默认工厂实现里,有一个方法可以看出工厂怎么产出一个产品:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call
// close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这是一个openSession调用的底层方法,该方法先从configuration读取对应的环境配置,然后初始化TransactionFactory获得一个Transaction对象,然后通过Transaction获取一个Executor对象,最后通过configuration、Executor、是否autoCommit三个参数构建了SqlSession。
在这里其实也可以看到端倪,SqlSession的执行,其实是委托给对应的Executor来进行的。
而对于LogFactory,它的实现代码:
public final class LogFactory {
private static Constructor<? extends Log> logConstructor;
private LogFactory() {
// disable construction
}
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
3、单例模式
单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。
单例模式
在Mybatis中有两个地方用到单例模式,ErrorContext和LogFactory,其中ErrorContext是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息,而LogFactory则是提供给整个Mybatis使用的日志工厂,用于获得针对项目配置好的日志对象。
ErrorContext的单例实现代码:
public class ErrorContext {
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
private ErrorContext() {
}
public static ErrorContext instance() {
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext();
LOCAL.set(context);
}
return context;
}
构造函数是private修饰,具有一个static的局部instance变量和一个获取instance变量的方法,在获取实例的方法中,先判断是否为空如果是的话就先创建,然后返回构造好的对象。
只是这里有个有趣的地方是,LOCAL的静态实例变量使用了ThreadLocal修饰,也就是说它属于每个线程各自的数据,而在instance()方法中,先获取本线程的该实例,如果没有就创建该线程独有的ErrorContext。
4、代理模式
代理模式
这里有两个步骤,第一个是提前创建一个Proxy,第二个是使用的时候会自动请求Proxy,然后由Proxy来执行具体事务;
当我们使用Configuration的getMapper方法时,会调用mapperRegistry.getMapper方法,而该方法又会调用mapperProxyFactory.newInstance(sqlSession)来生成一个具体的代理:
/**
* @author Lasse Voss
*/
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> 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<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
在这里,先通过T newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象,然后调用T newInstance(MapperProxy mapperProxy)生成代理对象然后返回。 先获取到Mapper的代理对象 因为MapperProxy是实现了InvocationHandler接口使用 后面调用操作方法的时候就会默认调用invoke方法 然后再去执行execute执行方法 。
而查看MapperProxy的代码,可以看到如下内容:
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
非常典型的,该MapperProxy类实现了InvocationHandler接口,并且实现了该接口的invoke方法。
通过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个Mapper接口的时候,就会转发给MapperProxy.invoke方法,而该方法则会调用后续的sqlSession.cud>executor.execute>prepareStatement等一系列方法,完成SQL的执行和返回。
5、组合模式
Mybatis支持动态SQL的强大功能,比如下面的这个SQL:
<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
UPDATE users
<trim prefix="SET" prefixOverrides=",">
<if test="name != null and name != ''">
name = #{name}
</if>
<if test="age != null and age != ''">
, age = #{age}
</if>
<if test="birthday != null and birthday != ''">
, birthday = #{birthday}
</if>
</trim>
where id = ${id}
</update>
在这里面使用到了trim、if等动态元素,可以根据条件来生成不同情况下的SQL;
在DynamicSqlSource.getBoundSql方法里,调用了rootSqlNode.apply(context)方法,apply方法是所有的动态节点都实现的接口:
public interface SqlNode {
boolean apply(DynamicContext context);
}
对于实现该SqlSource接口的所有节点,就是整个组合模式树的各个节点:
SqlNode
组合模式的简单之处在于,所有的子节点都是同一类节点,可以递归的向下执行,比如对于TextSqlNode,因为它是最底层的叶子节点,所以直接将对应的内容append到SQL语句中:
@Override
public boolean apply(DynamicContext context) {
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
context.appendSql(parser.parse(text));
return true;
}
但是对于IfSqlNode,就需要先做判断,如果判断通过,仍然会调用子元素的SqlNode,即contents.apply方法,实现递归的解析。
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
6 、模板方法模式
在Mybatis中,sqlSession的SQL执行,都是委托给Executor实现的,Executor包含以下结构:
Executor接口
其中的BaseExecutor就采用了模板方法模式,它实现了大部分的SQL执行逻辑,然后把以下几个方法交给子类定制化完成:
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
该模板方法类有几个子类的具体实现,使用了不同的策略:
- 简单SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)
- 重用ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。(可以是Statement或PrepareStatement对象)
- 批量BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或PrepareStatement对象)
比如在SimpleExecutor中这样实现update方法:
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null,
null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
7、适配器模式
在Mybatsi的logging包中,有一个Log接口:
/**
* @author Clinton Begin
*/
public interfaceLog{
booleanisDebugEnabled();
booleanisTraceEnabled();
voiderror(String s, Throwable e);
voiderror(String s);
voiddebug(String s);
voidtrace(String s);
voidwarn(String s);
}
该接口定义了Mybatis直接使用的日志方法,而Log接口具体由谁来实现呢?Mybatis提供了多种日志框架的实现,这些实现都匹配这个Log接口所定义的接口方法,最终实现了所有外部日志框架到Mybatis日志包的适配:
比如对于Log4jImpl的实现来说,该实现持有了org.apache.log4j.Logger的实例,然后所有的日志方法,均委托该实例来实现。
public classLog4jImplimplementsLog{
private static final String FQCN = Log4jImpl.class.getName();
private Logger log;
publicLog4jImpl(String clazz) {
log = Logger.getLogger(clazz);
}
@Override
publicbooleanisDebugEnabled() {
return log.isDebugEnabled();
}
@Override
publicbooleanisTraceEnabled() {
return log.isTraceEnabled();
}
@Override
publicvoiderror(String s, Throwable e) {
log.log(FQCN, Level.ERROR, s, e);
}
@Override
publicvoiderror(String s) {
log.log(FQCN, Level.ERROR, s, null);
}
@Override
publicvoiddebug(String s) {
log.log(FQCN, Level.DEBUG, s, null);
}
@Override
publicvoidtrace(String s) {
log.log(FQCN, Level.TRACE, s, null);
}
@Override
public void warn(String s) {
log.log(FQCN, Level.WARN, s, null);
}
}
8、装饰者模式
装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。
在mybatis中,缓存的功能由根接口Cache(org.apache.ibatis.cache.Cache)定义。整个体系采用装饰器设计模式,数据存储和缓存的基本功能由PerpetualCache(org.apache.ibatis.cache.impl.PerpetualCache)永久缓存实现,然后通过一系列的装饰器来对PerpetualCache永久缓存进行缓存策略等方便的控制。如下图:
Cache
用于装饰PerpetualCache的标准装饰器共有8个(全部在org.apache.ibatis.cache.decorators包中):
- FifoCache:先进先出算法,缓存回收策略
- LoggingCache:输出缓存命中的日志信息
- LruCache:最近最少使用算法,缓存回收策略
- ScheduledCache:调度缓存,负责定时清空缓存
- SerializedCache:缓存序列化和反序列化存储
- SoftCache:基于软引用实现的缓存管理策略
- SynchronizedCache:同步的缓存装饰器,用于防止多线程并发访问
- WeakCache:基于弱引用实现的缓存管理策略
另外,还有一个特殊的装饰器TransactionalCache:事务性的缓存
正如大多数持久层框架一样,mybatis缓存同样分为一级缓存和二级缓存
- 一级缓存,又叫本地缓存,是PerpetualCache类型的永久缓存,保存在执行器中(BaseExecutor),而执行器又在SqlSession(DefaultSqlSession)中,所以一级缓存的生命周期与SqlSession是相同的。
- 二级缓存,又叫自定义缓存,实现了Cache接口的类都可以作为二级缓存,所以可配置如encache等的第三方缓存。二级缓存以namespace名称空间为其唯一标识,被保存在Configuration核心配置对象中。
二级缓存对象的默认类型为PerpetualCache,如果配置的缓存是默认类型,则mybatis会根据配置自动追加一系列装饰器。
Cache对象之间的引用顺序为:
SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache
9、迭代器模式
比如Mybatis的PropertyTokenizer是property包中的重量级类,该类会被reflection包中其他的类频繁的引用到。这个类实现了Iterator接口,在使用时经常被用到的是Iterator接口中的hasNext这个函数。
public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
private String name;
private String indexedName;
private String index;
private String children;
public PropertyTokenizer(String fullname) {
int delim = fullname.indexOf('.');
if (delim > -1) {
name = fullname.substring(0, delim);
children = fullname.substring(delim + 1);
} else {
name = fullname;
children = null;
}
indexedName = name;
delim = name.indexOf('[');
if (delim > -1) {
index = name.substring(delim + 1, name.length() - 1);
name = name.substring(0, delim);
}
}
public String getName() {
return name;
}
public String getIndex() {
return index;
}
public String getIndexedName() {
return indexedName;
}
public String getChildren() {
return children;
}
@Override
public boolean hasNext() {
return children != null;
}
@Override
public PropertyTokenizer next() {
return new PropertyTokenizer(children);
}
@Override
public void remove() {
throw new UnsupportedOperationException(
"Remove is not supported, as it has no meaning in the context of properties.");
}
}
可以看到,这个类传入一个字符串到构造函数,然后提供了iterator方法对解析后的子串进行遍历,是一个很常用的方法类。