如何开发一个mybatis扩展框架

都说官网是最好的入门。当你参考mybatis官网执行查询发现这样:

// try 执行完后会关闭 session 
try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

如果你不关闭 SqlSession 会发现,执行几个后,连接池将会耗尽,因为你未关闭连接。

前提可参考这篇文章:https://lingkang.top/archives/tong-guo-dai-ma-jia-zai-mybatis-de-mapperxml

如果你要开发一个 mybatis 扩展框架,如何能反复调用呢?

如果你要开发一个 mybatis 扩展框架,如何能反复调用呢?
我们可以参考 mybatis-spring 框架源码,mybatis-spring 的SqlSessionTemplate 类实现了 mybatis 的 SqlSession 接口,在它构造函数中初始化了 SqlSessionFactory 代理处理 SqlSessionInterceptor

image-1709129296763

再查看代理实现 SqlSessionInterceptor.invoke,发现它会在执行完毕后关闭连接

image-1709129357963

基于代理 SqlSessionFactory 开发一个框架

基于这篇文章搭建的环境:https://lingkang.top/archives/tong-guo-dai-ma-jia-zai-mybatis-de-mapperxml

编写一个 SqlSession 代理处理

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.session.*;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

/**
 * @Author lingkang
 * @Date 2024/2/29 9:25
 */
@Slf4j
public class MySqlSession implements SqlSession {
    private SqlSessionFactory sqlSessionFactory;
    private ExecutorType executorType;
    private SqlSession sessionProxy;


    public MySqlSession(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;

        // 创建我们的代理,以扩展更多功能
        sessionProxy = (SqlSession) Proxy.newProxyInstance(getClass()
                        .getClassLoader(),
                new Class[]{SqlSession.class},
                new SqlSessionProxy()
        );
    }

    @Override
    public <T> T selectOne(String statement) {
        return sessionProxy.selectOne(statement);
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        return sessionProxy.selectOne(statement, parameter);
    }

    @Override
    public <E> List<E> selectList(String statement) {
        return sessionProxy.selectList(statement);
    }

    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        return sessionProxy.selectList(statement, parameter);
    }

    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        return sessionProxy.selectList(statement, parameter, rowBounds);
    }

    @Override
    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
        return sessionProxy.selectMap(statement, mapKey);
    }

    @Override
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
        return sessionProxy.selectMap(statement, mapKey, mapKey);
    }

    @Override
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
        return sessionProxy.selectMap(statement, mapKey, mapKey, rowBounds);
    }

    @Override
    public <T> Cursor<T> selectCursor(String statement) {
        return sessionProxy.selectCursor(statement);
    }

    @Override
    public <T> Cursor<T> selectCursor(String statement, Object parameter) {
        return sessionProxy.selectCursor(statement, parameter);
    }

    @Override
    public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
        return sessionProxy.selectCursor(statement);
    }

    @Override
    public void select(String statement, Object parameter, ResultHandler handler) {
        sessionProxy.select(statement, parameter, handler);
    }

    @Override
    public void select(String statement, ResultHandler handler) {
        sessionProxy.select(statement, handler);
    }

    @Override
    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        sessionProxy.select(statement, parameter, rowBounds, handler);
    }

    @Override
    public int insert(String statement) {
        return sessionProxy.insert(statement);
    }

    @Override
    public int insert(String statement, Object parameter) {
        return sessionProxy.insert(statement, parameter);
    }

    @Override
    public int update(String statement) {
        return sessionProxy.update(statement);
    }

    @Override
    public int update(String statement, Object parameter) {
        return sessionProxy.update(statement, parameter);
    }

    @Override
    public int delete(String statement) {
        return sessionProxy.delete(statement);
    }

    @Override
    public int delete(String statement, Object parameter) {
        return sessionProxy.delete(statement, parameter);
    }

    @Override
    public void commit() {
        throw new UnsupportedOperationException("不支持的方法,已经交由 final-magic 管理事务");
    }

    @Override
    public void commit(boolean force) {
        throw new UnsupportedOperationException("不支持的方法,已经交由 final-magic 管理事务");
    }

    @Override
    public void rollback() {
        throw new UnsupportedOperationException("不支持的方法,已经交由 final-magic 管理事务");
    }

    @Override
    public void rollback(boolean force) {
        throw new UnsupportedOperationException("不支持的方法,已经交由 final-magic 管理事务");
    }

    @Override
    public List<BatchResult> flushStatements() {
        return sessionProxy.flushStatements();
    }

    @Override
    public void close() {
        throw new UnsupportedOperationException("不支持的方法,已经交由 final-magic 管理连接");
    }

    @Override
    public void clearCache() {
        sessionProxy.clearCache();
    }

    @Override
    public Configuration getConfiguration() {
        // 从会话工厂中获取
        return sqlSessionFactory.getConfiguration();
    }

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

    @Override
    public Connection getConnection() {
        return sessionProxy.getConnection();
    }

    private class SqlSessionProxy implements InvocationHandler {
        private int countNumber = 0;

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 例如创建时将 session 存储到线程变量中,线程处理结束就关闭此会话
            SqlSession session = sqlSessionFactory.openSession(executorType);
            countNumber++;
            log.info("获取会话");
            System.out.println("获取连接: " + countNumber);
            // 如果存在事务时,开启事务
            // session.getConnection().setAutoCommit(false);
            Object result = null;
            try {
                result = method.invoke(session, args);
                // 事务处理 如果不存在事务就提交操作
                // AOP 结束、 请求处理结束、提交事务
            } catch (Exception e) {
                // 事务处理 如果存在事务就要回滚
                // session.rollback();
                // log.info("事务回滚");
                throw new RuntimeException(e);
            } finally {
                // 判断是否存在事务,不存在就关闭连接
                session.close();
                countNumber--;
                System.out.println("关闭连接: " + countNumber);
            }
            return result;

        }
    }
}

调用

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import top.lingkang.solonweb.mapper.UserMapper;

import java.io.InputStream;

/**
 * @author lingkang
 * Created by 2024/2/29
 */
@Slf4j
public class Demo01 {

    public static boolean isOk = false;

    public static void main(String[] args) throws Exception {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/test?useSSL=true&serverTimezone=UTC");
        config.setUsername("root");
        config.setPassword("123456");
        config.setMaximumPoolSize(3);
        HikariDataSource dataSource = new HikariDataSource(config);

        Configuration configuration = new Configuration();

        // 用for循环加载所有 xml

        InputStream inputStream = Resources.getResourceAsStream("mapper/UserMapper.xml");
        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, "mapper/UserMapper.xml",
                configuration.getSqlFragments());
        mapperParser.parse();


        // 配置
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        Environment environment = new Environment("dev", transactionFactory, dataSource);
        configuration.setEnvironment(environment);
        configuration.setMapUnderscoreToCamelCase(true);// 下划线转化驼峰

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);


        // 模拟aop生命周期,session可以存储到线程变量中,线程处理结束就关闭此会话
        MySqlSession session = new MySqlSession(sqlSessionFactory, null);
        UserMapper mapper = session.getMapper(UserMapper.class);
        System.out.println(mapper.selectAll());// 查询所有
        System.out.println(mapper.selectOne());
        System.out.println(mapper.selectOne());
        System.out.println(mapper.selectOne());
        System.out.println(mapper.selectOne());
    }

}

输出如下

image-1709180131701