mybatis源码介绍(二)

mybatis加载配置文件

mybatis框架执行的第一步就是加载配置文件,下面介绍mybatis是如何加载配置文件的。

 // 1.定义mybatis_config文件地址
String resources = "mybatis_config.xml";
// 2.使用Java api读取mybatis配置文件,获取InputStreamReader Io流
Reader reader = Resources.getResourceAsReader(resources);
// 3.获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

根据上面代码得知,在利用 SqlSessionFactoryBuilder 构建 SqlSessionFactory 工厂的时候,需要将 Reader 对象作为参数 SqlSessionFactoryBuilder().build(reader); 来读取 mybatis 的主配置文件。 Reader reader = Resources.getResourceAsReader(resources); mybatis 有自己的类(Resources)通过配置文件的路径来读取配置文件,最后生成Java中io包中的 java.io.Reader 类。

第一步、通过配置文件路径来读取配置文件
这里直接使用mybatis中的 Resources 类来读取然后获取 java.io.Reader 对象。源码如下:

 // 2.使用Java api读取mybatis配置文件,获取InputStreamReader Io流
Reader reader = Resources.getResourceAsReader(resources);

在这里插入图片描述
第二步、获取SqlSessionFactory对象
SqlSessionFactoryBuilder 读取配置文件的内容来构造 SqlSessionFactory 对象。

具体步骤:见下面代码一步步往下执行。

 // 1.定义mybatis_config文件地址
String resources = "mybatis_config.xml";
// 2.使用Java api读取mybatis配置文件,获取InputStreamReader Io流
Reader reader = Resources.getResourceAsReader(resources);
// 3.获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

SqlSessionFactoryBuilder 类中执行过程
在这里插入图片描述
XMLConfigBuilder 类继承了 BaseBuilder,通过下面的 propertiesElement 方法将配置文件的内容都加载到 BaseBuilder 类中的 Configuration 属性对象中。
注意:parse 方法中的 parsed 变量的作用是只加载一次配置文件。

加载完成之后返回 XMLConfigBuilder 的 parse 方法返回 Configuration 对象,将Configuration 对象作为参数传递给 SqlSessionFactoryBuilder 类用来生成 DefaultSqlSessionFactory 对象。
这里就可以看出:mybatis中SqlSessionFactory接口 默认使用的实现类是 DefaultSqlSessionFactory。
在这里插入图片描述
在这里插入图片描述
这里配置文件就加载完成了,最后返回 DefaultSqlSessionFactory 对象,mybatis 的主配置文件都加载到 Configuration 对象中,最后作为 DefaultSqlSessionFactory 的属性 Configuration 的值,并且是不可修改的。
在这里插入图片描述

MybatisMapper接口绑定原理

Mapper既然是接口,没有被初始化如何被调用的?
答案:使用动态代理技术

根据mapper接口的 class 类型去 configuration 对象中找到注册的 mapper 接口
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
每一个mapper接口对应一个代理对象工厂 mapperProxyFactory 对象,由这个工厂去生产代理对象,这个工厂对象会缓存这个 mapper接口的所有方法。构建mapper 接口代理对象的时候会传入 sqlSession、mapper接口、接口方法缓存参数。
在这里插入图片描述
最后,如果是Object中定义的方法,直接执行。如toString(),hashCode()等。反之,会去methodCache 中查找对应的 MapperMethod 对象,最后去执行方法对应的 sql 语句。
在这里插入图片描述
在这里插入图片描述

大致原理分析:
SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法,但是新版的Mybatis中就会建议使用Mapper接口的方法。

映射器其实就是一个动态代理对象,进入到MapperMethod的execute方法就能简单找到SqlSession的删除、更新、查询、选择方法,从底层实现来说:通过动态代理技术,让接口跑起来,之后采用命令模式,最后还是采用了SqlSession的接口方法(getMapper()方法等到Mapper)执行SQL查询(也就是说Mapper接口方法的实现底层还是采用SqlSession接口方法实现的)。

创建会话对象SqlSession

前面已经得到 SqlSessionFactory 对象了。

SqlSession 的作用

SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的。
SqlSession 因为线程不安全 所以不会共享

在创建sqlSession 对象的时候会指定对应的执行器 Executor 。
在这里插入图片描述
最后会返回 SqlSession 接口默认实现类 DefaultSqlSession 。

通过sqlSession 获取mapper接口

通过sqlSession 获取mapper接口后,执行接口中的方法会使用代理对象去执行对应的方法,

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

MyBatis中通过MapperProxy类实现动态代理,实现了InvocationHandler接口,invoke()方法中为通用的拦截逻辑。当我们调用自己Mapper接口中的方法时,其实就是在调用MapperProxy的invoke()方法。

// MapperProxy.java
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    // Mapper 接口
    private final Class<T> mapperInterface;
    // Mapper 接口中的每个方法都会生成一个MapperMethod对象, methodCache维护着他们的对应关系
    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 {
            // 如果是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);
        }
        // 将 Method 转换成 MapperMethod 并保存到 methodCache 中
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // 这里面将会执行 Mapper接口对应的 SQL
        return mapperMethod.execute(sqlSession, args);
    }
    
    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = methodCache.get(method);
        if (mapperMethod == null) {
            // 通过 Configuration 获取对应的 MappedStatement
            // 通过 MappedStatement 获取 MapperMethod 需要的各种信息
            mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
            methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
    }
}

通过源码得知,最后通过 mapperMethod 对象来执行代理方法,也就是执行对应的 sql 语句。

public class MapperMethod {
    // 里面有两个属性
    // name: 要执行的方法名,如com.example.demo.UserMapper.getUserByUserName
    // type: SQL标签的类型 insert update delete select
    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;
        // 根据insert、update、delete、select调用不同的方法
        switch (command.getType()) {
            case INSERT: {
                /**
                 * args是用户 Mapper 所传递的方法参数列表 
                 * 如果方法只包含一个参数并且不包含命名参数, 则返回传递的参数值。
                 * 如果包含多个参数或包含 @Param 注解修饰的参数,则返回包含名字和对应值的Map对象
                 */
                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);
                }
                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 中的代码得知,数据的增删改查都是通过SqlSession来实现的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值