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

时间:2024-12-12 18:13:40

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

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

在这里插入图片描述

目前已经更新到了:

  • MyBatis(正在更新)

框架实现

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

框架优化

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

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

SqlSession

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

<T> T getMappper(Class<?> mapperClass);

修改完成后,对应的截图如下:
在这里插入图片描述

DefaultSqlSession

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

@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 接口的类型。
Object proxyInstance = Proxy.newProxyInstance(
    DefaultSqlSession.class.getClassLoader(),
    new Class[]{mapperClass},
    new InvocationHandler() { ... }
);

  • Proxy.newProxyInstance:创建动态代理对象。
  • DefaultSqlSession.class.getClassLoader():指定类加载器,用于加载动态生成的代理类。
  • new Class[]{mapperClass}:指定代理类需要实现的接口数组,这里仅实现 mapperClass。
  • new InvocationHandler():传入一个 InvocationHandler,用于处理方法调用逻辑。

动态代理逻辑

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ... }

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

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

方法调用逻辑

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 语句标识符

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

方法返回类型判断

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 等方法来进行查询测试:

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));
    }

}

对应的截图如下所示:
在这里插入图片描述

运行结果

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

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)

对应的截图如下所示:
在这里插入图片描述