最近抽空理了一下mybatis的源码,想把它分享出来和大家一起讨论。
类图:
首先我们从使用入手,一步步深入到具体的源码。
- 依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.7</version>
</dependency>
- mybatis配置文件 mybatis-cfg.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">
<configuration>
<properties>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&serverTimezone=UTC"/>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
<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>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
- 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="mybatis.dao.UserMapper">
<select id="selectUserByName" resultType="mybatis.entity.User">
select * from user where name = #{name}
</select>
</mapper>
- UserMapper 接口
public interface UserMapper {
User selectUserByName(String name);
}
- 测试类
public class MybatisDemo {
public static void main(String[] args) throws IOException {
String confFile = "mybatis-cfg.xml";
// 解析配置文件,构建Configuration对象
Configuration configuration = new XMLConfigBuilder(Resources.getResourceAsStream(confFile)).parse();
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserByName("tro");
System.out.println("user:" + user.getName());
}
}
现在我们从测试类入手,一步步分析整个过程
解析xml文件
Configuration configuration = new XMLConfigBuilder(Resources.getResourceAsStream(confFile)).parse();
具体的解析过程我们不深入研究。我们主要来看我们的mybatis-cfg.xml配置文件里有什么:
1.数据库配置
2.需要解析的mapper文件。这里我们就配置了一个UserMapper.xml
然后我们的 UserMapper.xml 设置了什么信息:
- 设置了namespace ,关联接口UserMapepr
namespace="mybatis.dao.UserMapper"
2.设置了具体的方法名,sql内容,参数,参数类型,返回类型等信息
<select id="selectUserByName" parameterType="java.lang.String" resultType="mybatis.entity.User">
select * from user where name = #{name}
</select>
MappedStatement类就是对每个xml方法的抽象。每个方法对应一个MappedStatement实例。
MappedStatement类:
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
每个xml中的方法被记录在Configuration类中的键值对中,每个方法记录两个key,一个是简单方法名,一个是带包名的key,但是value值同一个MappedStatement实例
Configuration类:
public class Configuration {
protected Environment environment;
/**
省略部分代码
**/
// 该类中保存接口和对应的代理工厂键值对,后面具体讲
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
// 这里记录 方法名和 MappedStatement键值对
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
我们来看一下运行时mappedStatements的值:
ps:在这里我们可以思考一下,有了这些信息我们能做些什么呢?
我们知道了一个具体的接口,每个对应的方法要执行什么样的sql,参数是什么,返回是什么类型,这些我们都知道了。我们是不是可以构建一个模板行为,用这些已知的信息,去实现我们的接口呢?回答是肯定的。因为信息已经足够了。
我们接着往下,构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
先看一下SqlSessionFactory接口
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean var1);
SqlSession openSession(Connection var1);
SqlSession openSession(TransactionIsolationLevel var1);
SqlSession openSession(ExecutorType var1);
SqlSession openSession(ExecutorType var1, boolean var2);
SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);
SqlSession openSession(ExecutorType var1, Connection var2);
Configuration getConfiguration();
}
没别的什么东西,接着看一下实现类中如何构建SqlSession的
继续往下,获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
这里首先讲一下SqlSession是什么
public interface SqlSession extends Closeable {
<T> T selectOne(String var1);
<T> T selectOne(String var1, Object var2);
<E> List<E> selectList(String var1);
<E> List<E> selectList(String var1, Object var2);
<E> List<E> selectList(String var1, Object var2, RowBounds var3);
<K, V> Map<K, V> selectMap(String var1, String var2);
<K, V> Map<K, V> selectMap(String var1, Object var2, String var3);
<K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);
void select(String var1, Object var2, ResultHandler var3);
void select(String var1, ResultHandler var2);
void select(String var1, Object var2, RowBounds var3, ResultHandler var4);
int insert(String var1);
int insert(String var1, Object var2);
int update(String var1);
int update(String var1, Object var2);
int delete(String var1);
int delete(String var1, Object var2);
void commit();
void commit(boolean var1);
void rollback();
void rollback(boolean var1);
List<BatchResult> flushStatements();
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> var1);
Connection getConnection();
}
涵盖了我们所有的数据库操作行为。这显然实对jdbc的上层封装。
继续看看具体的实现类DefaultSqlSession,我们来挑一个具体的方法来说
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var6;
try {
// 根据方法名 从configuration中获取
MappedStatement ms = this.configuration.getMappedStatement(statement);
// 委托执行器执行
List<E> result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
var6 = result;
} catch (Exception var10) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10);
} finally {
ErrorContext.instance().reset();
}
return var6;
}
我们好像看到了什么熟悉的东西,对就是 MappedStatement 。我们从configuration中获取从xml中解析的方法描述。
这里还有一个发现 DefaultSqlSession并不知最终的执行类,而是将请求委托给executor去执行。
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement var1, Object var2) throws SQLException;
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean var1) throws SQLException;
void rollback(boolean var1) throws SQLException;
CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);
boolean isCached(MappedStatement var1, CacheKey var2);
void clearLocalCache();
void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);
Transaction getTransaction();
void close(boolean var1);
boolean isClosed();
void setExecutorWrapper(Executor var1);
}
Executor 提供了最基础的执行操作。
ps:这里我们可以想一下,Executor最终是不是调用jdbc的基本操作呢?
获取Mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
进入getMapper看一下:
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
然后从configuration获取mapper。入参时接口类型和当前DefaultSqlSession实例。
继续进入configuration.getMapper(type, this)
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
这里要说一下mapperRegistry
public class MapperRegistry {
private Configuration config;
// 关键在这里
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
// 代理工厂生成代理类
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
/**
省略部分代码
**/
}
重点看一下这个属性:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
应该可以猜到,这个map保存的应该是mapper接口和对应的代理工厂
看一下运行时数据:
然后继续看MapperProxyFactory
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return this.mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return this.methodCache;
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(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;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
// 从缓存中获取
MapperMethod mapperMethod = this.cachedMapperMethod(method);
// 具体执行
return mapperMethod.execute(this.sqlSession, args);
}
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
MapperProxy实现InvocationHandler是一个动态代理。为我们的mapper接口动态代理生成代理类。
进入mapperMethod看一下
public class MapperMethod {
private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, method);
}
// 重点看一下这个方法。将具体的执行交给了sqlSession
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if (SqlCommandType.INSERT == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
} else if (SqlCommandType.UPDATE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
} else if (SqlCommandType.DELETE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
} else {
if (SqlCommandType.SELECT != this.command.getType()) {
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
/**
省略部分代码
**/
}
绕了一圈。最终执行还是绕到了sqlSession去执行。
我们来总结一下:
1.首先解析配置mybatis-cfg.xml 和 UserMapper.xml 生成Configuration类
将每个方法名和具体xml中的sql信息保存至Map<String, MappedStatement> mappedStatements中保存
将mapper接口和对应的代理工厂类保存至中MapperRegistry
2.获取SqlSession,将SqlSession实例和对应的UserMapper接口类获取到UserMapperProxy代理类。
UserMapperProxy调用SqlSession
SqlSession委托Executor
Executor最终执行jdbc
这里知识单纯的使用mybatis,后面我们将讨论spring整合mybatis。