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

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

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


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


分析示意图


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

dac2e4a2165049c982d928b9f616f2c6.png


代码实现


  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. 测试效果


2358fda929ab4275965045597b9a2df3.png


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


分析【示意图】


b825b63279f74f95abb6910d776eb13b.png


代码实现


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


分析示意图


aabf70cb86344e0d8a24c254976ecb62.png


代码实现


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、测试效果

数据表没有填充数据,不影响测试

9f252e0fc61245ce9d198321ddff0065.png


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


分析示意图


33ebb24795ed44fdb5bdb02c232127bf.png


代码实现


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、测试效果

5f2dc40f4f7a4bd2b19b022cd15e68cb.png


😄总结


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


文章到这里就结束了,如果有什么疑问的地方请指出,诸大佬们一起来评论区一起讨论😁

希望能和诸大佬们一起努力,今后我们一起观看感谢您的阅读🍻

如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🤞

相关文章
|
SQL 缓存 Java
Mybatis-plus缓存机制
MyBatis-Plus(简称MP)是一个基于MyBatis的增强工具,提供了更便捷的CRUD操作和其他功能。与MyBatis相比,MyBatis-Plus并没有引入自己的缓存机制,而是直接使用了MyBatis的缓存机制。 在MyBatis中,缓存分为一级缓存和二级缓存。 1. 一级缓存:一级缓存是SqlSession级别的缓存,它默认是开启的。当查询操作执行时,查询的结果会被缓存在SqlSession的内部数据结构中。如果后续再次执行相同的查询,MyBatis会先检查一级缓存中是否存在结果,如果存在则直接返回缓存的结果,而不会再次执行SQL语句。一级缓存的生命周期与SqlSession相同,
748 0
|
Java 数据库连接 mybatis
MyBatis 插件机制
插件是一种常见的扩展方式,大多数开源框架也都支持用户通过添加自定义插件的方式来扩展或者改变原有的功能,MyBatis中也提供的有插件,虽然叫插件,但是实际上是通过拦截器(Interceptor)实现的。
58 0
|
缓存 Java 数据库连接
深入浅出 MyBatis 的一级、二级缓存机制
深入浅出 MyBatis 的一级、二级缓存机制
457 0
|
2月前
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
4天前
|
SQL Java 数据库连接
Mybatis架构原理和机制,图文详解版,超详细!
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。本文详细解析了MyBatis的架构原理与机制,帮助读者全面提升对MyBatis的理解和应用能力。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Mybatis架构原理和机制,图文详解版,超详细!
|
5月前
|
SQL 缓存 Java
Java框架之MyBatis 07-动态SQL-缓存机制-逆向工程-分页插件
Java框架之MyBatis 07-动态SQL-缓存机制-逆向工程-分页插件
|
6月前
|
SQL 数据库
MyBatisPlus之逻辑删除、MyBatisPlus解决并发问题的乐观锁机制
MyBatisPlus之逻辑删除、MyBatisPlus解决并发问题的乐观锁机制
83 2
|
6月前
|
XML 缓存 Java
MyBatis二级缓存解密:深入探究缓存机制与应用场景
MyBatis二级缓存解密:深入探究缓存机制与应用场景
462 2
MyBatis二级缓存解密:深入探究缓存机制与应用场景
|
6月前
|
SQL XML Java
MyBatis初探:揭示初始化阶段的核心流程与内部机制
MyBatis初探:揭示初始化阶段的核心流程与内部机制
57 2
MyBatis初探:揭示初始化阶段的核心流程与内部机制
|
6月前
|
SQL Java 数据库连接
深入源码:解密MyBatis数据源设计的精妙机制
深入源码:解密MyBatis数据源设计的精妙机制
107 1
深入源码:解密MyBatis数据源设计的精妙机制