一、 环境搭建
1. mysql的环境
1.1 mysql安装地址
Linux环境 :
可使用yum或者docker下载mysql
Windows环境:
可上mysql的官网下载,这里推荐清华tuna镜像网,下载速度更快。
1.2 建表
这里我们需要建立一张测试表用于mybatis的debug
建表语句如下:
CREATE TABLE `user` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`age` int(0) NOT NULL,
`create_time` datetime(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
这里创建了一张表user,字段有id、name、age和create_time
二、 Mybatis介绍
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录,大大简化了开发效率。
研究Mybatis源码可以有如下好处:
- 了解Mybatis的运行流程,当Mybatis出现异常的时候能够更好定位bug
- Mybatis出现性能瓶颈时,能够对Mybatis进行性能调优,对mybatis进一步封装等
- 查询执行的SQL语句,这尤其对于使用了tk.mybatis的项目,能够清楚执行的SQL语句,是否符合预期等
三、 原生JDBC语句
我们先来看看,在没有使用工具(JDBCTemplate等)和框架 (Mybatis等)的情况下,我们使用JDBC查询表user中的所有数据是如何操作的
import java.sql.*;
public class Main {
// JDBC 信息
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8";
static final String USER = "root";
static final String PASS = "123456";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
//STEP 2: 注册JDBC Driver
Class.forName(JDBC_DRIVER);
//STEP 3: 建立连接
conn = DriverManager.getConnection(DB_URL,USER,PASS);
//STEP 4: 执行查询
stmt = conn.createStatement();
String sql;
sql = "SELECT * FROM user";
ResultSet rs = stmt.executeQuery(sql);
//STEP 5: 封装结果集
while(rs.next()){
String id = rs.getString("id");
String name = rs.getString("age");
Integer age = rs.getInt("age");
Date createTime = rs.getDate("create_time");
User user = new User(id, name, age, createTime);
System.out.println(user);
System.out.println("---------------");
}
//STEP 6: 关闭资源
rs.close();
stmt.close();
conn.close();
}catch(SQLException e){
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(stmt!=null)
stmt.close();
} catch (SQLException se2){
}
try{
if(conn!=null)
conn.close();
} catch (SQLException e){
e.printStackTrace();
}
}
System.out.println("Goodbye!");
}
}
使用JDBC语句查询查询数据总结为如下步骤
- 注册JDBC Driver
- 建立连接,获取Connection对象
- 执行查询语句
- 将mysql结果集封装到Java对象中
- 关闭资源
我们发现,上述操作中,不同的DML语句上述的步骤是类似的,获取连接,执行语句(仅仅是SQL语句不同),封装结果集(封装到的对象不同),关闭资源。因此,我们可以将这些通用的操作封装到一个工具或者框架中,仅仅只需要提供执行的SQL语句,封装到的结果集。
而JDBCTemplate、Mybatis等框架,就是提供了这些功能的工具和框架。
四、 Mybatis源码的核心类
要深入理解Mybatis的流程和源码,我们首先介绍Mybatis的核心类以及它的功能,读者仅仅需要记住这些核心类以及他们的功能,后面会深入他们的源码。
1. SqlSessionFactory
SqlSession的工厂类,用于生产SqlSession,可以通过Configuration getConfiguration();获取Configuration对象。
DefaultSqlSessionFactory为SqlSessionFactory的实现类
2. SqlSession
Mybatis的关键类,通过SqlSession类执行语句,获取Mapper类等。
如查询有selectList、selectMap方法, 更新有update方法等
DefaultSqlSession和SqlSessionTemplate为SqlSession的实现类,DefaultSqlSession中有个重要的属性Executor executor。
3. Executor
执行器,真正获取连接,解析SQL语句,真正干活的类。前面的SqlSession正是组合Executor类来完成功能。
其中,CachingExecutor是一级缓存的实现,后面我们会介绍到。
4. Configuration
配置文件类,我们在编写Mybatis时需要使用一个全局的mybatis配置文件,以及多个XxxMapper.xml文件。而Java是一门面向对象的语言,因此使用了Configuration类将我们mybatis的配置文件信息封装起来。
5. MapperProxyFactory
听类名就猜到了该类的作用,MapperProxyFactory是一个用来创建MapperProxy的工厂类。
6. MapperProxy
MapperProxy,同样该类名的字面意思就是Mapper的代理类。我们在Mybatis框架中,通常使用的是Mapper的接口,而不需要创建真正的实体类。正是由MapperProxy代理类帮我们完成真正的逻辑。
7. 总结
接下来,我们用一张图总结上面的类关系图(非标准的UML类图)
- 由MapperProxyFactory创建UserMapper的代理类MapperProxy
- SqlSessionFactory创建了SqlSession
- SqlSession聚合了执行器Executor(Executor是真正干活的类)
- SqlSession和SqlSessionFactory都聚合了Configuration类(Mybatis的xml配置文件)
- MapperProxy聚合了SqlSession
五、 Mybatis的简单Demo
我们首先通过简单的Demo,看看如何使用Mybatis。使用到的数据库表为上述提到的User表。
以下Demo的代码链接,大家只需要安装好MySQL和修改对应的数据库配置信息,即可正确运行。
1. 创建数据库表对应的实体User
package com.junehua.pojo;
import java.util.Date;
public class User {
private String id;
private String name;
private Integer age;
private Date createTime;
public User() {
}
public User(String id, String name, Integer age, Date createTime) {
this.id = id;
this.name = name;
this.age = age;
this.createTime = createTime;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("User{");
sb.append("id='").append(id).append('\'');
sb.append(", name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append(", createTime=").append(createTime);
sb.append('}');
return sb.toString();
}
}
2. 创建User对象对应的Mapper接口
package com.junehua.mapper;
import com.junehua.pojo.User;
import java.util.List;
public interface UserMapper {
int insert(User user);
List<User> listAll();
}
3. Mybatis配置文件
首先,我们在resources目录下创建一个mybatisConfig.xml,mybatisConfig.xml的配置文件如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!--mybatis全局配置文件-->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--<properties resource="db.properties"/>-->
<settings>
<!--开启驼峰命名规则自动转换-->
<setting name="mapUnderscoreToCamelCase" value="true" />
<!--是否开启二级缓存-->
<setting name="cacheEnabled" value="false"/>
</settings>
<typeAliases>
<!--数据库表对应的Entity类所在包-->
<package name="com.junehua.pojo"></package>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<!--数据源信息-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--mapper文件所在的目录-->
<mappers>
<mapper resource="mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
4. 编写Mapper的映射文件UserMapper.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="com.junehua.mapper.UserMapper">
<select id="listAll" resultType="com.junehua.pojo.User" >
select * from user;
</select>
<insert id="insert" parameterType="com.junehua.pojo.User">
insert into user(id, name, age, create_time) value(#{id}, #{name}, #{age}, now())
</insert>
</mapper>
5. 编写并运行程序
public class MybatisDemo {
@Test
public void mybatisDemoTest() throws IOException {
// Mybatis全局配置文件
InputStream mybatisConfig = Resources.getResourceAsStream("mybatisConfig.xml");
// 创建SqlSession工厂类
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mybatisConfig);
// 由工厂类创建SqlSession类
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取Mapper文件
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 查询并打印数据
List<User> users = userMapper.listAll();
users.forEach(System.out::println);
}
}
使用SqlSessionFactory工厂类创建SqlSession,由SqlSession获取UserMapper接口,注意,UserMapper是个接口,该接口的实现类是由MapperProxyFactory创建的MapperProxy代理类。
6. 运行结果
我们使用Navicat查看数据库表中的数据信息,如下:
共有两条数据,查看IDEA控制台输出的信息如下:
成功打印数据。
六、 Mybatis源码
通过以上Mybatis的Demo,我们对Mybatis有了一个基本的理解,接下来,我们将根据上面给出的Demo,研究Mybatis的实现和核心源码。
public class MybatisDemo {
@Test
public void mybatisDemoTest() throws IOException {
// Mybatis全局配置文件
InputStream mybatisConfig = Resources.getResourceAsStream("mybatisConfig.xml");
// 创建SqlSession工厂类
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mybatisConfig);
// 由工厂类创建SqlSession类
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取Mapper文件
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 查询并打印数据
List<User> users = userMapper.listAll();
users.forEach(System.out::println);
}
}
1. SqlSessionFactory和Configuration
首先,我们看到通过new SqlSessionFactoryBuilder().build(mybatisConfig);创建了一个SqlSessionFactory
这里传入了我们的Mybatis配置文件,所以我们不难猜测这里会将mybatisConfiguration.xml解析为Configuration类。
我们进入 SqlSessionFactoryBuilder.build(),该方法返回SqlSessionFactory对象
// SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
// 我们接着进入build方法
// SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
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.
}
}
}
这里的 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties)返回一个 XMLConfigBuilder parser ,我们进入XMLConfigBuilder 的parse()方法
// XMLConfigBuilder
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
果然,这里返回了一个Configuration,我们进入Configuration类如下:
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
....
}
这里仅仅截取了部分的代码,我们观察上面的protected boolean mapUnderscoreToCamelCase;
这不正是Mybatis全局配置文件中是否开启下划线转驼峰命名的配置吗。其他的属性也是如此,将全局配置文件、Mapper的xml映射到Configuration类中。
<settings>
<!--开启驼峰命名规则自动转换-->
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
上面的Configuration类中还有个关键的属性Map<String, MappedStatement> mappedStatements,这个mappedStatements就是我们将XxxMapper.xml转化后存储的结果。
其中,
key:全类名方法名,如com.junehua.mapper.UserMapper.listAll
value:为一个MappedStatement对象,该对象保存了该方法的相关信息,如参数类型、resultSet类型等。
我们回到SqlSessionFactoryBuilder类中的build方法代码
// SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
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(parser.parse())
// SqlSessionFactoryBuilder
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
就是返回了一个SqlSessionFactory的实现类DefaultSqlSessionFactory。
我们接下来进入SqlSessionFactory源码如下:
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
可以看到SqlSessionFactory的作用是获取Configuration、创建SqlSession的工厂。
好的,第一部分的SqlSessionFactory和Configuration的源码我们分析清楚了,接下来我们总结一下:
我们的全局配置文件和Mapper的xml会存储在Configuration类中,SqlSessionFactory中保存了一份Configuration对象,如下图:
2. SqlSessionFactory.openSession();
我们进入到SqlSession sqlSession = sqlSessionFactory.openSession();
// DefaultSqlSessionFactory
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
继续进入到 openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
// DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 返回一个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();
}
}
这里我们关键看configuration.newExecutor(tx, execType);这里会返回一个Executor对象。我们重新查看Executor的继承图。
这里我们进入到Configuration的newExecutor方法中
// Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
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);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这里我们传入的executorType是ExecutorType.SIMPLE,因此会创建一个SimpleExecutor。我们看关键的一行代码
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
这里就是Mybatis的一级缓存,如果cacheEnabled是true(Mybatis的全局配置文件mybatisConfig.xml中配置),那么就创建一个CachingExecutor,并把刚刚创建的Executor作为CachingExecutor的属性。
我们回到如下代码:
// DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 返回一个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();
}
}
这里会创建一个DefaultSqlSession,并把刚刚创建出来的executor传入。
接下来,我们总结一下SqlSessionFactory.openSession()的作用
- 创建一个Executor,这里我们创建了一个SimpleExecutor。如果Mybatis开启了一级缓存cacheEnabled(默认开启),那么创建一个CachingExecutor并且将SimpleExecutor传入。
- 创建一个DefaultSqlSession并返回
3. SqlSession.getMapper(UserMapper.class);
我们进入到sqlSession.getMapper(UserMapper.class); 这里的SqlSession的实现类是DefaultSqlSession
// DefaultSqlSession
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
继续进入到Configuration的getMapper
// Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
进入到如下代码:
// MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取MapperProxyFactory工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 创建一个UserMapper代理类
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
在这里,我们看到了我们熟悉的MapperProxyFactory。对,MapperProxyFactory正是我们用于创建MapperProxyFactory的工厂类,果然,在下面出现了return mapperProxyFactory.newInstance(sqlSession);
我们进入到代码
// MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
// 创建MapperProxy
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
这正是我们使用JDK动态代理的方法Proxy.newProxyInstance(类加载器, 代理接口, 拦截方式)。后面我们会看MapperProxy如何进行拦截。至此,我们通过UserMapper userMapper = sqlSession.getMapper(UserMapper.class);拿到了UserMapper的代理类,接下来,我们将进入到UserMapper的MapperProxy代理类,看看该代理类如何完成代理操作。
4. MapperProxy
MapperProxy类比较简单,这里我们把所有的代码拉出来如下
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
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;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
/**
* Backport of java.lang.reflect.Method#isDefault()
*/
private boolean isDefaultMethod(Method method) {
return (method.getModifiers()
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
&& method.getDeclaringClass().isInterface();
}
}
这里,我们看到熟悉的InvocationHandler。对,这就是我们动态代理的拦截类,我们重点看InvocationHandler的抽象方法invoke。
// MapperProxy
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 当前方法是Object方法,则直接执行方法,如toString、equal等
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) { // 是否是默认方法 default修饰的方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 我们的demo将会进入到此处
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
我们看到MapperMethod类,看类名大概的意思是Mapper的方法,这里是我们UserMapper的listAll()对应的额MapperMethod,代码如下:
public class MapperMethod {
// sql的信息,对应那个方法。 sql的类型,select、update等
private final SqlCommand command;
// 方法描述,如方法的参数、参数类型、返回结果
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
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() + ").");
}
return result;
}
....
}
MapperMethod中有两个属性,分别是SqlCommand和 MethodSignature
public static class SqlCommand {
// 这里的name 是我们的方法名 com.junehua.mapper.UserMapper.listAll
private final String name;
// 这里的type是我们方法的类型,例如INSERT、UPDATE等
// 我们这里的listAll用的是SELECT
private final SqlCommandType type;
....
}
// 我们方法的相关信息,例如方法的返回值、 参数名映射规则等
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
....
}
了解了SqlCommand和MethodSignature,我们回到MapperMethod的execute方法
// MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
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()) {
// demo将进入到此处
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() + ").");
}
return result;
}
这里,我们使用的是SELECT类型,并且返回的是List<User>。代码会进入到result = executeForMany(sqlSession, args),我们继续进入到executeForMany
// MethodMapper
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.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.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;
}
这里,总算来到了我们的SqlSession,执行了selectList方法,接下来,我们将分析SqlSession的源码。
5. SqlSession
我们进入SqlSession的实现类DefaultSqlSession。
public class DefaultSqlSession implements SqlSession {
// 前面介绍的Configuration,存储mybatis的xml文件
private final Configuration configuration;
// 执行器,这里我们用到的实现类是SimpleExecutor,若开启一级缓存,这里使用的是CachingExecutor
private final Executor executor;
// 是否自动提交
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
这里再次说明,SqlSession并不存在Connection、Statement处理等,真正执行JDBC连接、查询、封装结果的并不是SqlSession,而是SqlSession的Executor。
接下来,我们进入到DefaultSqlSession的sqlSession.selectList(command.getName(), param);
// DefaultSqlSession
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 还记得这里的statement值吗?
// 获取Map中key:com.junehua.mapper.UserMapper.listAll的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();
}
}
DefaultSqlSession真正执行查询的是Executor执行器,接下来,我们将分析SimpleExecutor源码。
6. SimpleExecutor
首先,我们复习一下Executor执行的继承关系图
我们使用到的是SimpleExecutor,我们进入到executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
// BaseExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 这里获取到了一个BoundSql对象,BoundSql对象里面有个String sql属性,该sql属性就是我们在数据库中执行的SQL语句
// 这里的BoundSql的sql值为“select * from user;”,即查询表user中的所有数据
// 如果我们在程序中,需要知道执行的sql语句,可以打断点来到此处查看sql语句
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// BaseExecutor
@SuppressWarnings("unchecked")
@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 {
// 我们看queryFromDatabase,从数据库中查询,我们进入到对应的代码块
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 {
// 执行doQuery
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;
}
接下来,我们进入到SimpleExecutor的doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
// SimpleExecutor
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// 这里看到了我们JDBC中熟悉的Statement
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 获取到一个StatementHandler的实现类RountingStatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行RoutingStatementHandler的query(stmt, resultHandler)
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// Configuration
// 此方法将会返回一个RoutingStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// RoutingStatementHandler会根据StatementType的类型,聚合一个StatementHandler对象
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 如果Mybatis有插件,则添加到此处
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
// 进入到prepareStatement(handler, ms.getStatementLog());
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 这里看到了我们JDBC中熟悉的Connection
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
我们进入到RountingStatementHandler的query(stmt, resultHandler);
// 进入到handler.query(stmt, resultHandler);
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 这里的delegate是PreparedStatementHandler
return delegate.query(statement, resultHandler);
}
// delegate.query(statement, resultHandler);
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 这里看到了我们JDBC中熟悉的PreparedStatment
PreparedStatement ps = (PreparedStatement) statement;
// 执行我们的sql语句
ps.execute();
// 使用DefaultResultSetHandler完成我们的结果集处理
// 将查询出来的数据封装到List<User>中
return resultSetHandler.handleResultSets(ps);
}
总结一下SimpleExecutor的执行流程:
- 获取执行的SQL语句:BoundSql boundSql
- 根据Statement的类型StatementType创建RoutingStatementHandler
- 执行SimpleExecutor的doQuery方法()
- 使用StatementHandler的query进行查询
- 使用ResultSetHandler对结果集进行封装
至此,我们对Mybatis的核心源码有了一定的了解,大家可以通过UserMapper中的insert(User user)方法,进行debug,走一遍Mybatis的源码实现。
如果本文有什么错误的地方,欢迎留言私信我。