mybatis学习五之源码分析学习1

一、准备工作,环境搭建

在这里插入图片描述
1.1 mybatis 配置

<?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="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="root123"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="xml/TestMapper.xml"/>
    </mappers>
</configuration>

1.2 接口及mapper文件

public interface TestMapper  {
    List<User> findAll();
}


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 指明当前xml对应的Mapper -->
<mapper namespace="com.mybatis.source.mapper.TestMapper">
    <select id="findAll" resultType="com.mybatis.source.pojo.User">
      select * from user
    </select>
</mapper>

1.3 测试类

public class Demo {
    public static void main(String[] args) throws IOException {

       SqlSession sqlSession = getSqlSession();
       TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
        List<User> all = testMapper.findAll();
        all.stream().forEach(System.out::println);
    }

    private static SqlSession getSqlSession() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        return sqlSessionFactory.openSession();
    }
}

1.4 测试运行,可以正常查询数据

User{id='1', name='老王', password='12341234', remark='老王是各好邻居'}
User{id='2', name='老王', password='12341234', remark='老王是各好邻居吗?'}
User{id='3', name='老王', password='@cipher@p8fry4MbKGn39vXEEg2adw==', remark='老王是各好邻居吗?'}
User{id='4', name='老王', password='12341234', remark='老王是各好邻居吗?'}

二、debug跟踪源码
2.1 sqlSession的获取
mybatis在操作数据库前首先会先获取sqlSession,它是mybatis和db沟通的桥梁。sqlSession的获取过程如下。
2.1.1 加载资源文件流。

InputStream inputStream = Resources.getResourceAsStream(resource);

2.1.2 根据文件流获取获取SqlSessionFactory,SqlSessionFactory再生产出SqlSession。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 return sqlSessionFactory.openSession();

2.2 debug
2.2.1 在sqlSession获取mapper处打断点,debug启动。
F7 下一步,会进入方法内部,但是不会进入方法中嵌套的方法。
F8 下一步,不会进入方法
F9 恢复方法运行,或者跳到下一个断点处。
在这里插入图片描述
2.2.2 进入getMapper方法。
SqlSession 是一个接口,F7进入getMapper,进入了DefaultSqlSession方法中,这是sqlSession的默认实现。属性如下定义如下,这里有两个重要对象,包含mybatis配置属性的Configuration对象,和真正操作DB的Executor对象。

public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;
    private Executor executor;
    private boolean autoCommit;
    private boolean dirty;
    
    。。。。。
   
    }

进入了DefaultSqlSession的如下代码,该方法根据传入的泛型返回一个接口对象。具体操作委托给了Configuration对象的getMapper方法。

 public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }

2.2.3 进入Configuration的getMapper方法

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

发现Configuration又把getMapper的事情委托给了MapperRegistry去做了。

2.2.4 查看 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);
            }
        }
    }

    public <T> boolean hasMapper(Class<T> type) {
        return this.knownMappers.containsKey(type);
    }

    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }
            }
        }
    }

    public Collection<Class<?>> getMappers() {
        return Collections.unmodifiableCollection(this.knownMappers.keySet());
    }

    public void addMappers(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
        resolverUtil.find(new IsA(superType), packageName);
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        Iterator i$ = mapperSet.iterator();

        while(i$.hasNext()) {
            Class<?> mapperClass = (Class)i$.next();
            this.addMapper(mapperClass);
        }

    }

    public void addMappers(String packageName) {
        this.addMappers(packageName, Object.class);
    }
}

MapperRegistry 对象有一个Configuration对象属性和一个HashMap属性,key为Class对象,value为一个代理工厂类。还一个getMapper方法和一个addMapper方法。通过对源码的大概浏览及结合mybatis的使用经验,可以推测MapperRegistry 的大概作用为:1、将所有的Mapper文件通过addMapper方法存入一个HashMap中。2、通过getMapper方法从HashMap获取指定类型的Mapper。

2.2.5 进入MapperRegistry的getMapper方法

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

根据2.2.4对源码的查询,可以看出MapperRegistry 对象的上述getMapper方法首先是根据传入的接口类型从HashMapper中取获取MapperProxyFactory代理工厂对象。如果获取不到代理工厂对象则抛出异常。然后使用该代理工厂对象生产出指定泛型的Mapper对象。

2.2.6 继续进入mapperProxyFactory.newInstance(sqlSession)方法
MapperProxyFactory的重要属性如下:

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

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

newInstance(SqlSession sqlSession)首先构建了一个MapperProxy代理对象,然后调用newInstance(MapperProxy mapperProxy)方法通过反射生成了一个T类型对象。
F8,F8…返回到测试类中。至此,sqlSession对象完成了getMapper动作。

2.2.7 继续debug,调用findAll()方法
在这里插入图片描述
重要代码如下:

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

F7 findAll()后进入了MapperProxy代理类对象的invoke()方法中,2.2.6的源码查看中我们也看到了MapperProxy对象会对传入的接口类进行动态代理。invoke()方法会判断传入的方法是否是Object类型的,如果不是则执行cachedMapperMethod方法。在该方法中,首先判断缓存map对象methodCache中是否有该方法了,如果有则直接返回,如果没有则根据传入的参数构建一个MapperMethod 对象返回,并put入methodCache缓存map中。

2.2.8 MapperMethod对象分析
2.2.7 流程执行完后返回了一个MapperMethod对象,F8继续走,F7进入mapperMethod.execute()方法。
MapperMethod方法的部分源码如下,MapperMethod有两个属性SqlCommand和MethodSignature。2.2.7 执行流程中,构建MapperMethod对象调用了其构造函数对其初始化过。execute()方法首先会判断初始化过的SqlCommand 是的type是什么类型:INSERT、UPDATE、DELETE 还是SEELCT类型?F8继续下一步,进入到SELECT判断内,接着就是判断返回值:是否是void,是否有ResultHandler,是否returnMany,是否returnMap。继续F8下一步,进入executeForMany()方法。

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

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

2.2.9 继续F8 进入executeForMany
判断是否需要分页,然后又把查询的任务委托给了sqlSession的selectList()继续查询。

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        Object param = this.method.convertArgsToSqlCommandParam(args);
        List result;
        if (this.method.hasRowBounds()) {
            RowBounds rowBounds = this.method.extractRowBounds(args);
            result = sqlSession.selectList(this.command.getName(), param, rowBounds);
        } else {
            result = sqlSession.selectList(this.command.getName(), param);
        }

        if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
            return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        } else {
            return result;
        }
    }

2.2.10 F7进入sqlSession的默认实现方法DeafaultSession类中
部分源码如下。首先DeafaultSession对象从Configuration中获取到指定的statement,然后DeafaultSession又将任务委托了executor接口对象的query()方法去执行。

    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) {
        List var6;
        try {
            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;
    }

2.2.11 Executor的query()方法
做的三件事情,1、绑定sql参数;2、创建缓存key;3、执行query()方法。

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

2.2.12 进入query方法。
进入的是Executor的实现类BaseExecutor的query()方法。然后调用调用的是queryFromDatabase()方法,该方法又进入了doQuery()方法。后续就是一些类似于JDBC的复杂操作,进行Connection、prepare、resultSet之类的操作,不再跟踪,太复杂了,后面再慢慢学习。

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
        List list;
        try {
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            this.localCache.removeObject(key);
        }

        this.localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            this.localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值