MyBatisr如何模拟生成Mapper代理对象

MyBatis操作数据库使用Mapper接口和XML SQL定义,并没有创建接口实现类,MyBatis如何为Mapper接口生成代理对象的,MyBatis使用JDK proxy或Cglib生成一个代理对象,在org.apache.ibatis.session.SqlSession方法中扫描Mapper和XML完成对数据库操作。

本例通过两种方式演示MyBatis生成代理工作原理,

一是按照MyBatis规范设计Mapper接口和XML定义,自定义代理生成类MapperProxy,使用SqlSession触发方法。

java 复制代码
TestMapper.xml


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hk.mapper.TestMapper">
    <resultMap id="UserMap" type="com.hk.entity.UserInfo">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="login_id" property="loginId"/>
        <result column="pwd" property="pwd"/>
        <result column="version" property="version"/>
        <result column="status" property="status"/>
        <result column="org_name" property="orgName"/>
    </resultMap>
    <select id="getUserList" parameterType="String" resultType="com.hk.entity.UserInfo">
        select u.* from user u
    </select>

</mapper>


TestMapper.java


package com.hk.mybatis;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.hk.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface TestMapper extends BaseMapper<UserInfo> {
    public List<UserInfo> getUserList();
    public List<UserInfo> getUserByName(@Param("name") String name);
}



MapperProxy.java


package com.hk.mybatis;

import org.apache.ibatis.session.SqlSession;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;

    private SqlSession sqlSession;
    private final Class<T> mapperInterface;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) { // Object 提供的toString、hashCode等不需要代理执行
            return method.invoke(this, args);
        } else {
            return sqlSession.selectList(method.getName(), args); // 代理映射最终执行是 SqlSession
        }
    }

}


MapperProxyFactory.java


package com.hk.mybatis;

import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Proxy;
import java.util.Map;

public class MapperProxyFactory<T> {

    private final Class<T> mapperInterface;

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
        // 注意:接口在扫描时通过ClassScanner.scanPackage()加载为了一个Java类
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); // 传入类加载器、接口的类对象,代理目标类,创建 JDK 动态代理对象
    }
}


MapperProxyFactoryTest.java


package com.hk.mybatis;

import com.hk.Starter;
import com.hk.entity.UserInfo;
import com.hk.mapper.UserMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = Starter.class)
public class MapperProxyFactoryTest {
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Test
    public void test_MapperProxyFactory() {
        MapperProxyFactory<TestMapper> factory = new MapperProxyFactory<>(TestMapper.class);
        Map<String, String> xmlMap = new HashMap<>(); // 模拟 SqlSession
        TestMapper userDao = factory.newInstance(sqlSessionFactory.openSession());
        List<UserInfo> list = userDao.getUserList();
        System.out.println(list);
    }
}

二是模拟代理类,自定义SQL操作,绕开SqlSession

java 复制代码
MapperProxy.java


package com.hk.mybatis;

import org.apache.ibatis.session.SqlSession;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;

    private SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private Map<String,String> xmlMap;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) { // Object 提供的toString、hashCode等不需要代理执行
            return method.invoke(this, args);
        } else {
            String sql = xmlMap.get(method.getName());
            return executeSql(sqlSession.getConnection(), sql);

            //return sqlSession.selectList(method.getName(), args); // 代理映射最终执行是 SqlSession
        }
    }

    public Object executeSql(Connection conn, String sql) throws Throwable
    {
        List rtn = new ArrayList();
        Statement stat = conn.createStatement();
        ResultSet rs = stat.executeQuery(sql);
        ResultSetMetaData rsmd = rs.getMetaData();
        int cols = rsmd.getColumnCount();
        while(rs.next())
        {
            Map m = new HashMap();
            for(int i=1;i<=cols;i++)
            {
                String colName = rsmd.getColumnName(i);
                String colVal = rs.getString(colName);
                m.put(colName,colVal);
            }
            rtn.add(m);
        }
        return rtn.toArray();
    }
}


MapperProxyFactory.java


package com.hk.mybatis;

import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Proxy;
import java.util.Map;

public class MapperProxyFactory<T> {

    private final Class<T> mapperInterface;

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public T newInstance(SqlSession sqlSession, Map<String, String> xmlMap) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, xmlMap);
        // 注意:接口在扫描时通过ClassScanner.scanPackage()加载为了一个Java类
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); // 传入类加载器、接口的类对象,代理目标类,创建 JDK 动态代理对象
    }
}


TestMapper.java


package com.hk.mybatis;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.hk.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface TestMapper extends BaseMapper<UserInfo> {
    public Object getUserList();
    public Object getUserByName(@Param("name") String name);
}



MapperProxyFactoryTest.java


package com.hk.mybatis;

import com.hk.Starter;
import com.hk.entity.UserInfo;
import com.hk.mapper.UserMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = Starter.class)
public class MapperProxyFactoryTest {
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Test
    public void test_MapperProxyFactory() {
        MapperProxyFactory<TestMapper> factory = new MapperProxyFactory<>(TestMapper.class);
        Map<String, String> xmlMap = new HashMap<>(); // 模拟 SqlSession
        xmlMap.put("getUserList", "select u.* from user u");
        xmlMap.put("getUserByLoginId", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户信息");
        TestMapper userDao = factory.newInstance(sqlSessionFactory.openSession(),xmlMap);
        Object obj = userDao.getUserList();
        Object[] userDim = (Object[])obj;
        for(int row=0;row<userDim.length;row++)
        {
            Map map = (Map)userDim[row];
            System.out.println(map);
        }
    }
}