前期准备
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.ith</groupId>
<artifactId>mybatis-demo4-design</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
实体类:
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
dao接口:
public interface IUserDao {
List<User> findAll();
}
SqlMapConfig.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis的主配置文件-->
<configuration>
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务类型-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源(数据库连接池)-->
<dataSource type="POOLED">
<!--配置连接数据库的基本信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置-->
<mappers>
<mapper resource="cn/ith/dao/IUserDao.xml"/>
</mappers>
</configuration>
IUserDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--映射配置文件-->
<mapper namespace="cn.ith.dao.IUserDao">
<!--配置查询所有-->
<select id="findAll" resultType="cn.ith.domain.User">
select * from user
</select>
</mapper>
原理分析前应具备的知识
- 动态代理:《Java 动态代理详解以及在Spring事务控制中的使用案例》
- JDBC原生写法
JDBC原生写法简述和示例
DriverManager:驱动管理对象
作用:
- 注册驱动(告诉程序使用那个驱动jar包)
- 获取数据库连接
Connection:数据库连接对象
作用:
- 获取执行sql的对象
- 管理事务
开始事务 setAutoCommit(false)
提交事务 commit()
回滚事务 rollback()
ResultSet:结果集对象
Statement:执行sql的对象
- boolean execute(String sql):可以执行任意sql(很少用)
- int executeUpdate(String sql):执行DML(insert、update、delete)、DDL(create、alter、drop)
- ResultSet executeQuery(String sql):select
PreparedStatement:执行sql的对象
示例:
//查询
public static void test() throws Exception {
Connection con = null;
ResultSet result = null;
PreparedStatement pstmt = null;
try{
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false", "root", "root");
String sql = "select * from t where name = ?";
pstmt = con.prepareStatement(sql);
pstmt.setString(1, "张三");
result = pstmt.executeQuery();
while(result.next()){
int id = result.getInt(1);
String name = result.getString("name");
System.out.println(id);
System.out.println(name);
}
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (SQLException e) {
e.printStackTrace();
}finally {
if(result != null) {
try{
result.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if(pstmt != null) {
try{
pstmt.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if(con != null) {
try{
con.close();
}catch (SQLException e){
e.printStackTrace();
}
}
}
}
MyBatis原理分析
总体流程和用到的类/接口
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。【来自MyBatis官网】
- SqlSessionFactoryBuilder类 :用于创建SqlSessionFactory的工厂类
- SqlSessionFactory接口 :用于创建SqlSession的工厂类
- SqlSession接口 :这才是和数据库进行交互的核心类。SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。就是test2中的写法,调用SqlSession提供的selectList方法。不过现在有了一种更简洁的方式——test1的写法:
Mapper接口
的方式。
MyBatis执行流程分为六个步骤,在下面的代码中展示:
public class MyBaitsTest {
@Test
public void test1() throws Exception{
//1读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4使用SqlSession创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//5使用代理对象执行方法
List<User> users = userDao.findAll();
users.stream().forEach(System.out::println);
//6释放资源
session.close();
in.close();
}
//或者这种写法
@Test
public void test2() throws Exception{
//1读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
// //4使用SqlSession创建Dao接口的代理对象
// IUserDao userDao = session.getMapper(IUserDao.class);
// //5使用代理对象执行方法
// List<User> users = userDao.findAll();
List<User> users = session.selectList("cn.ith.dao.IUserDao.findAll");
users.stream().forEach(System.out::println);
//6释放资源
session.close();
in.close();
}
}
开始断点调试分析源码(先使用传统的test2方式做分析)
初始化配置信息
SqlSessionFactory factory = builder.build(in);
打断点进入build方法中,看看SqlSessionFactoryBuilder的实例对象是如何创建SqlSessionFactory对象的。
源码:
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//解析配置文件(SqlMapConfig.xml和IUserDao.xml)
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//从这里可以看出,调用build方法,会最终返回一个DefaultSqlSessionFactory对象
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
Configuration简介:
Configuration对象的属性包含了SqlMapConfig.xml和IUserDao.xml中的所有配置项信息。下面的dataSource中的四个关于数据库连接的参数是不是很眼熟呢?对,这就是我们在SqlMapConfig.xml中配置的信息。
还有mappedStatements属性(MappedStatement类类型,和Configuration一样,是一个很重要的类,后面做介绍),就是我们在IUserDao.xml中的配置信息
也就是说,调用build()方法,主要做了两件事:解析配置文件,存入Configuration对象中;返回一个DefaultSqlSessionFactory对象。
初始化配置文件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部的属性中。
DefaultSqlSessionFactory和SqlSessionFactory介绍:
DefaultSqlSessionFactory类是SqlSessionFactory接口的实现类。DefaultSqlSessionFactory类中主要包含一个Configuration类型的对象属性和多个重载的openSession()方法。
现在,已经拿到了SqlSessionFactory对象,有个这个对象,就能使用openSession(),创建SqlSession。
获取SqlSession
SqlSession简介
SqlSession是一个接口,DefaultSqlSession是它的一个实现类。DefaultSqlSession是MyBatis中用于和数据库交互的顶层类,通常将它与ThreadLocal绑定,一个会话使用一个SqlSession,并且在使用完毕后需要close。
其中有两个重要的属性:
- configuration就是上面提到的Configuration类型的变量
- Executor也是一个接口,和执行具体的SQL语句相关。它有三个常用的实现类BatchExecutor(重用语句并执行批量更新),ReuseExecutor(重用预处理语句prepared statements),SimpleExecutor(普通的执行器,默认)。
继续调试,SqlSession session = factory.openSession();
源码:
@Override
public SqlSession openSession() {
//getDefaultExecutorType()传递的是SimpleExecutor
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别(默认使用数据库的隔离级别),autoCommit是否开启事务
//openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根据参数创建指定类型的Executor
final Executor executor = configuration.newExecutor(tx, execType);
//返回的是DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这一步的主要作用就是获得 SqlSession 的实例 (即DefaultSqlSession对象),打开DefaultSqlSession类的源码可以发现里面提供了很多selectOne、selectMap等方法来操作数据库。 也就是说,DefaultSqlSession对象完全包含了面向数据库执行 SQL 命令所需的所有方法
执行selectList方法
继续调式List<User> users = session.selectList("cn.ith.dao.IUserDao.findAll");
源码:
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据传入的全限定名+方法名从映射的Map中取出MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
//调用Executor中的方法处理
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
MappedStatement简介
MappedStatement与Mapper配置文件中的一个select/update/insert/delete节点相对应。mapper中配置的标签都被封装到了此对象中,主要用途是描述一条SQL语句。在Mybatis中,每一个<select>、<insert>、<update>、<delete>
标签,都会被解析为一个MappedStatement对象。
在最开始的初始化配置信息
讲解中,传入SqlMapConfig.xml配置文件,其中的:
<mappers>
<mapper resource="cn/ith/dao/IUserDao.xml"/>
</mappers>
mappers标签用来引入mapper.xml文件或者配置mapper接口的目录。因此,IUserDao.xml也进行了解析,封装成一个MappedStatement对象,然后存储在Configuration对象的mappedStatements属性中(也就是最开始截图那部分显示的mappedStatements属性信息)。mappedStatements 是一个HashMap,存储时key = 全限定类名 + 方法名,value = 对应的MappedStatement对象。
configuration.getMappedStatement(statement)
这一句根据key来获取值:
继续调式,进入executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER)
的query中。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
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);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
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();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
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 {
// 查询的方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 将查询结果放入缓存
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
//进入doQuery
@Override
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();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
在doQuery方法中,终于看到了Statement类型的变量,这就是我在原理分析前应具备的知识
中提到的执行sql的对象。
现在,进入handler.<E>query(stmt, resultHandler);
的query方法中:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.<E>query(statement, resultHandler);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
很开心,终于看到了execute方法,ps变量中包含了执行的sql语句等消息。
继续进入resultSetHandler.<E> handleResultSets(ps);
方法中:
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
听名字这个方法就是用于处理结果集的,结果集已经出来了:
用test1接口的方式分析
把断点打到这里:IUserDao userDao = session.getMapper(IUserDao.class);
先思考一个问题IUserDao 是一个接口类,为什么可以调用findAll()方法,答案是动态代理
。下面就来分析这个过程,进入getMapper方法中:
//此处跳过两个方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//进入mapperProxyFactory.newInstance(sqlSession);的newInstance方法中:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
//接下来调用这个newInstance
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
//接下来调用这个newInstance
//在这里可以看到通过Proxy.newProxyInstance的方式返回了一个反向代理
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
接下来调式List<User> users = userDao.findAll();
进入findAll方法中,第一个执行的就是invoke方法(通过反射返回的代理对象调用方法时,首先进入的就是invoke方法去执行前置通知)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断调用是是不是Object中定义的方法,toString,hashCode这类非。是的话直接放行。
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 重点在这:MapperMethod最终调用了执行的方法
return mapperMethod.execute(sqlSession, args);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断mapper中的方法类型,最终调用的还是SqlSession中的方法
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//进入的是这个语句中
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
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() + ").");
}
//result就是返回的结果集
return result;
}
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
//程序进入到这个语句,可以看到,虽然是通过动态代理的方式执行findAll()方法,程序最终还是调用的是SqlSession对象提供的操作数据库的接口。
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;
}
注解的方式
上面提到的是mapper.xml配置文件的方式写sql信息,然后读取配置文件,封装成MappedStatement对象。如果是注解的方式,又是怎样获取到sql信息呢?其实xml配置的方式
和注解的方式
对于封装得到的MappedStatement对象是一样的,只是如果我们在SqlMapConfig.xml
中配置了<mapper class="cn.ith.dao.IUserDao"/>
,那么解析器认定我们使用的是注解的方式,如果我们配置了<mapper resource="cn/ith/dao/IUserDao.xml"/>
,那么解析器认定我们使用的是配置xml的方式,然后采取不同的方法去读取信息,封装MappedStatement对象。
接下来可以简单看下源码,可以在SqlSessionFactory factory = builder.build(in);
这一句上打断点,然后进入build(),再进入build(),就能看到build(parser.parse());
,这里就是开始MyBatis配置文件的解析,进入到parse()中:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//从configuration出开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//从这里进入mappers映射配置信息的解析
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
//这个方法就是判断我们使用的是注解还是xml配置的方式,并进行解析操作
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
总结
以上面示例中的findAll方法总结MyBatis的执行流程。
(1)读取SqlMapConfig.xml配置信息中的environment部分,就能创建数据库连接的Connection对象(注册驱动,获取连接)。(用dom4j技术解析xml)
(2)读取SqlMapConfig.xml配置信息中的mappers部分,就能获取到映射配置文件或者是注解(如果是通过dao接口上写注解的方式),然后封装成MappedStatement对象(这其中就包含了sql语句,返回值类型等消息)。
以上两个文件的解析信息都放在了Configuration对象的属性中。
(3)得到SqlSession对象(实际上是它的实现类DefaultSqlSession对象,其中有个重要的属性:executor,执行器,用于调用查询方法,最终调用到原生的JDBC接口查询数据库)
(4)执行sql语句(如果是传统方式,则直接调用DefaultSqlSession对象提供的方法,如果是接口方式,则利用动态代理返回dao接口的代理对象)
(5)得到结果集
附录
参考:
https://blog.csdn.net/weixin_43184769/article/details/91126687
https://mybatis.org/mybatis-3/zh/getting-started.html