手写版mybatis

323 阅读6分钟

手写mybaits简单版本梳理:

mybatis功能,

初始化:

1.读取config.xml 配置文件,解析为confguration对象 2.读取mapper.xml 配置文件 把每一个sql 节点(insert|delete update |select) 解析为mappedStatement 对象,注入到configuration对象一个属性变量中存储

书写版就只是 配置了一个sql.properties文件,在文件里直接配置mapper接口的全路径+对应的方法名作为一个statementId去获取对应的id,也不用配置mapper.xml文件去里面写sql语句了,只是把sql语句抽出来,放到配置文件里去,省去解析mapper.xml文件的步骤,这样做主要是为了了解精髓思想

edu.yuz.yubatis.v1.mapper.BlogMapper.selectBlogById = select * from blog where bid = %d

3.创建mapper接口,mapper接口中有对应的方法和查询的sql参数

public interface BlogMapper {
    /**
     * 根据主键查询文章
     * @param bid
     * @return
     */
    public Blog selectBlogById(Integer bid);
}

mapper接口和方法包含的信息有: 1.全路径 对应mapperf映射文件的namespace, 方法名对应映射文件的sql节点,全路径+方法名 作为mappedstatementid获取到对应的mappedstatement对象,里面封装了一个sql节点的sql语句,sql查询配置 2. 方法参数 也就是查询条件, 方法的返回类型也就是查询返回的类型

  1. 创建一个对用户开放的api 命名为sqlSession 里面封装了增删改查 方法,再创建一个真正的执行器命名为Executor,由Executor真正的去执行一个sql语句的操作,比如获取connection 连接,创建prepastatment 对象,执行查询,对返回ResultSet进行映射为pojo对象 (第4步创建 sqlSession 对象和 Executor对象)
public class YuSqlSession {
//执行器里得知道怎么通过传入的statementId 获取对应的查询sql信息,所以需要有configuration//对象的引用
    private YuConfiguration yuConfiguration;
// 真正执行jdbc操作的对象
    private YuExecutor yuExcutor;

    public YuSqlSession(YuConfiguration yuConfiguration, YuExecutor yuExcutor) {
        this.yuConfiguration = yuConfiguration;
        this.yuExcutor = yuExcutor;
    }

    public <T> T selectOne(String statementId, Object params) {
        String sql = yuConfiguration.getSqlString(statementId);
        // 由Excutor去执行具体的sql
        return yuExcutor.quereyOne(sql, params);
    }

    // 获取mapper对象 在这个方法中对mapper接口生成代理对象,在invocationHandler对象中 获取statementId 调用sqlSession的方法
    // 接口和方法的作用就是作为一个staementId 和parmas参数的传递桥梁作用
    public <T> T getMapper(Class<T> clazz) {
        return yuConfiguration.getMapper(clazz,this);
    }
}

5.用户只是通过一个接口还有一个方法,就可以得到对应的实例,这个肯定是通过了动态代理去做的,获取接口实例的方法是下面这样调用:

YuSqlSession yuSqlSession = new YuSqlSession(new YuConfiguration(), new YuExecutor());
        BlogMapper blogMapper = yuSqlSession.getMapper(BlogMapper.class);

所以一定是在这个方法getMapper(BlogMapper.class)中对传入的接口生成动态代理对象

具体怎么生成呢

public class YuConfiguration {
    private static final ResourceBundle RESOURCE_BUNDLE;

    static {
        RESOURCE_BUNDLE = ResourceBundle.getBundle("sql_v1");
    }

    public String getSqlString(String statementId) {
        return RESOURCE_BUNDLE.getString(statementId);
    }
// 可以在configuration对象中创建一个方法,在方法中传入需要被代理的接口,还要实现了invocationHandler接口的触发管理类,这样就可创建一个代理对象了

    public <T> T getMapper(Class<T> clazz, YuSqlSession yuSqlSession) {
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader()
                ,new Class[]{clazz}
                ,new YuMapperProxy(yuSqlSession));
    }
}

6.那么代理对象创建了,具体的调用代码在那里执行呢,怎么把方法的参数值还要代理的类传过去呢(这个和代理的知识有关)在触发管理类里面的invoke方法里,执行sqlSession的增删改查的方法

public class YuMapperProxy implements InvocationHandler {
    private YuSqlSession yuSqlSession;

    public YuMapperProxy(YuSqlSession yuSqlSession) {
        this.yuSqlSession = yuSqlSession;
    }

    // 在生成的代理类中 会调用 h.invoke() 把接口的方法和参数传到里面进行调用 method 调用的接口方法,objects传入的参数
    // Object o 传入的代理对象
// 在这个invoke方法中就可以通过持有的sqlSession去执行相关的操作了
// 这样的代理和常规的代理不太一样,常规的代理是在invoke方法中执行 被代理类的方法,同时前后执行增强代码方法
// mybatis的代理,接口都没用实现的子类,怎么去调用被代理的实例呢,其他他压根不用调用,因为mybatis中接口的作用就相当于一个配置类,告诉sqlSession,这个方法的对应namespace\和对应sql ,找到对应的mappendstament去进行代码织入,然后在生成动态代理类后,在调用的时候把参数传进去就可以把参数传给执行器进行调用了
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// 代理对象的方法名    
    String interFaceName = method.getDeclaringClass().getName();
        String methodName = method.getName();
        String statementId = interFaceName + "." + methodName;
        return yuSqlSession.selectOne(statementId, objects[0]);
    }
}

7.针对怎么把方法的参数值还有代理的类传过去进行代理生成这个做个解答: 7.1 怎么把被代理类传过去的 代码如下:

// 创建sqlSession对象,把接口名称传进去输出代理对象,这个方法就是入口
YuSqlSession yuSqlSession = new YuSqlSession(new YuConfiguration(), new YuExecutor());
        BlogMapper blogMapper = yuSqlSession.getMapper(BlogMapper.class);

// 然后在 sqlSession对象中这个方法的代码如下
public <T> T getMapper(Class<T> clazz) {
        return yuConfiguration.getMapper(clazz,this);

    }
// 真正创建代理对象的在这个方法中, clazz就是传入的接口名称了,有了被代理的接口,有了在代理类中需要被执行的增强代码(jdbc操作) 这样就可以生成代理类了
  public <T> T getMapper(Class<T> clazz, YuSqlSession yuSqlSession) {
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader()
                ,new Class[]{clazz}
                ,new YuMapperProxy(yuSqlSession));
    }
// 传教触发管理类

public class YuMapperProxy implements InvocationHandler {
    private YuSqlSession yuSqlSession;

    public YuMapperProxy(YuSqlSession yuSqlSession) {
        this.yuSqlSession = yuSqlSession;
    }

    // 在生成的代理类中 会调用 h.invoke() 把接口的方法和参数传到里面进行调用 method 调用的接口方法,objects传入的参数
    // Object o 传入的代理对象
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        String interFaceName = method.getDeclaringClass().getName();
        String methodName = method.getName();
        String statementId = interFaceName + "." + methodName;
        return yuSqlSession.selectOne(statementId, objects[0]);
    }
}


///那你又想问怎么知道调用的方法名,这个和代理知识有关
JDK proxy代理类结构是大体这样的

final $proxy extends Proxy implements interface(被代理类接口){
InvocationHander h;
// 被代理方法a
@override
methodA(params){
h.invoke(this.methodA,new Object[]{params})
}
// 被代理方法b
@override
methodB(params){
h.invoke(this.methodB,new Object[]{params})
}
// 被代理方法c
@override
methodC(params){
h.invoke(this.methodC,new Object[]{params})
}
} 
// 看到这里就明白了吧 invoke方法类似一个委托模式,方法的参数在调用的时候传入, 方法对象实例也是你调用的代理实例类的那个方法,就传那个方法,
这样方法名称、参数名称就都有了吧
而且每个接口对应一个代理类,每个代理类里的executor在都是独立的,

总结

要实现这个简易版的手写版mybatis的几个关键类 1.configuration 配置类 加载时先把配置信息解析放到配置类里 2.executor 执行器 底层执行jdbc操作的类 3.sqlSession 对用户提供的api 提供了select 、update 、delete 、insert 方法,在该方法里通过调用执行器去执行,为什么不直接在sqlsession里操作jdbc代码呢,也是为了方便扩展 还要解耦,表示层和执行层分开 4.MapperProxy怎么实现mapper接口的代理呢,通过一个实现了InvocationHandler类的代理类MapperProxy在invoke方法中执行通过被代理接口、和调用的方法 拼接statementId参数,得到对应的执行sql,调用已经初始化的sqlSession对象方法 在sqlSession方法中通过执行器executor去执行jdbc代码,然后进行参数解析 resultset结果的映射处理 5.还有配置类