Mybatis源码分析(2)

Mybatis源码分析(2)

Mybatis源码分析(1)

接上一章总结【从哪里开始?】

1、SqlSessionManager

实现至SqlSession

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

  private final SqlSessionFactory sqlSessionFactory;
  private final SqlSession sqlSessionProxy;

  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
}

代码中容易看到:SqlSessionManager管理器继承自SqlSession,里面维护了自己的一个SqlSessionFactory 和一个SqlSession的代理类型(源码可以看出是jdk代理模式)以及一个当前线程可见全局的localSqlSession

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

selectOne为例我们能看到这是通过代理来完成的sql查询操作,那么这个代理是做了什么增强呢?

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
  if (sqlSession != null) {
    try {
      return method.invoke(sqlSession, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  } else {
    try (SqlSession autoSqlSession = openSession()) {
      try {
        final Object result = method.invoke(autoSqlSession, args);
        autoSqlSession.commit();
        return result;
      } catch (Throwable t) {
        autoSqlSession.rollback();
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }
}

我们可以看到这个代理,为我们做了如下几件事:

  • 获取本地的SqlSession
  • 如果本地有,并执行方法且不加增强
  • 如果本地没有,用当前管理器的工厂创建一个新的SqlSession并自动提交或回滚

2、DefaultSqlSession

同上,也实现至SqlSession

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;// 配置信息,也是生成Mapper代理类的方式
  private final Executor executor;// 内部封装的执行器,用来真正执行操作

  private final boolean autoCommit;// 暂不解释
  private boolean dirty;// 暂不解释
  private List<Cursor<?>> cursorList;// 暂不解释

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }
}

4、SqlSession是如何被创建出来的?

到目前为止我们只是粗略的讲解了SqlSession以及关联的类的一些作用,那么这些类中的字段等信息是如何生成并运作的呢?

我们上章节中可以看出SqlSession是通过工厂模式创建出来的,里面都做了哪些事情,我们来深入解刨一下这个SqlSession的作用

// DefaultSqlSessionFactory 中生成SqlSession的底层方法
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
  	// 环境信息
    final Environment environment = configuration.getEnvironment();
  	// 事务工厂
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  	// 事务(维护连接的一个封装)
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  	// 执行器(发放给将要创建的SqlSession,作为执行操作的核心)
    final Executor executor = configuration.newExecutor(tx, execType);
    // 生成SqlSession实例
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

我们只看方法名字可以看出,我们将要从DataSource中创建出来SqlSession,但不巧的是,并没有数据源参数,我们的数据源从代码里可以看出来是从Environment中来,我们从源码上其实可以看出环境又是从configuration来的,配置类是抽象工厂(SqlSessionFactoryBuilder)从一开始根据xml配置能解析出来的所有的信息(里面内容非常的齐全),并通过DefaultSqlSessionFactory构造函数注入到了实例中,而我们知道了配置类的由来,那么Environment在Mybatis中又扮演者什么角色呢?

public final class Environment {
  private final String id;
	// 事务工厂
  private final TransactionFactory transactionFactory;
  	// 数据源
  private final DataSource dataSource;

  public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
    if (id == null) {
      throw new IllegalArgumentException("Parameter 'id' must not be null");
    }
    if (transactionFactory == null) {
      throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
    }
    this.id = id;
    if (dataSource == null) {
      throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
    }
    this.transactionFactory = transactionFactory;
    this.dataSource = dataSource;
  }
 }

从代码中可以看出,Environment中可以取出事务工厂用来创建事务,也可以获取到真正的数据源,而这个类,是在XML解析的时候已经构造好的,我们接下来翻看一下事务类是做什么的

public interface Transaction {
  // 获取连接
	Connection getConnection() throws SQLException;
  // 连接提交
	void commit() throws SQLException;
  // 连接回滚
	void rollback() throws SQLException;
  // 连接关闭
	void close() throws SQLException;
  // 获取连接超时时间
	Integer getTimeout() throws SQLException;
}

从方法上看,我们的事务其实就是对连接的一个封装,为了对事物有更深的体会,我们看一下下面两者的区别:

  • Common

    package com.lsb.spring.boot.mybatisplus.源码解析.操作数据库;
    
    import org.apache.ibatis.transaction.Transaction;
    import org.apache.ibatis.transaction.jdbc.JdbcTransaction;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * @author lishanbiao
     * @date 2022/6/6 5:06 下午
     */
    public class Common {
        private static final String DRIVER = "com.mysql.jdbc.Driver";
    
        private static final String URL = "jdbc:mysql://127.0.0.1:3306/mydb2";
    
        private static final String USER = "root";
    
        private static final String PASSWORD = "root";
    
        public static final String SQL = "insert into user(name,gender,salary) values(?,?,?)";
    
        public static Transaction getTransaction() throws SQLException, ClassNotFoundException {
            Connection conn = getConnection();
            Transaction transaction = new JdbcTransaction(conn);
            return transaction;
        }
    
        public static Connection getConnection() throws ClassNotFoundException, SQLException {
            // 注册数据库驱动
            Class.forName(DRIVER);
            Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
            // 获取连接
            conn.setAutoCommit(false);
            return conn;
        }
    
        public static void printResult(ResultSet rs) throws SQLException {
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String gender = rs.getString("gender");
                float salary = rs.getFloat("salary");
                System.out.println(id + ":" + name + ":" + gender + ":" + salary);
            }
        }
    }
    
  • 原生JDBC

package com.lsb.spring.boot.mybatisplus.源码解析.操作数据库.原生jdbc;


import com.lsb.spring.boot.mybatisplus.源码解析.操作数据库.Common;

import java.sql.*;

/**
 * @author lishanbiao
 * @date 2022/6/6 4:42 下午
 */
public class JDBCTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        Connection conn = Common.getConnection();
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            // 创建预编译方式
            pstmt = conn.prepareStatement(Common.SQL);
            // 设置参数位置和参数数据
            pstmt.setString(1, "xiaozheng");
            pstmt.setString(2, "男");
            pstmt.setFloat(3, 8000);
            //进行编译
            rs = pstmt.executeQuery();
            conn.commit();
            Common.printResult(rs);
        } catch (Exception e) {
            try {
                conn.rollback();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            if (rs != null) {//轻量级,创建和销毁rs所需要的时间和资源较小
                try {
                    rs.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (pstmt != null) {//轻量级,创建和销毁rs所需要的时间和资源较小
                try {
                    pstmt.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {//重量级,创建和销毁rs所需要的时间和资源较小
                try {
                    conn.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 事务封装的JDBC
package com.lsb.spring.boot.mybatisplus.源码解析.操作数据库.事务Jdbc;

import com.lsb.spring.boot.mybatisplus.源码解析.操作数据库.Common;
import org.apache.ibatis.transaction.Transaction;
import java.sql.*;

/**
 * 对比原生的jdbc连接,我们可以明显感觉到,Mybatis的事务类(Transaction)是用来管理连接的
 * 包括创建、获取、提交、回滚、关闭等操作
 *
 * 但是Transaction仅仅是用来创建并封装连接的,并非用来自动管理的连接的
 *
 * @author lishanbiao
 * @date 2022/6/6 4:59 下午
 */
public class JDBCTransactionTest {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        Transaction transaction = Common.getTransaction();
        Connection conn = transaction.getConnection();
        try {
            // 创建预编译方式
            pstmt = conn.prepareStatement(Common.SQL);
            // 设置参数位置和参数数据
            pstmt.setString(1, "xiaozheng");
            pstmt.setString(2, "男");
            pstmt.setFloat(3, 8000);
            //进行编译
            rs = pstmt.executeQuery();
            transaction.commit();
            Common.printResult(rs);
        } catch (Exception e) {
            transaction.rollback();
            e.printStackTrace();
        } finally {
            if (rs != null) {//轻量级,创建和销毁rs所需要的时间和资源较小
                try {
                    rs.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (pstmt != null) {//轻量级,创建和销毁rs所需要的时间和资源较小
                try {
                    pstmt.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            transaction.close();
        }
    }
}

所以一句话总结事务:Transaction仅仅是用来创建并封装连接的,并非用来自动管理的连接的,事务即连接

好的,我们知道事务之后,再回过头来看一下DefaultSqlSession是不是有更深的理解了呢?(好吧,我承认是真的乱,我也刚看源码,后续有时间会整理的)

总结一下吧

  • 事务即连接,之所以将连接用事务包装起来,我想应该是,不同的事务管理,会针对连接做不同的操作和状态更改,逻辑不同,但其实底层真正的还是用连接操作数据库的喔
  • SqlSession内部维护一个已加载解析的配置类,理论上是已经获取了几乎所有的信息(事务、执行器类型、提交状态等等)
  • SqlSession内部维护的Executor是底层真正执行操作的类
  • SqlSession总结下来就是拥有我们原生连接Connectionstatement的所有信息、状态以及操作

4、Executor继承结构图(下章解释)

Executor 是SqlSession底层真正执行操作的执行器

image-20220605171848128

5、Mapper:映射器实例

映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。 映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 你的应用逻辑代码
}

6、SqlSession是如何生成Mapper实例的?

image-20220605161943111

image-20220605162046947

我们可以看出SqlSession是有两个实现类,其实现类中都维护一个配置信息实例,事实上不管是哪一个,最终都会走到Configuration的一个方法中:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

这里我们是从MapperRegistry中获取Mapper的

public class MapperRegistry {

  private final Configuration config;
  // 维护了一个类字节码与对应的Mapper的代理工厂缓存
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
}

MapperRegistry 中维护了一个代理类型工厂容器,此工厂应该用来创建相应的Mapper接口的代理类,我们往下依次推下去:

/**
1 ---------------------------------------------------------------------------------------------
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  // 根据Mapper类型获取Mapper代理工厂
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    // Mapper代理工厂创建实例
    return mapperProxyFactory.newInstance(sqlSession);
}
/**
2 -------------------------new MapperProxy实例用来创建代理类--------------------------------------
*/
 public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
/**
3 -----------------------mapperProxy实现了InvocationHandler-------------------------------------
*/
public class MapperProxy<T> implements InvocationHandler, Serializable {}
/**
4 -----------------------------动态代理生成Mapper代理类-------------------------------------------
*/
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

我们发现其实底层是通过SqlSession下面的子类--> Configuration-->MapperRegistry-->MapperProxyFactory-->MapperProxy-->JDK 自带的基于接口的动态代理这些步骤创建的Mapper实例

posted @   码出新生活!  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示