仿Mybatis持久层框架 - mapper代理方式查询流程实现 jdk动态代理 statementId命名规范

前言

最近在学习mybatis源码相关的课程,有一部分是仿Mybatis手写一个持久层框架,一开始是通过传统方式进行测试 每次执行sql 都需要进行如下步骤:

  1. 根据配置文件的路径 加载成字节输入流 存到内存中
  2. 解析配置文件 封装Configuration对象 创建SqlSessionFactory工厂对象
  3. 生产sqlSession 创建执行器对象
  4. 调用sqlSession中的方法
ini 复制代码
public void test() throws DocumentException {
    //1.根据配置文件的路径 加载成字节输入流 存到内存中。此时配置文件还并未解析
    InputStream stream = Resources.getResourceAsStream("sqlMapConfig.xml");

    //2.解析了配置文件 封装了Configuration 创建了SqlSessionFactory工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream);

    //3.生产sqlSession 创建了执行器对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    //4.调用sqlSession中的方法
    User user = new User();
    user.setId(1);
    user.setUsername("tom");
    User user1 = sqlSession.selectOne("user.selectOne", user);
    System.out.println(user1);
}

有两个问题:

  1. 加载、解析配置文件,获取sqlSession代码,存在重复的问题。
  2. 调用sqlSession中方法的时候,statementId存在硬编码问题。

下面通过JDK动态代理,使用代理对象来进行数据操作,规避上述两个问题。

代理方式实现

创建dao层接口

创建dao层的接口,同时在接口中声明查询方法。就等同于在项目开发中的使用方式

csharp 复制代码
public interface IUserDao {
    /**
     * 查询所有数据
     */
    List<User> findAll();

    /**
     * 根据多条件查询数据
     */
    User findByCondition(User user);
}

SqlSession添加getMapper方法

SqlSession接口中 添加getMapper方法 用来获取dao层对象的代理对象

arduino 复制代码
public interface SqlSession {
    //查询多个结果
    <E> List<E> selectList(String statementId, Object param);

    //查询单个结果
    <T> T selectOne(String statementId, Object param);
    
    //释放资源
    void close();

    //生成基于接口的代理对象
    <T> T getMapper(Class<?> mapperClass);
}
typescript 复制代码
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;
    private Executor executor;

    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }

    @Override
    public <E> List<E> selectList(String statementId, Object param) {
        //省略具体查询逻辑...
    }

    @Override
    public <T> T selectOne(String statementId, Object param) {
        //省略具体查询逻辑...
    }

    @Override
    public void close() {
        executor.close();//资源的释放 也委派给底层的执行器
    }
    
    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        //基于JDK动态代理 生成基于接口的代理对象
        Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return null;
            }
        });
        return ((T) proxy);
    }
}

getMapper分析

java 复制代码
public class DefaultSqlSession implements SqlSession {
    //其它代码省略...
    /*
        InvocationHandler参数:
            Object proxy:代理对象的引用,很少用
            Method method:被调用方法的字节码对象
            Object[] args:调用的方法的参数
     */
    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        //基于JDK动态代理 生成基于接口的代理对象
        /*
            InvocationHandler参数:
                Object proxy:代理对象的引用,很少用
                Method method:被调用方法的字节码对象
                Object[] args:调用的方法的参数
         */
        Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //具体逻辑:执行底层JDBC代码 应当通过调用sqlsession中的方法来完成sql执行

                //1、参数的准备:1.statementId(sql语句的唯一标识) 2.param(sql的参数)
                /*
                    sql配置文件中的namespace以及sql语句的id 这里无法获取
                    为了成功拼接statementId,定下一个规范:
                        1.namespace的值 要与接口的全路径一致
                        2.sql语句id的值 要与接口中的方法名一致
                    满足上面的规范 就可以用 接口的全路径 + 方法名 拼接出statementId,找到对应的sql语句
                 */
                String namespace = method.getDeclaringClass().getName();
                String id = method.getName();
                String statementId = namespace + "." + id;

                //2、接下来要调用SqlSession中的哪个方法
                /*
                    MappedStatement中 添加属性 sqlCommandType 来标识本条sql的操作类型
                 */
                MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
                //select update delete insert
                String sqlCommandType = mappedStatement.getSqlCommandType();
                switch (sqlCommandType) {
                    case "select":
                        //3.判断进行查询时 要进行selectList还是selectOne 通过method的返回值类型来判断
                        Type genericReturnType = method.getGenericReturnType();
                        //判断返回值 是否实现了 泛型类型参数化
                        if (genericReturnType instanceof ParameterizedType) {
                            if (args != null) {
                                return selectList(statementId, args[0]);
                            }
                            return selectList(statementId, null);
                        }
                        return selectOne(statementId, args[0]);
                    case "update":
                        break;
                    case "delete":
                        break;
                    case "insert":
                        break;
                }
                return null;
            }
        });
        return ((T) proxy);
    }
}

总结

通过jdk动态代理方式、在执行数据库操作时,就可以调用getMapper方法,传入一个class,获取到接口的代理对象,通过代理对象来执行数据库操作,同时 为了避免这种方式拿不到自定义的statementId,这里还引入了namespace和sql ID的命名规范。

学习过程中,觉得这部分很巧妙,所以在此记录一下。如果有不对的地方,还请各路大佬指正,谢谢各位~

相关推荐
ai小鬼头4 小时前
Ollama+OpenWeb最新版0.42+0.3.35一键安装教程,轻松搞定AI模型部署
后端·架构·github
萧曵 丶5 小时前
Rust 所有权系统:深入浅出指南
开发语言·后端·rust
老任与码5 小时前
Spring AI Alibaba(1)——基本使用
java·人工智能·后端·springaialibaba
华子w9089258596 小时前
基于 SpringBoot+VueJS 的农产品研究报告管理系统设计与实现
vue.js·spring boot·后端
星辰离彬6 小时前
Java 与 MySQL 性能优化:Java应用中MySQL慢SQL诊断与优化实战
java·后端·sql·mysql·性能优化
GetcharZp7 小时前
彻底告别数据焦虑!这款开源神器 RustDesk,让你自建一个比向日葵、ToDesk 更安全的远程桌面
后端·rust
jack_yin8 小时前
Telegram DeepSeek Bot 管理平台 发布啦!
后端
小码编匠9 小时前
C# 上位机开发怎么学?给自动化工程师的建议
后端·c#·.net
库森学长9 小时前
面试官:发生OOM后,JVM还能运行吗?
jvm·后端·面试
转转技术团队9 小时前
二奢仓店的静默打印代理实现
java·后端