框架优化

上节已经实现了部分内容,下面继续。

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

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

SqlSession

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

<T> T getMapper(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 接口的类型

Proxy.newProxyInstance 参数:

  • DefaultSqlSession.class.getClassLoader():指定类加载器
  • new Class[]{mapperClass}:指定代理类需要实现的接口数组
  • new InvocationHandler():传入 InvocationHandler 处理方法调用逻辑

invoke 方法参数:

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

方法调用逻辑

String methodName = method.getName();
if (method.getDeclaringClass() == Object.class) {
    return method.invoke(this, args);
}

获取方法名,检查方法是否是 Object 类的方法(如 toString()、equals() 等)。如果是,直接调用原始实现。

SQL 语句标识符

String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;

拼接方法所在类名和方法名,生成唯一的 SQL 语句标识符。

方法返回类型判断

Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
    List<Object> objects = selectList(statementId, args);
    return objects;
}

如果返回类型是参数化类型(例如 List),则调用 selectList 方法执行查询;否则调用 selectOne。

测试方法

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)