mybatis框架设计图
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|update|delete|insert>节点的封装, - SqlSource
负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回 - BoundSql
表示动态生成的SQL语句以及相应的参数信息 - Configuration
MyBatis所有的配置信息都维持在Configuration对象之中。
SqlSession过程流程
- 开启一个数据库访问会话—创建SqlSession对象
- 为SqlSession传递一个配置的Sql语句 的Statement Id和参数,然后返回结果:
List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);
3.MyBatis执行器Executor根据SqlSession传递的参数执行query()方法
上述的Executor.query()方法几经转折,最后会创建一个StatementHandler对象,
然后将必要的参数传递给StatementHandler,使用StatementHandler来完成对数据库的查询,最终返回List结果集。
从上面的代码中我们可以看出,Executor的功能和作用是:
- 根据传递的参数,完成SQL语句的动态解析,生成BoundSql对象,供StatementHandler使用;
- 为查询创建缓存,以提高性能(具体它的缓存机制不是本文的重点,我会单独拿出来跟大家探讨,
感兴趣的读者可以关注我的其他博文); - 创建JDBC的Statement连接对象,传递给StatementHandler对象,返回List查询结果。
- StatementHandler对象负责设置Statement对象中的查询参数、处理JDBC返回的resultSet,
将resultSet加工为List 集合返回:
StatementHandler对象主要完成两个工作:
- 对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使用的是SQL语句字符串会包含 若干个? 占位符,我们其后再对占位符进行设值。
StatementHandler通过parameterize(statement)方法对Statement进行设值; - StatementHandler通过
List<E> query(Statement statement, ResultHandler resultHandler)
方法来完成执行Statement,并将Statement对象返回的resultSet封装成List;
StatementHandler 的parameterize(statement) 方法的实现:
/**
* StatementHandler 类的parameterize(statement) 方法实现
*/
public void parameterize(Statement statement) throws SQLException {
//使用ParameterHandler对象来完成对Statement的设值
parameterHandler.setParameters((PreparedStatement) statement);
}
StatementHandler 的parameterize(Statement) 方法调用了 ParameterHandler的setParameters(statement) 方法,
ParameterHandler 的setParameters(Statement)方法负责根据我们输入的参数,对statement对象的 ? 占位符处进行赋值。
StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)方法的实现:
源码
org.apache.ibatis.executor.statement.PreparedStatementHandler#query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 1.调用PreparedStatement#execute()方法,然后将resultSet交给ResultSetHandler处理
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//2. 使用ResultHandler来处理ResultSet
return resultSetHandler.<E> handleResultSets(ps);
}
StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)
方法的实现,是调用了ResultSetHandler的handleResultSets(Statement) 方法。
ResultSetHandler的handleResultSets(Statement) 方法会将Statement语句执行后生成的 resultSet 结果集转换成List 结果集:
mybatis层次结构
mybatis mapper xml的解析
mybatis 加载入口
org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet
@Override
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = buildSqlSessionFactory();
}
org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
//在默认的构造器中,会注册很多别名处理器
configuration = new Configuration();
// mybatis 插件注册
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
// mybatis 全局配置文件解析
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();
}
}
// mapper的xml解析 重点
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 + "'");
}
}
}
mapper xml 文件下的 resultMap 标签的解析
ResultMap:将查询结果集中的列一一映射到java对象的各个属性上
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
// parser 就是XPathParser对象,使用该对象查找 mapper标签节点
configurationElement(parser.evalNode("/mapper"));
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
// context 就是mapper xml文件中的内容
// 解析 resultMap 标签下的所有标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
返回到
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
mapper xml 文件下的 sql 标签的解析
sql片段:Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的
sqlElement(context.evalNodes("/mapper/sql"));
mapper xml 文件下的 crud 的标签解析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
查看
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
// 将 context 内容即sql内容解析后,分为 动态的SqlNode和静态的SqlNode
List<SqlNode> contents = parseDynamicTags(context);
SqlNode
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags
List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
//sql的所有节点,即 ParentNode#getChildNodes,使用 ParentNode的实现类是 DeferredElementImpl
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE ||
child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
//解析到 sql 内容有静态sql
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
//是动态标签时,会走到这里,例如: <where>
String nodeName = child.getNode().getNodeName();
//根据标签名获取对应的处理器,这里的 nodeName 就是动态sql的标签
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//解析动态标签,例如 <if>
handler.handleNode(child, contents);
isDynamic = true;
}
}
// 看下面动态sql生成的结果 <if>
return contents;
}
// 注册了标签对就的处理类
NodeHandler nodeHandlers(String nodeName) {
Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
map.put("trim", new TrimHandler());
map.put("where", new WhereHandler());
map.put("set", new SetHandler());
map.put("foreach", new ForEachHandler());
map.put("if", new IfHandler());
map.put("choose", new ChooseHandler());
map.put("when", new IfHandler());
map.put("otherwise", new OtherwiseHandler());
map.put("bind", new BindHandler());
return map.get(nodeName);
}
==NodeHandler ==
if 标签解析
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.IfHandler#handleNode
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
//这里迭代调用 parseDynamicTags,解析动态标签里的 sql
List<SqlNode> contents = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
//获取 test 元素上的值,即表达式
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
<!--sql片段-->
<sql id="query_user_where_sql">
<if test="id!=null and id!=''">
id=#{id}
</if>
</sql>
<select id="selectUserById" resultType="com.dn.spring.mybatis.bean.UserDo">
SELECT * from t_user
<where>
<include refid="query_user_where_sql"/>
</where>
</select>
上面的动态sql解析后,如下图所示
回到
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
// MixedSqlNode 里有很多元素,这些元素是分层次结构的,每个元素下面可能还有很多元素,如上图所示的层次
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
回到
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
//将前面获取到的 xml属性,封装成 MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
MappedStatement 类
- id
mapper的命名空间 + sql语句的id - SqlSource
sql已拼装好了, 但是还没有参数值 - resultMaps
sql 结果的映射关系也封装在这个在类中 - sqlCommandType
当前标签是sql的哪个类型
sqlSource内容
将 sql中的 #{ }
替换为 ?
是在 org.apache.ibatis.builder.SqlSourceBuilder#parse
中的 String sql = parser.parse(originalSql);
中做的
最终 会封装到 org.apache.ibatis.session.Configuration#mappedStatements 属性中,key就是mapper的命名空间 + sql语句的id ,value 就是 MappedStatement 对象.每个定义的sql都会有一个MappedStatement 对象
mybatis mapper 注解的解析
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
if (!configuration.isResourceLoaded(resource)) {
//mapper xml的解析
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// mapper 接口方法上 annotation的支持
bindMapperForNamespace();
}
org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// mapper 接口类
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
//查看该方法,根据key(mapper的接口类) 在org.apache.ibatis.binding.MapperRegistry#knownMappers容器中获取值,
// 值是 MapperProxyFactory
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
//初始化 org.apache.ibatis.binding.MapperRegistry#knownMappers 容器
configuration.addMapper(boundType);
}
}
}
}
org.apache.ibatis.session.Configuration#addMapper
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
org.apache.ibatis.binding.MapperRegistry#addMapper
//容器存入信息
knownMappers.put(type, new MapperProxyFactory<T>(type));
//mapper接口类中的方法上的注解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
查看构造器和parse方法
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//加载 mapper xml的配置文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
// 获取接口的方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
//解析方法
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement
// 解析方法上的注解后,将信息封装成 SqlSource
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#getSqlSourceFromAnnotations
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
}
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
final String[] strings =
(String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
// 查看 buildSqlSourceFromStrings 方法
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
} else if (sqlProviderAnnotationType != null) {
//例如 @SelectProvider 注解的支持日解析
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
}
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#buildSqlSourceFromStrings
return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, java.lang.String, java.lang.Class<?>)
if (script.startsWith("<script>")) {
//方法上的注解,例如 @Select 的值是 <script>开头的
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
// 使用XMLLanguageDriver#createSqlSource解析 ognl表达式
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
}else {
//走到这里,则注解里的值是纯文本的
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
// 封装成 DynamicSqlSource
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
注解上也是支持 ognl表达式的,并且使用的是 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource的方式解析的