前言:
-
这个链接中有mybatis的各个对象说明,一定要结合看:https://blog.csdn.net/hancoder/article/details/111244516
-
个人源码解析地址:https://blog.csdn.net/hancoder/article/details/110152732
-
mybatis插件分析:https://blog.csdn.net/hancoder/article/details/110914534
-
本文主要按解析xml、获取session、执行sql等分为几个大章节
-
有的小章节前面加了【】,是代表面试比较常见的问题,比如@Param、分页和sql注入等问题
-
设计模式的Builder建造者模式、装饰者模式很重要。插件中也用到了代理模式和责任链模式
-
动态代理模式也得看会才能看这个,最起码invocationHandler和invoke知道怎么回事
一、xml文件解析
- XMLConfigBuilder是解析根标签是Configuration的
- XMLMapperBuilder是解析跟标签是Mapper的
SqlSessionFactoryBuilder
- build()的方法是传入一个配置文件流参数,返回一个SqlSessionFactor实例对象。在这里就是传入configuration配置,得到一个SqlSessionFactory实例
// SqlSessionFactoryBuilder.java
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 开始创建sqlSessionFactory
// 1.我们最初调用的build
public SqlSessionFactory build(InputStream inputStream) {
//调用了重载方法
return build(inputStream, null, null);
}
// 2.调用的重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder是专门解析mybatis的配置文件的类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//这里又调用了一个重载方法Configuration。parser.parse()的返回值是Configuration对象
return build(parser.parse());//XMLConfigBuilder的parse()返回一个Configuration对象
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} //省略部分代码
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
总结:
两个Builder,
XMLConfigBuilder是parser
parser.parse()就是返回Builder包含的Configuration对象
sqlSessionFactoryBuilder.build(config)返回都是DefaultSqlSessionFactory对象。
XMLConfigBuilder
- XMLConfigBuilder中的属性configuration在父类BaseBuilder中
- 通过parse()方法返回一个Configuration实例
// XMLConfigBuilder
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());//BaseBuilder
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;// XML里的成员parser = new XPathParser(reader, true, props, new XMLMapperEntityResolver())
}
// 返回Configuration
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;//也就是说这个函数只能进来一次
parseConfiguration(parser.evalNode("/configuration"));//这里进去把config的属性注入好
return configuration;
}
mybatis-config.xml元素解析
解析mybatis.xml配置文件中的各个标签。
值得注意的是,先解析好properties,然后就可以把设置注入到configuration中了
//------XMLConfigBuilder---------------
private void parseConfiguration(XNode root) {
//Xnode是一个XML,properties还没注入
try {
// 解析properties
propertiesElement(root.evalNode("properties")); //issue #117 read properties first // 在这里把配置才拿出来
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));// 解析拦截器插件
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 //<environments> //函数传入的时候后root.evalNode("environments")整体已经把配置拿到了
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
上面解析的是mybatis.xml配置文件,值得注意的是在解析其中<mappers>
标签的时候,会解析指定的Xxxmapper.xml文件
①properties配置解析
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="root"/>
<property name="password" value="123123"/>
</properties>
解析properties
//---------------------
//先解析properties把属性设置好 // propertyConfigurer
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
// 配置文件的属性有url或者resource
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
//不能同时指定
throw new BuilderException("不能同时指定url和res");
}
if (resource != null) {
// 如果指定的是resource
// 得到一个Properties对象,然后放到configuration.variables这个Properties中
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
// 如果指定的是url
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
②解析数据源environment、DataSource
会把解析出来的内容放到configuration 属性中,然后用属性解析数据源
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
//---------------------
// 解析完properties后就能${}拿到了
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");
if (isSpecifiedEnvironment(id)) {
// 事物 mybatis有两种:JDBC 和 MANAGED, 配置为JDBC则直接使用JDBC的事务,配置为MANAGED则是将事务托管给容器
// <transactionManager type="JDBC"/>
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//enviroment节点下面就是dataSource节点了,解析dataSource节点
// 解析dataSource标签,解析到后先set到DataSourceFactory中,下步再拿出来
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));//看下面函数
// 得到数据源
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 设置environment给configuration
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
//---------------------
// 数据源
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
// 解析多个property标签,数据源的${}肯定从这里面替换
Properties props = context.getChildrenAsProperties();// 获得属性
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();// 获得class对象,然后new
factory.setProperties(props);//把属性设置到数据源
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
// 解析多个property标签
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
/**
* 解析typeAliases 节点
* <typeAliases>
* <!--<package name="com.lpf.entity"></package>-->
* <typeAlias alias="UserEntity" type="com.lpf.entity.User"/>
* </typeAliases>
* @param parent
*/
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//如果子节点是package, 那么就获取package节点的name属性, mybatis会扫描指定的package
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
//TypeAliasRegistry 负责管理别名, 这儿就是通过TypeAliasRegistry 进行别名注册
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
//如果子节点是typeAlias节点,那么就获取alias属性和type的属性值
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);
}
}
}
}
}
/**
* 解析typeHandlers节点
* 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,
* 还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
* Mybatis默认为我们实现了许多TypeHandler, 当我们没有配置指定TypeHandler时,
* Mybatis会根据参数或者返回结果的不同,默认为我们选择合适的TypeHandler处理。
* @param parent
* @throws Exception
*/
private void typeHandlerElement(XNode parent) throws Exception {
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");
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);
}
}
}
}
}
插件标签解析
/**
* 解析plugins标签
* mybatis中的plugin其实就是个interceptor,
* 它可以拦截Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler 的部分方法,处理我们自己的逻辑。
* @param parent
* @throws Exception
*/
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
// 我们在定义一个interceptor的时候,需要去实现Interceptor
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
// 向configuration对象中注册拦截器
configuration.addInterceptor(interceptorInstance);
}
}
}
PropertyParser
PropertyParser这个类中包含一个内部私有的静态类VariableTokenHandler。VariableTokenHandler实现了TokenHandler接口,包含了一个Properties类型的属性,在初始化这个类时需指定该属性的值。VariableTokenHandler类对handleToken函数的具体实现如下:
https://blog.csdn.net/u014213453/article/details/40399475
③mapper元素解析
获取sql、加载接口
配置要注册的mapper有四种方式(优先级由高到低)
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
addMapper
// XMLConfigBuilder.java
// 解析configuration标签中的mapper子标签
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//mappers/ mappers/<package name="">
// ① 找包下的接口,然后找同名的xml //这时候没有MSC
if ("package".equals(child.getName())) {
// 拿到包名
String mapperPackage = child.getStringAttribute("name");
// 得到包下所有的类,依次调用addMapper(mapperClass);,而addMapper(mapperClass)第一句就是判断这个类是不是接口,不是直接的话直接跳过
configuration.addMappers(mapperPackage);
} else {
// mappers/<mapper resource="">
// 获取resource/url/class属性
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 上面3个对应下面3个
// ② resource属性不为空,如XxxMapper.xml
if (resource != null && url == null && mapperClass == null) {
// resource不为空
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 解析mapper的xml // 传入了configuration参数,那么肯定就设置到configuration里面了
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
// ③url,和上面的逻辑一样,就是解析xml而已
} 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
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 这个方法和上面resource里面最终依次调用的方法一样,就是对接口进行操作
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
// 上面解析mapper标签主要内容为把标签的内容放到XMLMapperBuilder实例中,然后调用parse方法,parse方法的作用是解析另一个xml文件
在上面我们知道:
- package和class就是扫描接口而已
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
,然后调用parser.parse();
- config是MapperRegistry 类持有的私有属性
- MapperRegistry类还有一个属性是knownMappers ,已经加载过的mapper集合,是从接口类到MapperProxyFactory的映射map,在addMapper(接口.class)的时候,第一句就是先knownMappers .put,告诉这个接口加载过了,即每个接口只能加载一次,否则报错
- 而对于xml:
好吧我们先不管class的问题了,先看看如何把xml的内容解析好然后再调用addMapper了
上面解析mapper标签主要内容为把标签的内容放到XMLMapperBuilder实例中,然后调用parse方法,parse方法的作用是解析另一个xml文件(XxxMapper.xml)
addMappers
MapperXml解析好后,我们接着刚才的addMapper分析
[①]addMapper(包)
// 如果<mappers>标签里指定的是接口包路径的话
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
//MapperRegistry
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
// 遍历包下的接口
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
[②]addMapper(接口)与MapperAnnotationBuilder
public <T> void addMapper(Class<T> type) {
// 如果是接口
if (type.isInterface()) {
// 有没有该类型的mapper(用接口全类名作为key查),如果有则报错,每个接口只能解析一遍
if (hasMapper(type)) {
throw new BindingException("MapperRegistry中已经有这个类型了,不要重复解析。解析完的接口都会让MapperRegistry知道");
}
boolean loadCompleted = false;
try {
// 没有被解析过该接口,开始解析该接口,放入已解析的map中,防止后面重复解析
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 比如解析接口方法上的@Select(slect * from a)
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
// 如果未完成解析,移除key
knownMappers.remove(type);
}
}
}
}
虽然还没有学习解析xml,但还是想在这里加几句方便复习。在xml阶段底层还是会走到addMapper(接口)这里。hasMapper(type)这个判断也能倒推出xml的解析并没有解析完,只是把解析的信息放到configuration中,然后调用addMapper(接口),直到解析接口的时候拿到xml的信息,再加载注解的信息。所以注解的信息优先级高。
MapperAnnotationBuilder就是上面这个过程
[③]addMapper(xml)与XMLMapperBuilder
实际上是没有addMapper(xml)这个东西的,只有解析UserMapper.xml的过程
那我们看看如果是xml的话怎么办吧
倒退到解析xml文件
思想:解析xml的<mapper>
标签元素,放到对应的接口
//XMLConfigBuilder.java
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
//XMLMapperBuilder.java
public void parse() {
// resourse:"mapper/UserMapper.xml"
if (!configuration.isResourceLoaded(resource)) {
//从这里看出解析mapper.xml时会看有没有被解析过,如果解析过了config会知道,config持有一个Set<String>
// 1
/** ★★★解析mapper标签,即解析sql语句,将sql语句的所有信息封装成MappedStatement对象,然后存储在configuration对象中。
*/
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);// 解析后加到config的Set<String>中
// 2
/* 绑定mapper到接口类,里面有addMapper */
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
③.1
解析sql加到configuration中,解析XxxMapper.xml中,<mapper>
标签下的子标签,如<select>
,当然还包括<resultMap>
等
// XMLMapperBuilder.java
// 解析mapperXML里的configuration
private void configurationElement(XNode context) {
//mapper标签里的内容
try {
String namespace = context.getStringAttribute("namespace");// 属性
// 设置当前解析的接口。从这里也可以推测builderAssistant对象是全局单例的
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));//Ele才是子标签
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// ★★★解析CRUD标签 // 参数类型为List<XNode> list
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//select子标签
} catch (Exception e) {
throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
}
}
③.1.1 解析mapper.xml中的每个sql
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// 我们只有一个select标签,所以list.size==1,如果还有别的标签就++
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 去parse标签内容
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
【解析标签】③.1.1.1 parseStatementNode
解析mapperxml中的sql标签
public void parseStatementNode() {
// 此时的id还只是方法名,如"selectUser"
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
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
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes,
// in case if IncompleteElementException (issue #291)
List<XNode> selectKeyNodes = context.evalNodes("selectKey");
if (configuration.getDatabaseId() != null) {
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
}
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
// 动态sql和静态sql # 动态sql解析完问号都没有转
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 省略一些解析mapper文件里属性的内容
。。。;
// ★★★重点代码 // id:"selectUser" resource:"mapper/UserMapper.xml"
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
③.1.1.1.1
addMappedStatement
public MappedStatement addMappedStatement(
String id,//"selectUser",一会就拼接上类名了
SqlSource sqlSource,
StatementType statementType,// STATEMENT, PREPARED, CALLABLE
SqlCommandType sqlCommandType,//UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
// 把selectUser加上全类名 "mapper.UserMapper.selectUser"
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)// mapper/UserMapper.xml
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
//为null
statementBuilder.parameterMap(statementParameterMap);
}
// MappedStatement解析,即sql的封装,只是封装了MappedStatement
MappedStatement statement = statementBuilder.build();
// 放到configuration.mappedStatements这个map中,key为全类名+方法mapper.UserMapper.select,value为MappedStatement对象 // 一下放了两个key,全类名+方法、方法两个,总之出来的时候map.size已经是2了。可以通过ms.getId()得到key
configuration.addMappedStatement(statement);
return statement;
}
到这里解析完了xml到configuration.mappedStatements这个map中
③.2 bindMapperForNamespace():
接着往下执行XMLMapperBuilder.parse()
实际上就是解析该sql对应的class,并把该class放到configuration.mapperRegistry
中。实际上mybatis的所有配置信息以及运行时的配置参数全部都保存在configuration对象中。mapperRegistry这个东西我们之前提过,是一个map,管理着接口到MapperProxyFactory的映射
// XMLMapperBuilder.java
private void bindMapperForNamespace() {
// 我们是在解析每个mapper,设置过当前解析的接口是哪个,所以能拿到接口的namespace
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 获取接口类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
// 如果接口类型不为空
if (boundType != null) {
// 如果mapperRegistry中没有该接口类型,mapperRegistry和configuration都是全局唯一的,知道接口有没有被解析过
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
// 前面我们也执行过一个同样的方法,只不过现在这个加了个namespace前缀放到这个set,现在这个set中有两个,"mapper/UserMapper.xml"和" configuration.addLoadedResource("namespace:" + namespace);
configuration.addLoadedResource("namespace:" + namespace);
// 又到了addMapper(接口)的时候了,config.addMapper内部就是调用的是mapperRegistry.addMapper(type);// 不管是config还是mapperRegistry都是唯一的,addMapper互相"重载"
configuration.addMapper(boundType);
}
}
}
}
还有一个小疑问:xml解析后怎么设置到configuration中的,让configuration知道xml的解析信息
分析了一下,原来是并没有把解析出来的xml信息和mapper接口关联,而是xml信息仅仅和configuration关联
然后直接去调用config.addMapper(接口)
[④]殊途同归
通过上面分析,不管是接口还是包还是xml,最终都是调用addMapper(接口),只不过包的话是挨个调用,xml的话是先封装xml的信息放到configuration中,addMapper时候再拿出来
回忆addMapper(接口类)
// MapperRegistry.java
public <T> void addMapper(Class<T> type) {
// 如果是接口
if (type.isInterface()) {
// knownMappers<Class,MapperProxyFactory>中有没有该类型的mapper(用接口全类名作为key查),如果有则报错,每个接口只能解析一遍
if (hasMapper(type)) {
throw new BindingException("MapperRegistry中已经有这个类型了,不要重复解析。解析完的接口都会让MapperRegistry知道");
}
boolean loadCompleted = false;
try {
// 没有被解析过该接口,开始解析该接口,put(接口,创建代理工厂),防止后面重复解析
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 比如解析接口方法上的@Select(slect * from a)
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
好吧,就是new了个MapperAnnotationBuilder,然后parse
MapperAnnotationBuilder
下面的parse是addMapper的parse,即对接口的parse,把xml的信息和注解信息都放到接口中
接下来依次解析xml和注解形式的sql。注解会覆盖xml的
// MapperAnnotationBuilder.java
public void parse() {
// type是接口的名字 ,接口的toString方法是形如"interface mapper.UserMapper"
String resource = type.toString();
// 判断接口是否被加载过
if (!configuration.isResourceLoaded(resource)) {
// 如果这个接口关联着之前的xml解析出来的内容,那么添加一下 // 如果我们在<mappers>标签里指定的是class,那么进入到这里去加载对于的xml,而resource的方式我们已经加载过了,所以进去又跳出来了
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
// 获取接口中的方法
Method[] methods = type.getMethods();
// 遍历接口当前的方法看有没有注解 // 理论上接口上的注解会覆盖xml里的,但是实际上是报错
for (Method method : methods) {
try {
// 解析注解,没有注解又跳出来了
parseStatement(method);//解析xml的时候把sql语句翻译成MappedStatement对象,加到configuration全局对象中//也就是放到configuration.mappedStatement这个StrictMap中(继承hashmap),这个map的特点是第二个put时直接抛异常,也就是避免了xml和注解方式的重复
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
//
private void loadXmlResource() {
// config和MapperRegistry是全局唯一的,他们知道xml是否被解析过
// 加载一下,而对于resource方式的,在bindmapper阶段,我们已经加过了,所以不进if
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 拼凑接口对应的xml地址
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
// 看有没有xml文件
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
// 是否存在xml一个文件
if (inputStream != null) {
// 又是一个XML Builder,解析配置文件
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
@Select
在刚才的解析中,我们说了如果是xml没有相应注解的话直接跳出来了,如果有注解的话会进入parseStatement,他的最终结果也是往configuration里加了个mappedStatement,id和xml的一样,也就是说,注解会覆盖xml的
parseStatement处理接口的方法
void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
//
LanguageDriver languageDriver = getLanguageDriver(method);
// SqlSource是接口方法上的注解,相应方法上没有的话就是null
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
// 如果没有注解的话就拿不到sqlSource
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
// 全类名+方法,mapper.UserMapper.insertUser
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
SqlCommandType sqlCommandType = getSqlCommandType(method);
// 是@Select还是@Insert。。。
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 如果不是select的话,说明要清空缓存
boolean flushCache = !isSelect;
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = "id";
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else // 为null
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
} else {
keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = new NoKeyGenerator();
}
// null,不进入
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();
}
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
// 不进入
if (resultMapAnnotation != null) {
String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
//不是select类型,也不进入,没有ResultMap的事儿
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
// 注解同样是add一个mappedStatement
assistant.addMappedStatement(
mappedStatementId,//mapper.UserMapper.insertUser
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
上面解析了如<select>
标签和注解,把最终得到的信息都设置到了configuration属性的mappedStatements属性中。是个StrictMap
【静态sql#{}】
要注意一下头几行
void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
// 从注解获取sql
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
.....
}
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
try {
// 获取sql的注解类型
Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
throw new BindingException("不能同时提供静态SQL和 SqlProvider");
}
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
// 进入
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
} else if (sqlProviderAnnotationType != null) {
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
}
return null;
} catch (Exception e) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e);
}
}
// 此处我们只分析带有@Select注解的方法,所有就是进入if条件中进行处理,这里再进入buildSqlSourceFromStrings方法。
private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
final StringBuilder sql = new StringBuilder();
for (String fragment : strings) {
sql.append(fragment);
sql.append(" ");
}
// 进入
return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
}
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
SqlSource source = super.createSqlSource(configuration, script, parameterType);
checkIsNotDynamic(source);
return source;
}
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
if (script.startsWith("<script>")) {
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
// issue #127
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}
// 构造函数
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}
在两个方法中做了最后的sql包装,在createSqlSource方法中,第一种是处理带有**
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
}
这里实例化了一个DynamicCheckerTokenParser对象,但是进这个实例化方法中可以看到并没有做什么事情,主要是createParser中,转到createParser方法。
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
然后下一步对是否有"${", "}"进行判断,parser.parse(text);,最后返回是否返回是否是动态sql,在这毫无疑问的是我们前面写的方法中,第一个是返回的RawSqlSource对象,第二个返回的是DynamicSqlSource对象
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
// 此处判断sql中是否含有"#{""}",parser.parse(originalSql)中解析源码的过程比较复杂,没有太多可以说的,但是得说下占位符的替换,在替换参数是最终会调用到SqlSource中的handlerToken方法。
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
此处进行了参数的替换,以及对字段的映射。
然后最终返回StaticSqlSource对象,所以此处RawSqlSource最后还是返回StaticSqlSource对象,然后在StaticSqlSource对象中执行getBoundSql方法,可以进StaticSqlSource的getBoundSql中看看
@Override
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
二、创建sqlSession
前面做了什么:为每个接口生成了个mapperProxyFactory
SqlSession是一个接口,它有两个实现类:DefaultSqlSession(默认)和SqlSessionManager(弃用,不做介绍)
SqlSession是MyBatis中用于和数据库交互的顶层类,通常将它与ThreadLocal绑定,一个会话使用一个SqlSession,并且在使用完毕后需要close。
SqlSession中的两个最重要的参数,configuration与初始化时的相同,Executor为执行器,
2.1 创建sqlSession
解析xml的过程就是创建SqlSessionFactory的过程,我们解析完上面的xml文件后,得到了一个DefaultSqlSessionFactory对象。调用它的openSession方法就得到了一个SqlSession,每个SqlSession对应一种执行器
①openSession
当执行openSession()操作的时候,就是给当前会话创建一个执行器
// DefaultSqlSessionFactory.java
public SqlSession openSession(boolean autoCommit) {
// 传入configuration里的执行器类型,默认是simple
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
// 获得environment标签解析出来的内容
final Environment environment = configuration.getEnvironment();
// 从Environment配置信息获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);//new JdbcTransaction(ds, level, autoCommit);
// 初始化事务, 此时事务持有dataSource对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//return new JdbcTransaction(ds, level, autoCommit);
// ****核心代码*****
// 根据 事务和execType创建Executor对象, Executor通过Transaction间接持有DataSource对象
// 通过执行器执行查询sql
final Executor executor = configuration.newExecutor(tx,
execType);// enum ExecutorType{ SIMPLE,REUSE,BATCH}
// 初始化SqlSession对象, SqlSession持有configuration和executor引用
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();
}
}
从上面知道,在返回DefaultSqlSession之前,new了个执行器configuration.newExecutor(tx, execType);
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// executorType如果未配置, 默认为Simple
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 未配置默认开启
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 通过拦截器加载plugin, 返回对象为代理对象
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
public Object pluginAll(Object target) {
// 此处遍历Plugins标签内所有Plugin进行加载
// 通过代理对象再代理模式, 保证每一个拦截器都能被执行
// 此处注意点 : 代理对象可以被再次代理
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);//代理对象可以被再次代理
}
return target;
}
2.2、创建执行器
Executor是MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。Executor是Mybatis的四大对象之一
Executor会在执行openSession()的时候创建,返回的sqlSession里包含了指定的执行器。执行器分3种:
- SimpleExecutor(普通的执行器,默认)
- BatchExecutor(重用语句并执行批量更新)
- ReuseExecutor(重用预处理语句prepared statements)

上层接口是 SimpleExecutor/ReuseExecutor/SimpleExecutor
–>BaseExecutor
–>Executor
- BaseExecutor持有一级缓存属性
- cachingExecutor管理二级缓存,装饰者模式。持有Executor delegate;
- cachingExecutor–>Executor,这个执行器里面有个类型为Executor的delegate属性(委托/装饰)
https://tech.meituan.com/2018/01/19/mybatis-cache.html
public abstract class BaseExecutor implements Executor {
// BaseExecutor持有一级缓存localCache,类型是PerpetualCache
protected PerpetualCache localCache;
protected Configuration configuration;
//=========CachingExecutor.java=============
public class CachingExecutor implements Executor {
// cache在MappedStatement中持有
private Executor delegate;
private boolean autoCommit; // issue #573. No need to call commit() on autoCommit sessions
private TransactionalCacheManager tcm = new TransactionalCacheManager();
private boolean dirty;
Cache: MyBatis中的Cache接口,提供了和缓存相关的最基本的操作,如下图所示:
有若干个实现类,使用装饰器模式互相组装,提供丰富的操控缓存的能力,部分实现类如下图所示:
BaseExecutor
成员变量之一的PerpetualCache
,是对Cache接口最基本的实现,其实现非常简单,内部持有HashMap,对一级缓存的操作实则是对HashMap的操作。如下代码所示:public class PerpetualCache implements Cache { private String id; private Map<Object, Object> cache = new HashMap<Object, Object>();
②newExecutor
// Configuration.java // 从openSessionFromDataSource()中过来
public Executor newExecutor(Transaction transaction,
ExecutorType executorType,
boolean autoCommit) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 3种执行器
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);//基本就创建了一个一级缓存map而已
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 如果二级缓存开启,是使用CahingExecutor装饰BaseExecutor的子类(装饰者模式)
if (cacheEnabled) {
executor = new CachingExecutor(executor, autoCommit);
}
// 插件 拦截器,后面讲 // 通过代理对象再代理模式, 保证每一个拦截器都能被执行
executor = (Executor) interceptorChain.pluginAll(executor);//Configuration.interceptorChain
return executor;
}
/*
默认情况下会返回一个SimpleExecutor对象。然后SimpleExecutor被封装到DefaultSqlSession。
这里我们需要注意一下,在Executor创建完毕之后,会根据配置是否开启了二级缓存,来决定是否使用CachingExecutor包装一次Executor。最后尝试将executor使用interceptorChain中的每个interceptor包装一次(根据配置),这里是对Mybatis强大的插件开发功能做支持。
*/
③new SimpleExecutor
public class SimpleExecutor extends BaseExecutor {
// 构造器
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
// 父类构造器
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
}
创建完后用二级缓存重新包装了一下(开启二级缓存情况下),装饰者的体现。
④interceptorChain.pluginAll(executor);
注意这里只是代理执行器
- Executor 是 openSession() 的 时 候 创 建 的 ,创建之后即调用 InterceptorChain.pluginAll(),返回层层代理后的对象。
- StatementHandler 是SimpleExecutor.doQuery()创建的,里面包含了处理参数的 ParameterHandler 和处理结果集的 ResultSetHandler 的创建,创建之后即调用 InterceptorChain.pluginAll(),返回层层代理后的对象。
// executor = (Executor) interceptorChain.pluginAll(executor);
public Object pluginAll(Object target) {
// 此时目标对象是执行器
for (Interceptor interceptor : interceptors) {
// 解析xml阶段准备好的拦截器
target = interceptor.plugin(target);//
}
return target;
}
// 自定义的Interceptor实现类
public Object plugin(Object target) {
// 此时目标对象是执行器
return Plugin.wrap(target, this);// 创建代理
}
// Plugin.java
public static Object wrap(Object target, // 此时目标对象是执行器
Interceptor interceptor) {
// xml配置的handler拦截器
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 动态代理
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Intercepts({
@Signature(type = Executor.class,
method = "query",
args = {
MappedStatement.class,Object.class,RowBounds.class,ResultHanlder.class }) })
public class MyPageInterceptor implements Interceptor{
还要注意到,在返回执行器前,还会执行器用拦截器包装了一下。
Executor 初始化时, 关联加载 Plugin 插件,并生成Executor代理对象,此处注意:代理对象可以被再次代理,而且只是代理了拦截器的插件,别的插件在查询阶段才代理
三、创建代理
流程:
- 创建sqlSession
- MapperProxyFactory全局每个类型只有一个
- 但是每个会话都对应一个MapperProxy(每个类型都有自己的MapperProxy)
①getMapper
我们使用getMapper方法获取接口对应的代理对象。
当我们使用如下代码:
UserMapper mapper = session.getMapper(UserMapper.class);
来获取UserMapper的时候,实际上是从configuration当中的MapperRegistry当中获取UserMapper的代理对象:
// DefaultSqlSession
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);//session转交给configuration
}
// Configuration--------
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);//configuration交给mapperRegistry注册器
}
所以getMapper是在最后获取的
//MapperRegistry--------
// 通过mapper代理工厂得到一个mapper代理
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//此时的T就相当于比如User
// 根据传入的接口获取MapperProxyFactory // 工厂生产代理对象。在config中每个接口都有唯一的mapper代理工厂
// knownMappers属性里面的值,实际上就是我们在mappers扫描与解析的时候放进去的mapper代理工厂。
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null)
// 找不到借口对应的mapper代理工厂,就是说在解析阶段没有加载过这个接口
throw new BindingException("MapperRegistry/config没解析过这个接口");
try {
// 动态代理 // 创建接口的动态代理 // 从mapperProxyFactory中创建MapperProxy,然后创建出代理对象
return mapperProxyFactory.newInstance(sqlSession);//底层就是java动态代理
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
②MapperProxyFactory
public class MapperProxyFactory<T> {
// 该工厂对应的是哪个接口
private final Class<T> mapperInterface;
// Mapper.Statement执行缓存。缓存这MapperMethod
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
// 构造器
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public T newInstance(SqlSession sqlSession) {
// 根据sqlSession+接口 获取到MapperProxy,这个就是invocationHandler // 传入methodCache,免得每次调用还得重新代理
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession,
mapperInterface,
methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//动态代理我们写的dao接口,获取动态代理实例
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[] {
mapperInterface },
mapperProxy);
// 从这里也可以看出mapperProxy肯定实现了InvocationHandler
}
从上面可以知道,mapper最后就创建了个动态代理,其中的invocationHandler参数传入的是mapperProxy,
③MapperProxy
动态代理其实执行的是super.h.invoke()
方法
大家都在说执行invoke方法,那么哪里可以找到呢?其实肉眼是找不到的,因为JVM启动后把代理类生成在了内存中,代理类内部执行对应的方法都是间接调用invoke方法
也就是说,获取到的UserMapper实际上是代理对象MapperProxy,所以我们执行查询语句的时候实际上执行的是MapperProxy的invoke方法:
每个MapperProxy对应一个dao接口, 那么咱们在使用的时候,MapperProxy是怎么做的呢
MapperProxy
- 因为Mapper接口不能直接被实例化,Mybatis利用JDK动态代理,创建MapperProxy间接实例化Mapper对象。
- MapperProxy还可以缓存MapperMethod对象
// mybatis里执行sql的自定义InvocationHandler
public class MapperProxy<T> implements InvocationHandler, Serializable {
// 注意有几个属性
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
// 构造函数
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
// invocationHandler的invoke()方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**如果调用的是Object原生的方法,则直接放行*/
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// 是自定义方法
// ①通过method从缓存中获取mapperMethod,获取不到就创建
final MapperMethod mapperMethod = cachedMapperMethod(method);
// ②执行方法execute
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
// 尝试中缓存中获取
MapperMethod mapperMethod = methodCache.get(method);
// 缓存中没空,创建
if (mapperMethod == null) {
// 从缓存集合中未获取到Statment, 初始化当前Statement为MapperMethod
// 并添加初始化后的MapperMethod到缓存集合
// mapperInterface, sqlSession, methodCache在构造Mapper代理对象时已经通过MapperProxy初始化传递
// MapperMethod初始化会从Configuration配置中初始化SqlCommand, MethodSignature信息
// 表示当前Statment的SQL类型和状态等信息
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
// 添加当前Mapper.Statement到缓存中
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
// 可以看到,先根据方法签名,从方法缓存中获取方法,如果为空,则生成一个MapperMethod放入缓存并返回。
}
所以最终执行查询的是MapperMethod的execute方法:
④MapperMethod
- 负责解析Mapper接口的方法,并封装成MapperMethod对象
- 将Sql命令的执行路由到恰当的SqlSesison方法上
public class MapperMethod {
// 2个属性
// sql语句,能得到string
private final SqlCommand command;// 保存了Sql命令的类型和键id
private final MethodSignature method;// 保存了Mapper接口方法的解析信息
// 构造器
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);// 内部类,根据method的信息拿到类、方法 // 找到ms对象
/*
name = declaringInterface.getName() + "." + method.getName();//接口+方法拿到ms
MappedStatement ms = configuration.getMappedStatement(name);
type = ms.getSqlCommandType();
*/
// 包装method,MethodSignature里有返回类型是void还是Many/Cursor
this.method = new MethodSignature(config, mapperInterface, method);
}
MapperMethod.execute
// MapperMethod.java
// 根据解析结果,路由到恰当的SqlSession方法上
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// command.getType()拿到sql是增删改查哪一种
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
// select查询语句
} else if (SqlCommandType.SELECT == command.getType()) {
// 查看该sql的返回值类型。注意这里只是预先设置好返回值怎么处理,并没有执行呢*/
// 当返回值为空
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);// 底层也是selectOne方法
result = null;
/** 当返回many(list)的时候*/
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
/**当返回值类型为Map时*/
} else if (method.returnsMap()) {
// 内部也是调用convertArgsToSqlCommandParam和selectMap
result = executeForMap(sqlSession, args);
} else {
// 就返回了一个对象
// 底层也是selectOne方法
Object param = method.convertArgsToSqlCommandParam(args);
// 调用SqlSession单条数据查询执行方法
// SqlSession在初始化SqlSession分析中已经指明, 此处应为DefaultSqlSession
// 单数据查询, 且查询结果为 VO
result = sqlSession.selectOne(command.getName(), param);
}
} else {
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;
}
executeForMany会解析是否有@Param注解和是否有分页参数
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 如果是对个参数的话,返回值是param是一个map
Object param = method.convertArgsToSqlCommandParam(args);
// 看参数中有没有rowBound
if (method.hasRowBounds()) {
// 如果有分页参数的话,就根据arg数组拿到分页参数
RowBounds rowBounds = method.extractRowBounds(args);// 抽离出rowBounds是第一个参数,并放回return (hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null);
// 传入参数:语句、实参、rowBounds
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
【@Param
】:convertArgsToSqlCommandParam
这个方法的作用就是解析@Param
注解
@Param
注解的用法:一个参数不用,多个参数必须用注解指定,否则默认的是param1/arg0
// 转化参数
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
/**参数没有标注@Param注解,并且参数个数为一个*/
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next()];
/**否则执行这个分支*/
// 这里参数解析如果判断参数一个只有一个(一个单一参数或者是一个集合参数),并且没有标注`@Param`注解,那么直接返回这个参数的值,否则会被封装为一个Map,然后再返回。
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
解释参数问题:
例1:
/**接口为*/ User selectByNameSex(String name, int sex); /**我们用如下格式调用*/ userMapper.selectByNameSex("张三",0); /**参数会被封装为如下格式:*/ 0 ---> 张三 1 ---> 0 param1 ---> 张三 param2 ---> 0
例2:
/**接口为*/ User selectByNameSex(@Param("name") String name, int sex); /**我们用如下格式调用*/ userMapper.selectByNameSex("张三",0); /**参数会被封装为如下格式:*/ name ---> 张三 1 ---> 0 param1 ---> 张三 param2 ---> 0
为什么arg和param是重复的内容还要重复写:这个jdk有关,这个机制让 jdk8之后不写@Param也能找到对应参数
四、开始查询
(1)使用mapper接口对象在spring中的Bean是mapper类的代理类 MapperProxy实例,调用对象查询方法selectABC。
(2)实际上会调用MapperProxy的invoke反射的方法。
(3)在invoke方法中会最终调用
DefaultSqlSession类中的select方法,此方法中configuration对象根据selectABC方法的唯一id,获得MappedStatement元数据信息。并用BaseExecutor(SqlSession中已经注入executor)执行器执执行query并传入 MappedStatement。
(4)接着通过configuration.newStatementHandler会生成一个StatementHandler对象 ,并在构造器中调用生成parameterHandler和resultSetHandler,同时存储在StatementHandler对象中。
(5)然后用handler生成一个java.sql.statement
(6)最后试用statemenet执行sql。
③selectOne
与spring整合的mapper的底层也是selectOne等方法,我们先了解selectOne
通过configuration.mappedStatement取得之前解析xml时准备好的sql
缓存:https://tech.meituan.com/2018/01/19/mybatis-cache.html
public <T> T selectOne(String statement) {
return this.<T>selectOne(statement, null);//无参
}
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
// 底层都是selectList()
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
// 如果只有一个元素,无需返回list,直接返回该元素
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
public <E> List<E> selectList(String statement) {
return this.<E>selectList(statement, null);// 无参
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.<E>selectList(statement,
parameter,
RowBounds.DEFAULT);// static :new RowBounds();
}
// select底层都是调用的这个
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 从config中获取我们之前解析xml时(还有注解的)准备好的mappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行器去执行
// Executor初始化阶段已经被包装过
// 如果有二级缓存,那么就被包装为CachingExecutor
// 如果只开启了一级缓存的话,首先会进入BaseExecutor的query方法,实现类可能就是SimleExecutor
List<E> result = executor.<E>query(ms, // 解析xml阶段封装好的mappedStatement
wrapCollection(parameter), // 包装参数成map
rowBounds, // 分页参数
Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//在执行query时,传入了一个wrapCollection
private Object wrapCollection(final Object object) {
// 判断传入的参数是什么类型的,list / array
if (object instanceof List) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("list", object);
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
④CachingExecutor.query之CacheKey
接下来执行query()
方法,如果开启了二级缓存,执行的就是CachingExecutor.query(),他只是看看缓存中有没有与sql一样的查询结果,有的话直接返回,没有的话再由普通的执行器会查询。二级缓存的map在MappedStatement中维护着,也就是我们通常说的二级缓存是namespace级别的缓存
// CachingExecutor:执行第一层 query() 方法
public <E> List<E> query(MappedStatement ms,
Object parameter,// 包装的参数,实参
RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException {
// 获取MappedStatement的boundSql,得到sql对象, 包含Sql, 参数, 返回结果等信息
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存数据 // 将MappedStatement的Id、SQL的offset、SQL的limit、SQL本身以及SQL中的参数传入了CacheKey这个类,最终构成CacheKey。
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 继续进行数据查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
/*
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
//后面是update了sql中带的参数
cacheKey.update(value);
*/
// 如果只开启了一级缓存的话,首先会进入BaseExecutor的query方法。
在上述代码中,会先根据传入的参数生成CacheKey,进入该方法查看CacheKey是如何生成的,代码如下所示:
// new一个Cache,然后把起始偏移、limit等设置进行
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
//后面是update了sql中带的参数
cacheKey.update(value);
在上述的代码中,将MappedStatement
的Id、SQL的offset、SQL的limit、SQL本身以及SQL中的参数传入了CacheKey这个类,最终构成CacheKey。以下是这个类的内部结构:
private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;
private int multiplier;
private int hashcode;
private long checksum;
private int count;
private List<Object> updateList;
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYER;
this.count = 0;
this.updateList = new ArrayList<Object>();
}
首先是成员变量和构造函数,有一个初始的hachcode
和乘数,同时维护了一个内部的updatelist
。在CacheKey
的update
方法中,会进行一个hashcode
和checksum
的计算,同时把传入的参数添加进updatelist
中。如下代码所示:
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);
}
同时重写了CacheKey
的equals
方法,代码如下所示:
@Override
public boolean equals(Object object) {
.............;
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;
}
除去hashcode、checksum和count的比较外,只要updatelist中的元素一一对应相等,那么就可以认为是CacheKey相等。只要两条SQL的下列五个值相同,即可以认为是相同的SQL。
Statement Id + Offset + Limmit + Sql + Params
BaseExecutor的query方法继续往下走,代码如下所示:
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 这个主要是处理存储过程用的。
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
如果查不到的话,就从数据库查,在queryFromDatabase
中,会对localcache
进行写入。
在query
方法执行的最后,会判断一级缓存级别是否是STATEMENT
级别,如果是的话,就清空缓存,这也就是STATEMENT
级别的一级缓存无法共享localCache
的原因。代码如下所示:
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
在源码分析的最后,我们确认一下,如果是insert/delete/update
方法,缓存就会刷新的原因。
SqlSession
的insert
方法和delete
方法,都会统一走update
的流程,代码如下所示:
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int delete(String statement) {
return update(statement, null);
}
update
方法也是委托给了Executor
执行。BaseExecutor
的执行方法如下所示:
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空缓存
clearLocalCache();
return doUpdate(ms, parameter);
}
每次执行update
前都会清空localCache
。
至此,一级缓存的工作流程讲解以及源码分析完毕。
④CachingExecutor.query()
// CachingExecutor:执行第二层 query() 方法
public <E> List<E> query(MappedStatement ms,
Object parameterObject,
RowBounds rowBounds,
ResultHandler resultHandler,
CacheKey key,
BoundSql boundSql) throws SQLException {
// useCache属性 // select的二级缓存 // <setting name="cacheEnabled" value="true"/> // userCache是用来设置是否禁用二级缓存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);// 是否清空缓存?如果是update的话
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
if (!dirty) {
cache.getReadWriteLock().readLock().lock();
try {
@SuppressWarnings("unchecked")
List<E> cachedList = (List<E>) cache.getObject(key);
if (cachedList != null) return cachedList;
} finally {
cache.getReadWriteLock().readLock().unlock();
}
}
// 执行器的delegate
List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
return list;
}
}
// 最常用的是派给simple
// 缓存中没有数据, 装饰者模式, 经过BaseExecutor转发到SimpleExecutor继续进行数据查询
return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
【sql注入】getBound
解析完xml后:
- 静态sql:#变成?
- 动态sql:#和$都不会变
执行sql时:执行到CachingExecutor.query的ms.GetBoundSql时
- 对于静态sql:无变化
- 对于动态sql:#变成?,而$变成具体语句了
?会用指定的XXXTypeHandler
处理
总结:
$
:在执行sql语句之前就要赋值- #:执行sql语句时用jdbc的set
jdbc的set会将?变成"",比如我们有如下java语句
PreparedStatement preparedStatement = connection.PreparedStatement("select * from stu where name=? and passwd=? "); preparedStatement.setString(1,"zs"); preparedStatement.setString(2,"123"); // 解析完会变成 "select * from stu where name='zs' and passwd='123' "
所以比如我们在select自己写了个?:
PreparedStatement preparedStatement = connection.PreparedStatement("select ? from stu where name=? and passwd=? "); preparedStatement.setString(1,"id"); preparedStatement.setString(2,"zs"); preparedStatement.setString(3,"123"); // 解析完会变成 select "id"。。。
显然是查不到想要的结果的,不能加""
二级缓存介绍
一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
④BaseExecutor.query
BaseExecutor:装饰者模式,转发到 BaseExecutor 继续执行 query() 方法
// simple // BaseExecutor.java
@SuppressWarnings("unchecked")
public <E> List<E> query(MappedStatement ms,
Object parameter,
RowBounds rowBounds,
ResultHandler resultHandler,
CacheKey key,
BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// 清空数据缓存
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 本地缓存中获取 // 一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 缓存中数据不为空, 参数处理后直接返回缓存数据
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 缓存中没获取到,去数据库查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
// 延迟加载策略
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
queryFromDatabase
缓存未加载到,从数据库中获取有效数据
// BaseExecutor.java
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 执行器调用doQuery // Query是父类的方法,doQuery是子类的方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 添加到一级缓存
localCache.putObject(key, list);
// 在query方法执行的最后,会判断一级缓存级别是否是STATEMENT级别,如果是的话,就清空缓存,这也就是STATEMENT级别的一级缓存无法共享localCache的原因。
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
只有第一次真正查询了数据库,后续的查询使用了一级缓存。
在修改操作后执行的相同查询,查询了数据库,一级缓存失效。
一级缓存只在数据库会话内部共享。
⑤SimpleExecutor.doQuery()
【创建stmt处理器,处理stmt,执行,获取结果】
执行器有多种,StatementHandler也有多种
接着调用doQuery方法:
// SimpleExecutor.java
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 获取配置信息
Configuration configuration = ms.getConfiguration();
// 1
/**这里出现了Mybatis四大对象中的StatementHandler */
// 获取StatementHandler, 生成已经被Plugin代理的执行器 // RoutingStatementHandler
// 此时他顺便创建了四大对象的newStatementHandler、newResultSetHandler、newResultSetHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 2
// 获取数据库连接, 并注册相关事务
// jdbc的setString在这里面发生
stmt = prepareStatement(handler, ms.getStatementLog());
// 3 执行器里的.doQuery里调用StatementHandler.query
// StatementHandler数据执行
return handler.<E>query(stmt, resultHandler);//StatementHandler先去执行,然后处理结果
} finally {
closeStatement(stmt);
}
}
可见Executor委托给StatementHandler执行查询,在此之前有一个预编译的过程(prepareStatement方法),StatementHandler接口的实现类:
CallableStatementHandler
,PreparedStatementHandler
,SimpleStatementHandler
对应JDBC中的CallableStatement
,PreparedStatement
和Statement
,分别的执行方法:
1 newStatementHandler【创建stmt处理器】
初始化 StatementHandler 处理器
// Configuration.java
public StatementHandler newStatementHandler(Executor executor,
MappedStatement mappedStatement,
Object parameterObject,
RowBounds rowBounds,
ResultHandler resultHandler,
BoundSql boundSql) {
// RoutingStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor,
mappedStatement,
parameterObject,
rowBounds,
resultHandler,
boundSql);
// 循环插件生成StatementHandler的代理对象, 此处代理为循环代理
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
1.1 SimpleStatementHandler
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
// 构造器
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根据getStatementType()进行内部初始化, 此次初始化为PreparedStatementHandler
switch (ms.getStatementType()) {
case STATEMENT:
// 在这里默认调用了父类BaseStatementHandler的构造函数
// 他里面有newParameterHandler、newResultSetHandler,即mybatis四大对象new了3个
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
public class SimpleStatementHandler extends BaseStatementHandler {
public SimpleStatementHandler(Executor executor,
MappedStatement mappedStatement,
Object parameter,
RowBounds rowBounds,
ResultHandler resultHandler,
BoundSql boundSql) {
// 调用BaseStatementHandler
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
在创建StatementHandler的同时,应用插件功能,同时创建了Mybatis四大对象中的另外两个对象:
protected BaseStatementHandler(Executor executor,
MappedStatement mappedStatement,
Object parameterObject,
RowBounds rowBounds,
ResultHandler resultHandler,
BoundSql boundSql) {
……;
……;
……;
// Mybatis四大对象中的ParameterHandler,也会代理
this.parameterHandler = configuration.newParameterHandler(mappedStatement,
parameterObject,
boundSql);
/**Mybatis四大对象中的ResultSetHandler*/
this.resultSetHandler = configuration.newResultSetHandler(executor,
mappedStatement,
rowBounds,
parameterHandler,
resultHandler,
boundSql);
}
1.2 newParameterHandler
同时在创建的时候,也运用到了插件增强功能:
// =====newParameterHandler=====
public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
Object parameterObject,
BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang()
.createParameterHandler(mappedStatement, parameterObject, boundSql);
// 代理 pluginAll
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
1.2 newResultSetHandler【创建结果集代理】
// =====newResultSetHandler=====
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement,
RowBounds rowBounds,
ParameterHandler parameterHandler,
ResultHandler resultHandler, // resultHandler
boundSql) {
ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ?
new NestedResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql,rowBounds) :
new FastResultSetHandler(executor,
mappedStatement,
parameterHandler,
resultHandler,
boundSql,
rowBounds);
// 代理 pluginAll
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
//==============
public FastResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql, RowBounds rowBounds) {
this.executor = executor;
this.configuration = mappedStatement.getConfiguration();
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.parameterHandler = parameterHandler;
this.boundSql = boundSql;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();//
this.resultHandler = resultHandler;//
this.proxyFactory = configuration.getProxyFactory();//
this.resultExtractor = new ResultExtractor(configuration, objectFactory);
}
2 【jdbc.set】prepareStatement
预编译sql产生ps
// SimpleExecutor.java
private Statement prepareStatement(StatementHandler handler,
Log statementLog)
throws SQLException {
Statement stmt;
// 初始化数据库连接 // 通过JdbcTransaction获取连接
Connection connection = getConnection(statementLog);
// StatementHandler初始化PreparedStatement预编译
stmt = handler.prepare(connection, transaction.getTimeout());//handler是RoutingStatementHandler,会装饰者模式转到别的PreparedStatement
// 封装Statement对象到StatementHandler处理器中
// jdbc的setString等
handler.parameterize(stmt);
return stmt;
}
2.1 getConnection
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
protected void openConnection() throws SQLException {
// dataSource已经封装好了数据库连接信息, 获取数据库连接
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommmit);
}
2.2 prepare
// RoutingStatementHandler.java
private final StatementHandler delegate;
public Statement prepare(Connection connection) throws SQLException {
return delegate.prepare(connection);
}
// BaseStatementHandler.java
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 从子类实现中获取最终的Statement
// 此时子类实现为PreparedStatement
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
2.3 stmtHandler.parameterize(stmt);
// RoutingStatementHandler.java
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
// PreparedStatementHandler.java
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
// DefaultParameterHandler.java
public void setParameters(PreparedStatement ps) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 拿到参数映射关系,执行到这里只有?没有其他内容
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
// issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
value = metaObject == null ? null : metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
// 用typeHandler设置参数,i+1轮询,for是0开始,jdbc是1开始 // 底层是jdbc.set
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
// BaseTypeHandler.java
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("给#i设置null是非法的,他的类型是jdbcType " );
}
} else {
// 参数非空时
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
// UnknownTypeHandler.java
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
// 根据参数和jdbc类型找到TypeHandler
TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
handler.setParameter(ps, i, parameter, jdbcType);
}
// StringTypeHandler.java
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
// jdbc的set方法
ps.setString(i, parameter);
}
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
// 通过数据库连接, 获取预编译平台
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
9,StatementHandler 处理器,进行最终结果集获取
* 此时的 StatementHandler 对象是被拦截器插件代理后的代理对象,会通过动态代理方式进行执行,生成代理方法如下
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 存在有效插件
if (interfaces.length > 0) {
// 对target即executor进行代理
// 外部循环调用, 则此时executor对象可能已经是代理对象
// 允许对代理对象再次进行代理
// 最终执行时, 装箱代理, 拆箱调用, 类似装饰者模式一层层进行处理
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
* StatementHandler.query()方法调用,会先执行 Plugin.invoke()方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
2.4 typeHandler.setParameter(ps
3 执行handler.query
再回顾一下是怎么从执行器的来到stmtHandler.的
// SimpleExecutor.java
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 获取配置信息
Configuration configuration = ms.getConfiguration();
// 1
// 获取StatementHandler, 生成已经被Plugin代理的执行器 // RoutingStatementHandler
// 此时他顺便创建了四大对象的newStatementHandler、newResultSetHandler、newResultSetHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 2
// 获取数据库连接, 并注册相关事务
// jdbc的setString在这里面发生
stmt = prepareStatement(handler, ms.getStatementLog());
// 3 执行器里的.doQuery里调用StatementHandler.query
// StatementHandler数据执行
return handler.<E>query(stmt, resultHandler);//StatementHandler先去执行,然后处理结果
} finally {
closeStatement(stmt);
}
}
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
// stmtHandler.query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return this.delegate.query(statement, resultHandler);
}
delegateStmtHandler.query
public class SimpleStatementHandler extends BaseStatementHandler {
public <E> List<E> query(Statement statement,
ResultHandler resultHandler) throws SQLException {
String sql = this.boundSql.getSql();
statement.execute(sql);
// resultSetHandler
return this.resultSetHandler.handleResultSets(statement);
}
public class PreparedStatementHandler extends BaseStatementHandler {
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 转换Statement为JDBC原生的PreparedStatement
PreparedStatement ps = (PreparedStatement) statement;
// 通过JDBC底层执行, 并将执行结果封装到PreparedStatement中
ps.execute();
// resultSetHandler:结果集处理器, 对查询结果进行ORM映射
return resultSetHandler.<E> handleResultSets(ps);
}
4 获取结果
⑥ RoutingStatementHandler.query()
//FastResultSetHandler
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<Object>();
final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
int resultSetCount = 0;
ResultSet rs = stmt.getResultSet();
while (rs == null) {
// move forward to get the first resultset in case the driver
// doesn't return the resultset as the first result (HSQLDB 2.1)
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else {
if (stmt.getUpdateCount() == -1) {
// no more results. Must be no resultset
break;
}
}
}
接下来就是执行正常的JDBC查询功能了,参数设置由ParameterHandler操作,结果集处理由ResultSetHandler处理。至此,整个查询流程结束。
流程
整个查询阶段的时序图可以用如下图表示:
整个查询流程,可以总结如下图:
五、插件与拦截器
插件源码级分析写到了另一篇中:https://blog.csdn.net/hancoder/article/details/110914534
插件的构建:
谈原理首先要知道StatementHandler,ParameterHandler,ResultHandler
都是代理,他们是Configuration创建,在创建过程中会调用interceptorChain.pluginAll()
方法,为四大组件组装插件(再底层是通过Plugin.wrap(target,XX, new Plugin( interceptor))来来创建的)。
插件链是何时构建的:
在执行SqlSession的query或者update方法时,SqlSession会通过Configuration创建Executor代理,在创建过程中就调用interceptor的pluginAll方法组装插件。然后executor在调用doQuery()方法的时候,也会调用Configuration的newStatementHandler方法创建StatemenHandler(和上面描述的一样,这个handler就是个代理,也是通过interceptorChain的pluginAll方法构建插件)
插件如何执行:
以statementhandler的prepare方法的插件为例,正如前面所说,statementHandler是一个proxy,执行他的prepare方法,将调用invokeHandler的invoke方法,而invokeHandler就是Plugin.wrap(target, xxx, new Plugin(interceptor))中的第三个参数,所以很自然invokeHanlder的invoke的方法最终就会调用interceptor对象的intercept方法。
@Intercepts({
@Signature(type = ResultSetHandler.class,
method = "handleResultSets",
args = {
Statement.class }) })// 方法也可能被重载了多个,所以要告诉参数,指定是哪个方法
public class PageResultSetInterceptor implements Interceptor {
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
@Override
public Object intercept(Invocation invocation) throws Throwable {
FastResultSetHandler resultSetHandler = (FastResultSetHandler) invocation.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(resultSetHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
// 获取rowBounds参数
RowBounds rowBounds = (RowBounds) metaStatementHandler.getValue("rowBounds");
Object result = invocation.proceed();
if (rowBounds instanceof Page) {
metaStatementHandler.setValue("rowBounds.result", result);
}
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
@Intercepts({
@Signature(type = Executor.class,
method = "query",
args = {
MappedStatement.class,Object.class,RowBounds.class,ResultHanlder.class }) })
public class MyPageInterceptor implements Interceptor{
public Object intercept(Invocation invocation) throws Throwable{
//逻辑分页改为物理分页
// 修改sql,添加limit
Object[] args = invocation.getArgs();//query方法的参数
// 获取MappedStatement
MappedStatement ms = (MappedStatement) args[0];
// 获取里面的boundSql,他是对sql和参数的封装
BoundSql boundSql = ms.getBoundSql(args[1]).;// 需要传参数
// 拿到原来的sql
String sql = boundSql.getSql();
// 拿到rowBounds
RowBounds rowBounds = (RowBounds)args[2]
sql =sql + " limit "+rowBounds.getOffset() + ","+rowBounds.getLimit();
new BoundSql(ms.getConfiguration,sql,boundSql.getParameterMapping(),parameterObject);
// 被代理的对象
Executor exec = invocation.getTarget();
return exec.query();
}
}
六、缓存问题
在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。具体执行过程如下图所示。
每个SqlSession中持有了Executor,每个Executor中有一个LocalCache(在Executor的父类BaseExecutor中)。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement
,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache
,最后返回结果给用户。具体实现类的类关系图如下图所示。
一级缓存配置
我们来看看如何使用MyBatis一级缓存。开发者只需在MyBatis的配置文件中,添加如下语句,就可以使用一级缓存。共有两个选项,SESSION
或者STATEMENT
,默认是SESSION
级别,即在一个MyBatis会话中执行的所有语句,都会共享这一个缓存。一种是STATEMENT
级别,可以理解为缓存只对当前执行的这一个Statement
有效。
<setting name="localCacheScope" value="SESSION"/>
一级缓存PerpetualCache
在一级缓存的介绍中提到对Local Cache
的查询和写入是在Executor
内部完成的。在阅读BaseExecutor
的代码后发现Local Cache
是BaseExecutor
内部的一个成员变量,如下代码所示。
public abstract class BaseExecutor implements Executor {
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 本地缓存
protected PerpetualCache localCache;
Cache: MyBatis中的Cache接口,提供了和缓存相关的最基本的操作,如下图所示:
有若干个实现类,使用装饰器模式互相组装,提供丰富的操控缓存的能力,部分实现类如下图所示:
BaseExecutor
成员变量之一的PerpetualCache
,是对Cache接口最基本的实现,其实现非常简单,内部持有HashMap,对一级缓存的操作实则是对HashMap的操作。如下代码所示:
public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
在阅读相关核心类代码后,从源代码层面对一级缓存工作中涉及到的相关代码,出于篇幅的考虑,对源码做适当删减,读者朋友可以结合本文,后续进行更详细的学习。
二级缓存CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);//拿到的是带?的sql
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();//二级缓存
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
if (!dirty) {
cache.getReadWriteLock().readLock().lock();
try {
@SuppressWarnings("unchecked")
List<E> cachedList = (List<E>) cache.getObject(key);
if (cachedList != null) return cachedList;
} finally {
cache.getReadWriteLock().readLock().unlock();
}
}
List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
return list;
}
}
return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);//BaseExecutor
}
MyBatis核心构件
名称 | 作用 |
---|---|
SqlSession | 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能 |
Executor | MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护 |
StatementHandler | 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合 |
ParameterHandler | 负责对用户传递的实参参数转换成JDBC Statement 所需要的参数 |
ResultSetHandler | 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合 |
TypeHandler | 负责java数据类型和jdbc数据类型之间的映射和转换 |
MappedStatement | MappedStatement维护了一条select |
SqlSource | 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回 |
BoundSql | 表示动态生成的SQL语句以及相应的参数信息 |
Configuration | MyBatis所有的配置信息都维持在Configuration对象之中 |
七、与spring的整合
我们在mybatis-spring官方看看整合有几种方式:http://mybatis.org/spring/zh/getting-started.html
我们整合时需要注册的bean,
-
SqlSessionFactoryBean:管理数据源,扫描xml文件(我们最终注入到spring中的mapper都是以工厂bean方式存在的)
-
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 加载数据源 --> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/> </bean>
-
-
接口的动态代理(多选一)
- MapperScannerConfigurer:负责扫描接口
- MapperFactoryBean
- @MapperScan
方式1 XMLbean
一个一个注入
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
方式2 @Bean
@Configuration
public class MyBatisConfig {
@Bean // 这种方式还是一个一个注入
public UserMapper userMapper() throws Exception {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
return sqlSessionTemplate.getMapper(UserMapper.class);
}
}
@Configuration
public class MyBatisConfig {
@Bean // 与上面一样
public MapperFactoryBean<UserMapper> userMapper() throws Exception {
MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
factoryBean.setSqlSessionFactory(sqlSessionFactory());
return factoryBean;
}
}
方式3 MapperScannerConfigurer
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定扫描的包,如果存在多个包使用(逗号,)分割 -->
<property name="basePackage" value="com.test.bean"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
方式4 @MapperScan
方式5 <mybatis:scan>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
<mybatis:scan base-package="org.mybatis.spring.sample.mapper" />
</beans>
1 MapperScannerConfigurer是个工厂后置处理器
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, //该接口继承了BeanFactoryPostProcessor
InitializingBean,
ApplicationContextAware,
BeanNameAware {
看过我spring解读的都知道工厂后置处理器太关键了,他几乎在所有bean实例化前就实例化好了。注解看他实现的方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();// 把xml中${XXX}中的XXX替换成属性文件中的相应的值
}
// 看对registry执行了上面操作
// new ClassPathMapperScanner的时候把工厂传进去了 // 然后调用它的scan()方法扫描出所有的代理bd定义
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);//传入了工厂的bd注册器
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);//
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();//根据配置的属性生成对应的过滤器,然后这些过滤器在扫描的时候会起作用。
// .scan()进行扫描工作 //
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, //
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
ClassPathMapperScanner
@ComponentScan的底层就是ClassPathMapperScanner
这个类会扫描一个包,将下面的注解都new bd放到spring容器中。但是该类并不能扫描接口,所以我们需要继承该类重写方法isCandidateComponent()
。
scan()
public class ClassPathBeanDefinitionScanner
extends ClassPathScanningCandidateComponentProvider {
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);// 会去执行子类重写的方法
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
doScan()
doScan方法得到bds集合
public class ClassPathMapperScanner
extends ClassPathBeanDefinitionScanner {
// 得到bds
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类的doScan()
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
//如果为空,日志warn扫描包为空
logger.warn("在该包里找不到该mapper");
} else {
// 遍历
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// 将接口传进去
//
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());//先把原始的类型取出来塞到BD的属性里 dao.UserMapper
// 设置成mapper工厂的类,他可以生产动态代理
definition.setBeanClass(MapperFactoryBean.class);//然后重新给BD赋予class,这样这个bean的类型就是 MapperFactoryBean
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
return beanDefinitions;
}
@Override // 指定要扫描出什么样的类。
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return (beanDefinition.getMetadata().isInterface() && // 得是接口
beanDefinition.getMetadata().isIndependent());
}
@Override
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
if (super.checkCandidate(beanName, beanDefinition)) {
return true;
} else {
logger.warn("Skipping MapperFactoryBean with name '" + beanName
+ "' and '" + beanDefinition.getBeanClassName() + "' mapperInterface"
+ ". Bean already defined with the same name!");
return false;
}
}
MapperFactoryBean
public class MapperFactoryBean<T>
extends SqlSessionDaoSupport
implements FactoryBean<T> {
private Class<T> mapperInterface;
public void setMapperInterface(Class<T> mapperInterface) {
//把接口通过构造传入
this.mapperInterface = mapperInterface;
}
public T getObject() throws Exception {
// 接下来就是mybatis的内容了,他内部是java动态代理
return getSqlSession().getMapper(this.mapperInterface);
}
ClassPathMapperScanner这个扫描器的主要的作用有以下几个:
-
第一扫描basePackage包下面所有的class类
-
第二将所有的class类(指定范围的)封装成为spring的ScannedGenericBeanDefinition sbd对象
-
第三过滤sbd对象,只接受接口类,从下面的代码中可以看出。
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
-
第四完成sbd对象属性的设置,比如设置sqlSessionFactory、BeanClass等,这个sqlSessionFactory是本文接下来要解析的SqlSessionFactoryBean
-
sbd.getPropertyValues().add("mapperInterface", definition.getBeanClassName()); sbd.setBeanClass(MapperFactoryBean.class); sbd.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
-
-
第五将过滤出来的sbd对象通过这个BeanDefinitionRegistry registry注册器注册到DefaultListableBeanFactory中,这个registry就是方法postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)中的参数。
以上就是实例化MapperScannerConfigurer类的主要工作,总结起来就是扫描basePackage包下所有的mapper接口类,并将mapper接口类封装成为BeanDefinition对象,注册到spring的BeanFactory容器中。以下时序图不代表实际过程。
mapper接口注册之后,在什么地方实例化和使用呢?后面在分析。
2 @MapperScan
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
// 又是这个类
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
if (resourceLoader != null) {
// this check is needed in Spring 3.1
scanner.setResourceLoader(resourceLoader);
}
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
// 注册bean定义
scanner.doScan(StringUtils.toStringArray(basePackages));
}
ClassPathMapperScanner
这个类上面分析过了
3 SqlSessionFactoryBean
接着看看spring和mybatis整合的另外一个标签。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 加载数据源 -->
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/>
</bean>
首先看出他是个工厂bean,其从他是生成sqlSession的
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, //工厂bean,关注getObject方法
InitializingBean, // 那么当bean初始化的时候,spring就会调用该接口的实现类的afterPropertiesSet方法,去实现当spring初始化该Bean 的时候所需要的逻辑。
ApplicationListener<ApplicationEvent> {
//如果注册了该监听的话,那么就可以监听到Spring的一些事件,然后做相应的处理
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();// 去看看他的作用
}
return this.sqlSessionFactory;//返回属性 SqlSessionFactory
}
// 初始化工厂bean的时候会调用此方法
public void afterPropertiesSet() throws Exception {
// sqlSessionFactory做定制的初始化
// 确保非空之后直接调用buildSqlSessionFactory();
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = buildSqlSessionFactory();
}
buildSqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
// mybatis的全局config
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (logger.isDebugEnabled()) {
logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (logger.isDebugEnabled()) {
logger.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (logger.isDebugEnabled()) {
logger.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (logger.isDebugEnabled()) {
logger.debug("Registered plugin: '" + plugin + "'");
}
}
}
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (logger.isDebugEnabled()) {
logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (logger.isDebugEnabled()) {
logger.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (logger.isDebugEnabled()) {
logger.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
// 持有事务和数据源等信息
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);//设置到config中
if (this.databaseIdProvider != null) {
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
// 遍历mapper,执行xmlMapperBuilder.parse(),行mapper的代理对象
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
// 执行parse()
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (logger.isDebugEnabled()) {
logger.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
// 将config中保存好的mapper代理对象肯定要放到ac中
return this.sqlSessionFactoryBuilder.build(configuration);
调用完上面的afterPropertiesSet方法之后,第二个被调用的就是onApplicationEvent方法,这个方法的调用时机是,spring容器初始化完成之后,该方法是接口ApplicationListener中的方法。
public void onApplicationEvent(ApplicationEvent event) {
if (this.failFast && event instanceof ContextRefreshedEvent) {
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
我们原来spring注册bean的时候,方式有
<bean>
、componentScan、@Service我们没有办法参与我们mybatis创建完mapper后,mybatis能自己拿到,但怎么让spring拿到?
我们能让spring拿到的方式有
- @Bean,我们可以在函数里面getMapper返回,但是这不是mybatis选择的方式
- 或者是创建完后使用ac.getBeanFactory().registerSingleton(beanname,User.class);
- factoryBean:跟@Bean一样得挨个注册,不实际
- mybatis选择的方式是beanFactory的API
- 我们弄完beanFactory后,不能直接@Service,那样spring就直接接管了
- 利用spring提供的扩展
- 存到了MutablePropertyValues
在前面介绍Mybatis初始化过程时提到,SqlSessionFactoryBuilder会通过XMLConfigBuilder等对象读取mybatis-config.xml配置文件以及映射配置信息,得到Configuration对象,然后创建SqlSessionFactory对象。而在spring集成式,mybatis中的SqlSessionFactory对象则是由SqlSessionFactoryBean创建的。spring.xml中配置了SqlSessionFactoryBean,其中指定了数据源对象,mybatis-config.xml配置文件的位置等信息。SqlSessionFactoryBean定义了很多mybatis配置相关的字段。
图4引展示的所有字段对应了开发人员可以在applicationContext.xmI配置文件中为SqlSessionFactoryBean配置的配置项,同时,也都可以在Configuration对象中找到相应的字段,其含义就不再重复描述了。
这里重点关注SqlSessionFactoryBean是如何创建SqlSessionFactory对象的,该功能是在SqlSessionFactoryBean.buildSqlSessionFactory()方法中实现的,其中涉及使用XMLConfigBuilder创建Configuration对象、对Configuration对象进行配置、使用XMLMapperBui1der解析映射配置过文件以及Mapper接口等一些列操作,这些操作的原理都在前面介绍过了,方法的具体实现如下:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { private static final Log logger = LogFactory.getLog(SqlSessionFactoryBean.class); private Resource configLocation; private Resource[] mapperLocations; private DataSource dataSource; private TransactionFactory transactionFactory; private Properties configurationProperties; private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); private SqlSessionFactory sqlSessionFactory; private String environment = SqlSessionFactoryBean.class.getSimpleName(); // EnvironmentAware requires spring 3.1 private boolean failFast; private Interceptor[] plugins; private TypeHandler<?>[] typeHandlers; private String typeHandlersPackage; private Class<?>[] typeAliases; private String typeAliasesPackage; private Class<?> typeAliasesSuperType; private DatabaseIdProvider databaseIdProvider; // issue #19. No default provider. private ObjectFactory objectFactory; private ObjectWrapperFactory objectWrapperFactory; protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; // //如果Configuration对象存在·则使用指定的configuration对其进行匹配 if (this.configLocation != null) { // 创建XMLConfigBui1der对象,读取指定的配置文件 xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (logger.isDebugEnabled()) { logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); configuration.setVariables(this.configurationProperties); } // 配置objectFactory if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } // applicationcontext.xml中的配置,设置objectWrapperFactory if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } // 扫描typeAliasespackage指定的包,并为其中的类注册别名 if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (logger.isDebugEnabled()) { logger.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } // 为typeA1iases集合中指定的类注册别名〔略) if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); if (logger.isDebugEnabled()) { logger.debug("Registered type alias: '" + typeAlias + "'"); } } } // 注册plugins集合中指定的插件(以) if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); if (logger.isDebugEnabled()) { logger.debug("Registered plugin: '" + plugin + "'"); } } } // 打描typeHand1ergpackage指定的包·并注册其中的TypeHand1er if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); if (logger.isDebugEnabled()) { logger.debug("Scanned package: '" + packageToScan + "' for type handlers"); } } } // 注册将typeHand1ers集合中指定的TypeHand1er() if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (logger.isDebugEnabled()) { logger.debug("Registered type handler: '" + typeHandler + "'"); } } } if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); if (logger.isDebugEnabled()) { logger.debug("Parsed configuration file: '" + this.configLocation + "'"); } } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource); configuration.setEnvironment(environment); //配置databaseIdProvider if (this.databaseIdProvider != null) { try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (logger.isDebugEnabled()) { logger.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (logger.isDebugEnabled()) { logger.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } return this.sqlSessionFactoryBuilder.build(configuration); }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) public @interface MapperScan {
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { private ResourceLoader resourceLoader; public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 可以看到这里主要是读取了注解上面的一些属性,然后给一个ClassPathMapperScanner对象去设置这些属性值, AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); // 那么这个ClassPathMapperScanner对象的是什么来的呢?这个对象的父类在spring里面及其重要,因为它的父类就是用来进行包扫描的,把扫描到的类封装成一个个的BeanDefinition放在了spring的BeanDefinitionMap里面,其中的细节是spring的核心知识点了,这里就不展开说了。而上面的代码重点是scanner.doScan,在看这句代码之前我们先来看下ClassPathMapperScanner的类继承结构图: ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); if (resourceLoader != null) { // this check is needed in Spring 3.1 scanner.setResourceLoader(resourceLoader); } Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { scanner.setAnnotationClass(annotationClass); } Class<?> markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { scanner.setMarkerInterface(markerInterface); } Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass)); } scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef")); scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); List<String> basePackages = new ArrayList<String>(); for (String pkg : annoAttrs.getStringArray("value")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (String pkg : annoAttrs.getStringArray("basePackages")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } scanner.registerFilters(); scanner.doScan(StringUtils.toStringArray(basePackages)); }
要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory
和至少一个数据映射器类。
在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean
来创建 SqlSessionFactory
。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
}
注意:SqlSessionFactory
需要一个 DataSource
(数据源)。这可以是任意的 DataSource
,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
假设你定义了一个如下的 mapper 接口:
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{userId}")
User getUser(@Param("userId") String userId);
}
那么可以通过 MapperFactoryBean
将接口加入到 Spring 中:
前面已经有了sqlSessionFactory,接下来要让sqlSessionFactory扫描到接口或xml
下面只是注入了单个mapper
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
========等价于===========
@Configuration
public class MyBatisConfig {
@Bean
public MapperFactoryBean<UserMapper> userMapper() throws Exception {
MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
factoryBean.setSqlSessionFactory(sqlSessionFactory());
return factoryBean;
}
}
=======多mapper的情况======
但是我们肯定不能自己挨个注入,所以可以用mybatis-scan的方式
<mybatis:scan base-package="org.mybatis.spring.sample.mapper" />
=====或者=========
@Configuration
@MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig {
// ...
}
需要注意的是:所指定的映射器类必须是一个接口,而不是具体的实现类。在这个示例中,通过注解来指定 SQL 语句,但是也可以使用 MyBatis 映射器的 XML 配置文件。
配置好之后,你就可以像 Spring 中普通的 bean 注入方法那样,将映射器注入到你的业务或服务对象中。MapperFactoryBean
将会负责 SqlSession
的创建和关闭。 如果使用了 Spring 的事务功能,那么当事务完成时,session 将会被提交或回滚。最终任何异常都会被转换成 Spring 的 DataAccessException
异常。
使用 Java 代码来配置的方式如下:
@Configuration
public class MyBatisConfig {
@Bean
public UserMapper userMapper() throws Exception {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
return sqlSessionTemplate.getMapper(UserMapper.class);
}
}
要调用 MyBatis 的数据方法,只需一行代码:
public class FooServiceImpl implements FooService {
private final UserMapper userMapper;
public FooServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User doSomeBusinessStuff(String userId) {
return this.userMapper.getUser(userId);
}
}
整合后自动装配的mapper对象应该和我们之前分析的mapper代理对象相同。
问题也就是如何转化为bean对象放到spring中
spring中bean是由beanDefinition定义的,我们只需要new beanDefinition()
,然后把mybatis的动态代理对象设置进去,然后spring.add(bd)即可。
但是有个问题是动态代理类的名字我们是未知的,同时也不支持getBean(接口)
所以mybatis与spring结合采用的是FactoryBean
spring.get("factoryBean")
拿到的是getObject()
返回的类型,在这里面执行我们前面的sqlSession.getMapper()
即可。
但是此时每个FactoryBean只能产生一个类型的动态代理对象,我们怎么知道到底有多少mybatis的代理对象。
我们在定义FactoryBean的时候通过【构造函数】传入一个【接口】,getObject返回Object类型,这个类就可以重复利用了,不断地new FactoryBean(XXXMapperInterface.class),得到不同的代理对象
然后再利用beanDefinition设置到spring中
list= new LinkedList<>();
for(Class clazz:list){
BeanDefinitionBuilder builder = BeanDefinitionBuilder.getGenericBeanDefinition();
BeanDefinition bd = builder.getBeanDefinition();
bd.setBeanClassName(LubanFactoryBean.class.getName);
db.getConstructorArgumentValues().addGenericAugumentValue(clazz);
}
然后利用@Import
的BeanDefinitionRegister那个类
然后我们就把上面的逻辑放到该类中即可
public class LubanBeanDefinitionRegister implements BeanDefinitionRegister{
public void registerBeanDefinitons(importingClassMetaData,registry){
list= new LinkedList<>();
for(Class clazz:list){
BeanDefinitionBuilder builder = BeanDefinitionBuilder.getGenericBeanDefinition();
BeanDefinition bd = builder.getBeanDefinition();
bd.setBeanClassName(LubanFactoryBean.class.getName);
db.getConstructorArgumentValues().addGenericAugumentValue(clazz);
registry.registryBeanDefinition("bd名字",bd);
}
}
}
-
FactoryBean:负责生成代理对象
-
BeanDefinitionRegister:扫描,根据mapper生成FactoryBean
-
把这个东西通过@Import加到spring的启动类上
-
实际上应该加到@MapperScan注解定义类上
-
@Import(LubanFactoryBean.class) @Retention(Retention.RUNTIME) public @interface MapperScan{ String value() default ""; }
-
让通过@MapperScan(“类”)扫描到
-
public void registerBeanDefinitons(importingClassMetaData,registry){ // 在前面的代码前加 Map<String,Object> annotationAttributes = importingClassMetaData.getAnnotation(MapperScan.class); annotationAttributes.get("value");//就能拿到要注册的类
-
八、sql注入
$和#
sqlSource
$
是动态sql:解析阶段不会进行处理- 在执行sql时生成的boundSQL阶段会进行参数的替换,#会替换为?,$会替换为参数值
- 最终boundSql的?号会在后续JDBC阶段进行赋值
- 动态SQL会引发SQL注入
#
是静态sql:语句中没有$就是静态sql- 静态sql会在sql解析阶段把#替换为?
- 静态sql在执行sql时的生成BoundSql阶段不会处理
- 最终boundSql的?号会在后续JDBC阶段进行赋值
https://www.cnblogs.com/miserable-faith/p/7658550.html
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);//建立GenericTokenParser
parser.parse(text);//GenericTokenParser解析text
return checker.isDynamic();
}
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", // openToken
"}", // closeToken
handler); // TokenHandler
}
DynamicCheckerTokenParser实现的handleToken方法
// DynamicCheckerTokenParser.java
public String handleToken(String content) {
this.isDynamic = true;
return null;
}
ParameterMappingTokenHandler实现的handleToken方法:
// ParameterMappingTokenHandler.java
public String handleToken(String content) {
//#的处理方式,返回占位符?
parameterMappings.add(buildParameterMapping(content));
return "?";
}
九、参数处理
梳理
SqlSession下的四大核心组件
Mybatis中SqlSession下的四大核心组件:
- ParameterHandler 、
- ResultSetHandler 、
- StatementHandler 、
- Executor 。
//ParameterHandler 处理sql的参数对象
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
//包装参数插件
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
//ResultSetHandler 处理sql的返回结果集
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
//包装返回结果插件
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
//StatementHandler 数据库的处理对象
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//包装数据库执行sql插件
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
//创建Mybatis的执行器:Executor
return newExecutor(transaction, defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//mybatis支持的三种执行器:batch、reuse、simple,其中默认支持的是simple
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//包装执行器插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
一切的执行从MapperProxy开始,MapperProxy是MapperProxyFactory使用SqlSession创建出来的。所以MapperProxy中包含SqlSession。
可以看到MapperProxy调用invoke方法,进而调用MapperMethod的execute(),这些MapperMethod就是和你要执行的命令相关,比如执行select语句,则会通过SqlSession的select()方法,最终调用到Executor的query方法。Executor会再协调另外三个核心组件。
- MapperProxyFactory用来创建MapperProxy,这个factory其实主要就是完成了InvokeHandler的bindTarget的功能。而MapperProxy只需要完成invoke方法的功能。
- MapperProxy包含SqlSession
执行器与缓存
把缓存抽象到BaseExecutor,即一级缓存放到这个里面,获取连接的操作也抽象到这里。里面有query和update。所以测试的时候要注意一下,是调用的doQuery还是query
二级缓存commit提交之后,缓存中才会有。一级缓存查询完立刻就有了。
流程:mapper注册中心、执行器、stmHandler、resultSetHandler。都可以加拦截器。责任链
- 一级缓存:kv形式,perpetualCache,hashMap
- sql和参数必须相同
- session一样
- stm一样
- selectOne和动态代理是互通的
- RowBounds返回行范围必须相同
- 没有执行修改、清空操作
- 改成statement可以屏蔽一级缓存,但只能全局修改,不能只修改mapperedStm,但嵌套查询不能屏蔽
- 二级缓存
XML与DOM
xml解析常见的方式有3种:DOM、SAX、StAX
- DOM:基于树形结构的XML解析方式
- SAX:不需要将整个XML文档加载到内存中,而只需将XML文档的一部分加载到内存中,即可开始解析,在处理过程中不会再内存中记录XML的数据(只能从前到后进行,无法返回),所以占用的资源比较少。
- StAX
XPath
mybatis在初始化过程中处理mybatis-config.xml配置文件以及映射文件时,使用的是DOM解析方式,并结合使用XPath解析XML配置文件。DOM会将整个XML文档加载到内存中并形成树状数据结构,而XPath是一种为查询XML文档而设计的语言,他可以与DOM解析方式配合使用。
XPath使用路径表达式来选取XML文档中指定的结点或者结点结集合,与常见的URL路径有些类似。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
@ 选取属性。
../ 从当前规则的父级开始匹配
./ 从当前规则开始匹配
/text() 表示从当前规则下匹配所有文本内容
/@name 表示匹配当前规则下的属性的value
[] 中括号中可以写过滤的条件(支持and、or语法),也可以写数组下标(从1开始)
/div[@class="classname"] 表示匹配当前规则下所有class为classname的div对象
/div[contains(@class,"classname")] 表示匹配当前规则下所有class包含classname的div对象
/div[contains(@class,"classname1") or contains(@class,"classname2")] 表示匹配当前规则下所有class包含classname1或者classname2的div对象
/span[text()="text"] 表示匹配当前规则下文本包含text的所有span对象
/a/following-sibling::* 表示匹配当前规则下a标签之后所有的同级节点对象
/a/following-sibling::*[1] 表示匹配当前规则下a标签之后的第一个同级节点对象
//*[name(.)!="i"] 表示排除所有i标签