自己实现MyBatis 底层机制--抽丝剥茧[下]

😀前言
本篇博文是MyBatis 底层机制的核心实现,简单的手法让MyBatis 不再神秘,希望能够帮助到你😊

🏠个人主页:晨犀主页
🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满意是我的动力😉😉

💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,感谢大家的观看🥰
如果文章有什么需要改进的地方还请大佬不吝赐教 先在次感谢啦😊

自己实现MyBatis 底层机制[下]

实现任务阶段3- 将Sqlsession 封装到执行器

分析示意图

先观察原生MyBatis 的SqlSession 接口和默认实现

代码实现

  1. 创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\NlcSqlsession.java
/**
 * NlcSqlSession: 搭建Configuration(连接)和Executor之间桥梁
 * 这里有操作DB的方法-
 */
public class NlcSqlSession {
    //属性
    //执行器
    private Executor executor = new NlcExecutor();
    //配置
    private NlcConfiguration nlcConfiguration =
            new NlcConfiguration();

    //编写方法SelectOne 返回一条记录-对象 [做了简化]
    //说明: 在原生的Mybatis中 statement 不是sql ,而是要执行的接口方法
    public <T> T selectOne(String statement, Object parameter) {
        return executor.query(statement, parameter);
    }
}

完成测试

  1. 修改nlc-mybatis\src\test\java\com\nlc\test\NlcMybatisTest.java , 增加测试方法
@Test
public void testNlcSqlsesion() {
    NlcSqlsession nlcSqlsession = new NlcSqlsession();
    Monster monster =  nlcSqlsession.selectOne("select * from monster where id=?", 2);
    System.out.println("monster== " + monster);
}
  1. 测试效果

实现任务阶段4- 开发Mapper 接口和Mapper.xml

分析【示意图】

代码实现

1 、创建nlc-mybatis\src\main\java\com\nlc\mapper\MonsterMapper.java

//MonsterMapper: 声明对db的crud方法
public interface MonsterMapper {

    //查询方法
    public Monster getMonsterById(Integer id);
}

2、创建nlc-mybatis\src\main\resources\MonsterMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.nlc.mapper.MonsterMapper">
    <!--实现配置接口方法getMonsterById-->
    <select id="getMonsterById" resultType="com.nlc.entity.Monster">
        select * from monster where id = ?
    </select>
</mapper>

实现任务阶段5- 开发和Mapper 接口相映射的MapperBean

分析示意图

代码实现

1 、创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\config\Function.java , 对应Mapper 的方法信息

// Function: 记录对应的Mapper的方法信息
public class Function {
    //属性
    private String sqlType; //sql类型,比如select,insert,update, delete
    private String funcName; //方法名
    private String sql;//执行的sql语句
    private Object resultType;//返回类型
    private String parameterType;//参数类型

    public String getSqlType() {
        return sqlType;
    }

    public void setSqlType(String sqlType) {
        this.sqlType = sqlType;
    }

    public String getFuncName() {
        return funcName;
    }

    public void setFuncName(String funcName) {
        this.funcName = funcName;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public Object getResultType() {
        return resultType;
    }

    public void setResultType(Object resultType) {
        this.resultType = resultType;
    }

    public String getParameterType() {
        return parameterType;
    }

    public void setParameterType(String parameterType) {
        this.parameterType = parameterType;
    }

    @Override
    public String toString() {
        return "Function{" +
                "sqlType='" + sqlType + '\'' +
                ", funcName='" + funcName + '\'' +
                ", sql='" + sql + '\'' +
                ", resultType=" + resultType +
                ", parameterType='" + parameterType + '\'' +
                '}';
    }
}

2 、创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\config\MapperBean.java, 将Mapper 的信息,进行封装

//MapperBean: 将Mapper信息,进行封装
public class MapperBean {
    private String interfaceName;//接口名

    //接口下的所有方法-集合
    private List<Function> functions;

    public String getInterfaceName() {
        return interfaceName;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public List<Function> getFunctions() {
        return functions;
    }

    public void setFunctions(List<Function> functions) {
        this.functions = functions;
    }
}

实现任务阶段6

任务:在NlcConfiguration, 读取XxxMapper.xml,能够创建MappperBean对象。

运行结果示意

mapperBean--MapperBean{interfaceName='com.nlc.mapper.MonsterMapper',
list=[Function{sqlType='select',
funcName='getMonsterById',
sql='select * from monster where id=?', 
resultType=com.nlc.mapper.MonsterMapper,
parameterType='null'}]}

代码实现

  1. 修改nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\NlcConfiguration.java, 增加方法
  public MapperBean readMapper(String path) {
        MapperBean mapperBean = new MapperBean();
        try {
            //获取到xml文件对应的InputStream
            InputStream stream = loader.getResourceAsStream(path);
            SAXReader reader = new SAXReader();
            //获取到xml文件对应的document-dom4j
            Document document = reader.read(stream);
            //得到xml文件的根元素/根节点
            /*
            <mapper namespace="com.nlc.mapper.MonsterMapper">
                <!--实现配置接口方法getMonsterById-->
                <select id="getMonsterById" resultType="com.nlc.entity.Monster">
                    select * from monster where id = ?
                </select>
            </mapper>
             */
            Element root = document.getRootElement();

            //获取到namespace
            String namespace = root.attributeValue("namespace").trim();
            //设置mapperBean的属性interfaceName
            mapperBean.setInterfaceName(namespace);
            //得到root的迭代器-可以遍历它的子节点/子元素-生成Function
            Iterator rootIterator = root.elementIterator();
            //保存接口下所有的方法信息
            List<Function> list = new ArrayList<>();
            //遍历它的子节点/子元素-生成Function
            while (rootIterator.hasNext()) {
                //取出一个子元素- dom4j Element
                /**
                 * <select id="getMonsterById" resultType="com.nlc.entity.Monster">
                 *                     select * from monster where id = ?
                 *  </select>
                 */
                Element e = (Element) rootIterator.next();
                Function function = new Function();
                String sqlType = e.getName().trim();
                String funcName = e.attributeValue("id").trim();
                //resultType是返回类型的全路径-即全类名
                String resultType = e.attributeValue("resultType").trim();
                String sql = e.getText().trim();
                //开始封装
                function.setSql(sql);
                function.setFuncName(funcName);
                function.setSqlType(sqlType);
                //这里 function-private Object resultType; 是resultType实例
                //使用反射生成一个对象, setResultType

                Object newInstance = Class.forName(resultType).newInstance();
                function.setResultType(newInstance);

                //将封装好的function对象加入到 list
                list.add(function);

            }
            //while循环结束后, 将function的list设置
            mapperBean.setFunctions(list);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return mapperBean;
    }

完成测试

1 、修改nlc-mybatis\src\test\java\com\nlc\test\NlcMybatisTest.java, 增加测试方法

  @Test
    public void readMapper() {

        NlcConfiguration nlcConfiguration = new NlcConfiguration();
        MapperBean mapperBean =
                nlcConfiguration.readMapper("MonsterMapper.xml");
        System.out.println("mapperBean---" + mapperBean);
    }

2、测试效果
数据表没有填充数据,不影响测试

实现任务阶段7- 实现动态代理Mapper 的方法

分析示意图

代码实现

1 、创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\NlcMapperProxy.java

//NlcMapperProxy: 动态代理生成Mapper对象,调用NlcExecutor方法
public class NlcMapperProxy implements InvocationHandler {
    //属性
    private NlcSqlSession nlcSqlSession;
    private String mapperFile;
    private NlcConfiguration nlcConfiguration;

    //构造器
    public NlcMapperProxy(NlcConfiguration nlcConfiguration, NlcSqlSession nlcSqlSession, Class clazz) {
        this.nlcConfiguration = nlcConfiguration;
        this.nlcSqlSession = nlcSqlSession;
        this.mapperFile = clazz.getSimpleName() + ".xml";
    }

    //当执行Mapper接口的代理对象方法时,会执行到invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MapperBean mapperBean =  nlcConfiguration.readMapper(this.mapperFile);
        //判断是否是xml文件对应的接口
        if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())) {
            return null;
        }
        //取出mapperBean的functions
        List<Function> functions = mapperBean.getFunctions();
        //判断当前mapperBean解析对应MappperXML后 , 有方法
        if (null != functions && 0 != functions.size()) {

            for (Function function : functions) {
                //当前要执行的方法和function.getFuncName()一样
                //说明我们可以从当前遍历的function对象中,取出相应的信息sql, 并执行方法
                if(method.getName().equals(function.getFuncName())) {
                    //如果我们当前的function 要执行的sqlType是select
                    //我们就去执行selectOne
                    /**
                     * 1. 如果要执行的方法是select , 就对应执行selectOne
                     * 2. 因为在NlcSqlSession就写了一个 selectOne
                     * 3. 实际上NlcSqlSession 应该对应不同的方法(多个方法)
                     * , 根据不同的匹配情况调用不同方法, 并且还需要进行参数解析处理, 还有比较复杂的字符串处理,拼接sql ,处理返回类型等等工作
                     * 4. 因为主要是mybatis 生成mapper动态代理对象, 调用方法的机制,所以这里做了简化
                     */
                    if("select".equalsIgnoreCase(function.getSqlType())) {
                        return nlcSqlSession.selectOne(function.getSql(),String.valueOf(args[0]));
                    }
                }
            }
        }
        return null;
    }
}

2 、修改nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\NlcSqlsession.java

    /**
        *解读
        * 1. 当调用getMapper(MonsterMapper.class)
        * 2. 这里的T 就是MonsterMapper
        * 3. 所有接收MonsterMapper monsterMapper =   getMapper(MonsterMapper.class)
        * 4. 在程序中,就可以通过monsterMapper.xxx() 方法
        * 5. 因为monsterMapper 的运行类型是代理类型$Proxy
        * 6. 所以当你调用monsterMapper.xxx(实参) 方法时,是一种动态代理执行,
        *也就是会调用到NlcMapperProxy 这个代理类的 invoke(Object proxy, Method method, Object[] args)
        * 7. 这时Method 就是xxx(实参), proxy 就是对象monsterMapper, args 就是你的实参
     */
    public <T> T getMapper(Class<T> clazz) {
        //返回动态代理对象
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz},
                new NlcMapperProxy(nlcConfiguration,this,clazz));
    }

3 、创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\NlcSessionFactory.java

//NlcSessionFactory 会话工厂-返回会话NlcSqlSession
public class NlcSessionFactory {
    public static NlcSqlSession openSession() {
        return new NlcSqlSession();
    }
}

完成测试

1 、修改nlc-mybatis\src\test\java\com\nlc\test\NlcMybatisTest.java, 增加测试方法

    @Test
    public void openSession() {
        NlcSqlSession nlcSqlSession = NlcSessionFactory.openSession();
        MonsterMapper mapper = nlcSqlSession.getMapper(MonsterMapper.class);
        Monster monster = mapper.getMonsterById(1);
        System.out.println("monster===" + monster);
    }

2、测试效果

😄总结

  1. 很多时候事务的本身并不是可怕的,而是我们自身对于未知事务的恐惧把它扩大了。
  2. MyBatis也是同样,它只是人类创建出来提高效率的工具,他的能力取决于使用的人。
  3. 去学习它、了解它,你会发现没有那么多的未知,得到的都是惊喜。
  4. MyBatis是一款优秀的持久层框架,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集,节省了原生90%的代码。

😁热门专栏推荐
MyBatis手写机制篇–自己实现MyBatis 底层机制–抽丝剥茧(上)

文章到这里就结束了,如果有什么疑问的地方请指出,诸大佬们一起来评论区一起讨论😁
希望能和诸大佬们一起努力,今后我们一起观看感谢您的阅读🍻
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🤞

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晨犀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值