Java-23 深入浅出 MyBatis - 手写ORM框架4 框架优化 SqlSession DefaultSqlSession

点一下关注吧!!!非常感谢!!持续更新!!!

大数据篇正在更新!https://blog.csdn.net/w776341482/category_12713819.html

目前已经更新到了:

  • MyBatis(正在更新)

框架实现

上节已经实现了部分内容 下面我们继续

框架优化

上述我们编写了自定义的框架,我们解决了 JDBC 带来的一系列的问题,但是目前也出现了一些问题:

  • DAO 的实现类存在重复的代码,整过操作模板重复,创建 SqlSession等等
  • DAO 的实现类中有硬编码,调用 SqlSession 的方法时,参数 Statement的 ID 硬编码。

SqlSession

解决:使用代理模式来创建接口的代理对象,我们在 SqlSession 中加入新的方法:getMapper,我们修改 SqlSession

java 复制代码
<T> T getMappper(Class<?> mapperClass);

修改完成后,对应的截图如下:

DefaultSqlSession

我们在实现类中进行实现刚才的 getMapper 的方法:

java 复制代码
@Override
public <T> T getMapper(Class<?> mapperClass) {
    Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            if (method.getDeclaringClass() == Object.class) {
                return method.invoke(this, args);
            }
            String className = method.getDeclaringClass().getName();
            String statementId = className + "." + methodName;
            Type genericReturnType = method.getGenericReturnType();
            if(genericReturnType instanceof ParameterizedType){
                List<Object> objects = selectList(statementId, args);
                return objects;
            }
            return selectOne(statementId,args);

        }
    });
    return (T) proxyInstance;
}

对应的截图如下所示:

  • Override:表示该方法是对父类或接口方法的重写。
  • T:定义了一个泛型方法,T 是返回值的类型,占位符在调用时会被具体类型替换。
  • getMapper(Class<?> mapperClass):方法接收一个 Class 类型参数,表示需要生成的 Mapper 接口的类型。
java 复制代码
Object proxyInstance = Proxy.newProxyInstance(
    DefaultSqlSession.class.getClassLoader(),
    new Class[]{mapperClass},
    new InvocationHandler() { ... }
);
  • Proxy.newProxyInstance:创建动态代理对象。
  • DefaultSqlSession.class.getClassLoader():指定类加载器,用于加载动态生成的代理类。
  • new Class[]{mapperClass}:指定代理类需要实现的接口数组,这里仅实现 mapperClass。
  • new InvocationHandler():传入一个 InvocationHandler,用于处理方法调用逻辑。

动态代理逻辑

java 复制代码
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ... }

invoke 方法:当代理对象调用任何方法时,都会触发此方法。

参数:

  • Object proxy:代理对象本身。
  • Method method:当前被调用的方法对象。
  • Object[] args:调用方法时传递的参数。

方法调用逻辑

java 复制代码
String methodName = method.getName();
if (method.getDeclaringClass() == Object.class) {
    return method.invoke(this, args);
}
  • 获取方法名:通过 method.getName() 获取被调用的方法名。
  • if 判断:检查方法是否是 Object 类的方法(如 toString()、equals() 等)。如果是,直接调用原始实现,通过 method.invoke(this, args) 执行。

SQL 语句标识符

shell 复制代码
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
  • className:获取方法所在类的全限定名。
  • statementId:拼接方法所在类名和方法名,生成唯一的 SQL 语句标识符,用于在 Mapper 中定位具体的 SQL 语句。

方法返回类型判断

java 复制代码
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
    List<Object> objects = selectList(statementId, args);
    return objects;
}
  • genericReturnType:获取方法的返回值类型。
  • instanceof 检查:如果返回类型是参数化类型(例如 List),则调用 selectList 方法执行查询。
  • selectList(statementId, args):模拟批量查询方法,传入 statementId 和参数 args。

这段代码实现了一个动态代理机制,用于生成符合特定 Mapper 接口(mapperClass)的动态代理对象。

代理对象的方法调用逻辑包括以下几部分:

  • 检查方法是否属于 Object,如果是则直接调用原始实现。
  • 根据方法名和类名生成唯一的 statementId,用于定位 SQL 语句。
  • 根据返回值类型决定调用批量查询(selectList)还是单结果查询(selectOne)。
  • 返回查询结果。

测试方法

我们编写一个方法,来通过 SqlSession的 openSession、getMapper 等方法来进行查询测试:

java 复制代码
package icu.wzk.test;

import icu.wzk.bean.Resources;
import icu.wzk.bean.SqlSession;
import icu.wzk.bean.SqlSessionFactory;
import icu.wzk.bean.SqlSessionFactoryBuilder;
import icu.wzk.dao.UserInfoMapper;
import icu.wzk.model.UserInfo;

import java.io.InputStream;


public class Test02 {

    public static void main(String[] args) throws Exception {
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("wzk");
        UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
        System.out.println("userInfoMapper: " + userInfoMapper);
        System.out.println(userInfoMapper.selectOne(userInfo));
    }

}

对应的截图如下所示:

运行结果

执行之后,控制台输出的结果如下所示:

shell 复制代码
log4j:WARN No appenders could be found for logger (com.mchange.v2.log.MLog).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
userInfoMapper: icu.wzk.bean.DefaultSqlSession$1@61dc03ce
SimpleExecutor getBoundSql: SELECT * FROM user_info WHERE username=?
UserInfo(id=1, username=wzk, password=icu, age=18)

对应的截图如下所示:

相关推荐
扛麻袋的少年39 分钟前
7.Kotlin的日期类
开发语言·微信·kotlin
tuokuac40 分钟前
maven与maven-archetype-plugin版本匹配问题
java·maven
麻辣清汤1 小时前
结合BI多维度异常分析(日期-> 商家/渠道->日期(商家/渠道))
数据库·python·sql·finebi
ankleless1 小时前
Spring Boot 实战:从项目搭建到部署优化
java·spring boot·后端
钢铁男儿1 小时前
Python 正则表达式实战:解析系统登录与进程信息
开发语言·python·正则表达式
野生技术架构师2 小时前
2025年中高级后端开发Java岗八股文最新开源
java·开发语言
静若繁花_jingjing2 小时前
JVM常量池
java·开发语言·jvm
David爱编程2 小时前
为什么线程不是越多越好?一文讲透上下文切换成本
java·后端
彷徨而立3 小时前
【C++】 using声明 与 using指示
开发语言·c++
A尘埃3 小时前
Redis在地理空间数据+实时数据分析中的具体应用场景
java·redis