mybatis源码分析(二)数据查询过程分析

请大家先看demo代码:

 

Main主类

 

Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/Configuration1.xml"));

SqlSessionFactory build = new SqlSessionFactoryBuilder().build(reader);

 

SqlSession sqlSession = build.openSession();

 

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

User user = mapper.getUser(1);

System.out.println(user.getId());

System.out.println(user.getName());

System.out.println(user.getPassword());

 

UserMapper类

    int insert(User user);

 

UserMapper.xml

<insert id="insert" parameterType="com.mybatis.study.entity.User" >

    insert into user(name,password) values(#{name},#{password})

</insert>

 

Configuration.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>

<environments default="dev">

<environment id="dev">

<transactionManager type="JDBC"/>

<dataSource type="POOLED">

<property name="driver" value="com.mysql.jdbc.Driver"/>

<property name="url" value="jdbc:mysql://127.0.0.1:3306/dev?useUnicode=true&amp;serverTimezone=UTC&amp;characterEncoding=UTF-8"/>

<property name="username" value="root"/>

<property name="password" value="123456"/>

</dataSource>

</environment>

</environments>

 

<mappers>

<mapper resource="UserMapper.xml"/>

</mappers>

 

</configuration>

 

这是一个简单的demo,使用传统的mybatis,配置文件中配置了数据源、mapper文件地址,事务管理器之类的信息,mybatis启动的逻辑为先读取配置文件,将读取到的流封装成Reader传入到大家都很熟悉的SqlSessionFactoryBuilder类中构建得到SqlSessionFactory工厂对象,通过工厂对象获得SqlSession用于数据访问的session对象,用我们自己定义的UserMapper类接收到Mapper对象,在通过我们自己定义的方法实现数据访问这里开始,我们就可能有一点奇怪,明明我们定义的是一个没有具体实现的接口,为什么可以直接调用方法,而且还可以获得我们想要的结果呢?这里可以先简单告诉大家,从SqlSession.getMapper中获取的对象,是一个实现了我们定义的UserMapper接口的一个动态代理类,mybatis帮助我们构建了这么一个动态代理类,其中的逻辑还帮助我们封装了对数据库的访问。下面我们来具体看看,mybatis是怎么实现数据查询的。

 

我们通过调试进入sqlSession.getMapper方法,这个方法传入一个Class<?>对象,也就是我们的接口UserMapper.class,SqlSession是一个接口,它有两个实现类,DefaultSqlSession和SqlSessionManager,在这里是走入到DefaultSqlsession中的getMapper方法(具体逻辑在openSession中,本文不做详述),我们进入会经过Configuration类和MapperRegistry类,我们直接看到MapperRegistry类的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);

}

 

可以看到,会先尝试从knownMappers(这个属性会在构建Configuration类的时候通过读取配置文件和映射文件,自动解析填充的)这个Map中获取MapperProxyFactory对象,如果没有获取到则抛出异常,获得mapperProxyFactory工厂对象后在传入sqlsession对象来构建实例

newInstance的主要逻辑:

 

public T newInstance(SqlSession sqlSession) {

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);

}

 

在这里就是重点了,MapperProxy类是一个实现了InvocationHandler接口的类,懂得Jdk动态代理的同学应该很熟悉,创建jdk动态代理时,也就是后面那个方法Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);是需要传入InvocationHandler的一个实例,这个时候生成的动态代理类逻辑的主要处理是在InvocationHandler接口的invoke方法中,也就是说我们获取的Mapper对象,调用他的方法时,都会去执行这个invoke方法。如下

@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 (method.isDefault()) {

if (privateLookupInMethod == null) {

return invokeDefaultMethodJava8(proxy, method, args);

} else {

return invokeDefaultMethodJava9(proxy, method, args);

}

}

} catch (Throwable t) {

throw ExceptionUtil.unwrapThrowable(t);

}

final MapperMethod mapperMethod = cachedMapperMethod(method);

return mapperMethod.execute(sqlSession, args);

}

invoke方法有三个参数,第一个参数proxy是代理对象,第二个是我们调用的方法的反射表示Method,第三个参数则是方法调用的参数,用数组表示。我们来分析一下这个方法。

前面的判断是为了过滤调用Object的方法,真正执行数据库查询的是在最后两行

final MapperMethod mapperMethod = cachedMapperMethod(method);

return mapperMethod.execute(sqlSession, args);

cacheMapperMethod(method)方式是查询和更新缓存,具体的实现如下

return methodCache.computeIfAbsent(method,

k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));

method是一个map缓存,其中method作为key,MapperMethod作为value,这个方法表示如果存在method则直接返回,不存在则new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())创建,并保存到map中,返回创建的对象。获得对象调用execute(sqlSession, args);进入到execute方法,如下:

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;

}

这个方法有点长,但是其实逻辑很简单,主要是判断传入当前对象需要执行的sql操作类型,我们可以看到,有五种sql类型分别是INSERT、UPDATE、DELETE、SELECT和FLUSH(刷新)操作,其中SELECT操作是相对比较负责的操作,涉及到一对多,一对一之类,相信大家也很熟悉。

再分析这段代码之前,我们先看一下,method和command两个字段的含义,这个字段是在构造函数中被创建,两个字段的类型分别是SqlCommand和MethodSignature,这两个类都是MapperMethod类中的静态内部类,先看一下 SqlCommand类

public static class SqlCommand {

 

private final String name;

private final SqlCommandType type;

 

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {

final String methodName = method.getName();

final Class<?> declaringClass = method.getDeclaringClass();

MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,

configuration);

if (ms == null) {

if (method.getAnnotation(Flush.class) != null) {

name = null;

type = SqlCommandType.FLUSH;

} else {

throw new BindingException("Invalid bound statement (not found): "

+ mapperInterface.getName() + "." + methodName);

}

} else {

name = ms.getId();

type = ms.getSqlCommandType();

if (type == SqlCommandType.UNKNOWN) {

throw new BindingException("Unknown execution method for: " + name);

}

}

}

 

public String getName() {

return name;

}

 

public SqlCommandType getType() {

return type;

}

 

private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,

Class<?> declaringClass, Configuration configuration) {

String statementId = mapperInterface.getName() + "." + methodName;

if (configuration.hasStatement(statementId)) {

return configuration.getMappedStatement(statementId);

} else if (mapperInterface.equals(declaringClass)) {

return null;

}

for (Class<?> superInterface : mapperInterface.getInterfaces()) {

if (declaringClass.isAssignableFrom(superInterface)) {

MappedStatement ms = resolveMappedStatement(superInterface, methodName,

declaringClass, configuration);

if (ms != null) {

return ms;

}

}

}

return null;

}

}

SqlCommand类用于封装sql语句,但是其中又并不存在真正的sql语句,其中只是存储了对应sql的id和类型,在sqlCommand的构造函数中,是通过resolveMappedStatement函数获取到封装有sql的对象,这个对象是从Configuration中获取的(这里可以看出,mybatis把很多运行要用到的东西都放进了Configuration类中,在build过程中做了很多事情),构造的前面逻辑判断方法上是否标识了Flush注解,这个我们也很少用,主要累积在else中,将封装有sql的mappedStatement对象的id和type保存下来了,其中的type也是用于后面分析sql操作类型的字段。

 

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;

 

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {

Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);

if (resolvedReturnType instanceof Class<?>) {

this.returnType = (Class<?>) resolvedReturnType;

} else if (resolvedReturnType instanceof ParameterizedType) {

this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();

} else {

this.returnType = method.getReturnType();

}

this.returnsVoid = void.class.equals(this.returnType);

this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();

this.returnsCursor = Cursor.class.equals(this.returnType);

this.returnsOptional = Optional.class.equals(this.returnType);

this.mapKey = getMapKey(method);

this.returnsMap = this.mapKey != null;

this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);

this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);

this.paramNameResolver = new ParamNameResolver(configuration, method);

}

 

public Object convertArgsToSqlCommandParam(Object[] args) {

return paramNameResolver.getNamedParams(args);

}

 

public boolean hasRowBounds() {

return rowBoundsIndex != null;

}

 

public RowBounds extractRowBounds(Object[] args) {

return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;

}

 

public boolean hasResultHandler() {

return resultHandlerIndex != null;

}

 

public ResultHandler extractResultHandler(Object[] args) {

return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;

}

 

public String getMapKey() {

return mapKey;

}

 

public Class<?> getReturnType() {

return returnType;

}

 

public boolean returnsMany() {

return returnsMany;

}

 

public boolean returnsMap() {

return returnsMap;

}

 

public boolean returnsVoid() {

return returnsVoid;

}

 

public boolean returnsCursor() {

return returnsCursor;

}

 

/**

* return whether return type is {@code java.util.Optional}.

* @return return {@code true}, if return type is {@code java.util.Optional}

* @since 3.5.0

*/

public boolean returnsOptional() {

return returnsOptional;

}

 

private Integer getUniqueParamIndex(Method method, Class<?> paramType) {

Integer index = null;

final Class<?>[] argTypes = method.getParameterTypes();

for (int i = 0; i < argTypes.length; i++) {

if (paramType.isAssignableFrom(argTypes[i])) {

if (index == null) {

index = i;

} else {

throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");

}

}

}

return index;

}

 

private String getMapKey(Method method) {

String mapKey = null;

if (Map.class.isAssignableFrom(method.getReturnType())) {

final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);

if (mapKeyAnnotation != null) {

mapKey = mapKeyAnnotation.value();

}

}

return mapKey;

}

}

MethodSignature类是对方法的封装,其中封装了方法一对多、参数解析等一系列对象,这里不作过多分析。我们再回过头来看数据的查询逻辑

Object param = method.convertArgsToSqlCommandParam(args);

result = sqlSession.selectOne(command.getName(), param);

先使用method方法将参数转化,在调用sqlSession.selectOne()方法,进入调试查看

 

@Override

public <T> T selectOne(String statement, Object parameter) {

// Popular vote was to return null on 0 results and throw exception on too many.

List<T> list = this.selectList(statement, parameter);

if (list.size() == 1) {

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;

}

}

查询一个返回值时,还是会调用查询列表的方法,只是只取第一个元素,当返回的list大小超过一条时,抛出异常TooManyResultsException,这个异常我们也可能经常可以看到。

我们进入到selecList方法

 

@Override

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {

try {

MappedStatement ms = configuration.getMappedStatement(statement);

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();

}

}

这里通过之前存的sql的id查询到MappedStatement对象,再讲查询的逻辑委托给执行器executor来查询和处理返回集。

进入到executor.query()方法,这个executor模式会使用CachingExecutor执行器(拥有缓存功能的执行器,也及时我们说的一级缓存),进入到CachingExecutor.query方法

BoundSql boundSql = ms.getBoundSql(parameterObject);

CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);

return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

获得绑定的sql,这里使用BoundSql封装,通过boundSql创建key,用于下次查询可以从缓存中获取。查询逻辑流转到query方法

 

@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, boundSql);

@SuppressWarnings("unchecked")

List<E> list = (List<E>) tcm.getObject(cache, key);

if (list == null) {

list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

tcm.putObject(cache, key, list); // issue #578 and #116

}

return list;

}

}

return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

}

 

先从缓存中获取,如果有,则返回缓存中的数据,没有回通过delegate代理查询。

 

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;

}

逻辑会流转到queryFromDatabase()方法,从名字看,也应该很清楚,从 数据库中查询。

 

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.query(stmt, resultHandler);

} finally {

closeStatement(stmt);

}

}

这里到从Configuration(又是这个玩意儿,万能啊)获取StatementHandler,这个对象可以帮助我们处理jdbc中的Statement对象,使用prepareStatement完成参数占位,最后通过StatementHandler的query方法,这个handler的类型RoutingStatementHandler,但是他真正的处理逻辑又委托给了另外一个Handler,这个handler是根据构造传入的mappedStatement的type分别创建的,如下

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

 

switch (ms.getStatementType()) {

case STATEMENT:

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());

}

}

这里是使用PreparedStatementHandler,所以RoutingStatementHandler的query方法又委托给了PreparedStatementHandler的query方法,进入到PreparedStatementHandler的query方法,如下

PreparedStatement ps = (PreparedStatement) statement;

ps.execute();

return resultSetHandler.handleResultSets(ps);

获得可以PrepareStatement对象,完成了参数绑定之后,可以直接执行(这一部分的内容涉及到JDBC的编程 ,大家应该都很清楚了,不作过多的详述)ps.execute()就是通过jdbc真正执行了sql,后面就是对sql执行后的返回集的处理,这个下次再分析吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值