从零实现Mybatis框架(一)

调用一个dao的接口到方法执行

从一个dao下的接口,到调用就能返回数据查询结果中间到底发生了什么?java本身接口是不能实例化的,必须要有具体的类,而一个类要直接使用只能使用其静态方法,要么对其类进行实例化对象。所有有点清楚的概念是,调用dao的接口返回数据,其中一定利用了代理模式。

先定义一个dao接口

public interface IUserDao {
    Integer queryUserAge(String uid);
    String queryUserName(String uid);
}

最简单MapperProxy的实现方式:

public void test_proxy_class(){
        IUserDao userDao = (IUserDao) Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class[]{IUserDao.class},
                (((proxy, method, args) -> "你被代理来了"))
        );

        String result = userDao.queryUserName("");
        logger.info("测试结果:{}" , JSON.toJSONString(result));
    }

对每一个接口进行代理,生成对应的代理对象,代理指定方法。

image-20250413160411337

根据结果来看,成功实现了代理,能够通过一个接口调用其方法,获取结果。

但是目前有个问题,一般来说,我们会有多个dao接口,如果都要一个个自己定义代理对象就会很麻烦,所有我们通过一个MapperProxy工厂,批量来创建MapperProxy。

对于一个MapperProxy来说,它可以是能不同接口的,所以需要一个变量表示当前代理对象的接口。另外我们都知道一个方法应该有对应的sql结果之类的,不能是固定的文本,我们先定义一个Map来模拟这个过程,后续再具体实现。

public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -642454912998906586L;

    // 模拟sqlSession
    // 每个接口的方法对应一个sql执行等
    private Map<String, String> sqlSession;
    // 接口
    private final Class<T> mapperInterface;

    public MapperProxy(Map<String, String> sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 判断是否是Object类中的方法
        if(Object.class.equals(method.getDeclaringClass())){
            return method.invoke(this, args);
        }else {
            // 核心代理
            return "你被代理了: " + sqlSession.get(mapperInterface.getName() + "." + method.getName());
        }
    }
}

对其进行简单工厂封装

public class MapperProxyFactory <T>{

    public T newInstance(Map<String, String> sqlSession, Class<T> mapperInterface) {
        // 创建代理对象
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
        // 进行代理,返回代理对象
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }
}

我们进行调用尝试

 public void test_MapperProxyFactory(){
        MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<>();
        Map<String, String> sqlSession = new HashMap<>();

        sqlSession.put("dao.IUserDao.queryUserName", "模拟执行Mapper.xml的queryUserName结果");
        sqlSession.put("dao.IUserDao.queryUserAge", "模拟执行Mapper.xml的结果");

        IUserDao userDao = factory.newInstance(sqlSession, IUserDao.class);
        String result = userDao.queryUserName("10001");
        logger.info("测试结果:{}" , JSON.toJSONString(result));
    }

执行之后可以看到,我们成功完成了对MapperProxyFactory的实现,不用再一个个实现,只需要传入接口类,和sqlSession就能完成指定dao接口的方法调用。

image-20250413162047200

自动扫描dao和SqlSession

进一步完善上面的,上面的调用接口方法还需要我们硬编码调用工厂,sqlSession之类的,不太方便。我们定义一个MapperRegistry自动扫描dao下面的接口完成MapperProxy,

public class MapperRegistry {
    /*
        将已添加的映射器代理加入HashMap中
     */
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new RuntimeException("Error getting mapper instance. Cause: "+ e, e);
        }
    }

    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
            }
            knownMappers.put(type, new MapperProxyFactory<>(type));
        }
    }

    public  <T> boolean hasMapper(Class<T> type) {
        return knownMappers.containsKey(type);
    }

    public void addMappers(String packageName) {
        Set<Class<?>> mapperSet = ClassScanner.scanPackage(packageName);
        for (Class<?> mapperClass : mapperSet) {
            addMapper(mapperClass);
        }
    }
}

而对于之前的SqlSession,我们定义专门接口,主要是将sql执行方法拆分为两类,不带参数和带参数。在具体的SqlSession类里去实现具体的操作。

public interface SqlSession {

    <T> T selectOne(String statement);

    <T> T selectOne(String statement, Object parameter);
 
    <T> T getMapper(Class<T> type);

}

例如:

public class DefaultSqlSession  implements SqlSession {

    private final MapperRegistry mapperRegistry;

    public DefaultSqlSession(MapperRegistry mapperRegistry) {
        this.mapperRegistry = mapperRegistry;
    }


    @Override
    public <T> T selectOne(String statement) {
        return (T)("你被代理了!" + statement);
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        return (T) ("你被代理了!" + "方法:"+ statement + "入参:" + parameter);
    }

    @Override
    public <T> T getMapper(Class<T> type) {
        return mapperRegistry.getMapper(type, this);
    }
}

这样就是将SqlSession从Map变成的类的方法,方便后续进行扩展,根据指定参数执行具体的sql行为

这次我们在使用的时候,不需要手动定义一个个的MapperProxy和SqlSession,会自动扫描,和自动检测。

 public void test_MapperProxyFactory(){
        // 1. 注册Mapper
        MapperRegistry registry = new MapperRegistry();
        registry.addMappers("dao");
        // 2. 从SqlSession工厂获取SqlSession
        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(registry);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 3. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        // 4. 测试验证
        String result = userDao.queryUserName();
        logger.info("测试结果:{}" , JSON.toJSONString(result));
    }

进行测试,成功输出

image-20250413225209259