Mybatis 源码分析

2023-11-20 14:42:47 浏览数 (1)

1、连接 MySQL

1.1、Java连接MySQL
代码语言:text复制
    public static void main(String[] args) throws SQLException {
        String url = "";
        String userName = "";
        String password = "";
//        创建数据库连接,当前操作在语句结束后会自动关闭流
        try(Connection connection = DriverManager.getConnection(url,userName,password)){
            String sql = "select * from dual";
            try(PreparedStatement preparedStatement = connection.prepareStatement(sql)){
                try(ResultSet resultSet = preparedStatement.executeQuery()){
                    while (resultSet.next()){
                        System.out.println(resultSet.getString(""));
                    }
                }
            }
        }
    }
1.2、Java 使用 Mybatis 连接 MySQL

config.xml

定义数据库连接配置

代码语言:html复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <!-- 配置数据库连接信息 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/data_generate?characterEncoding=utf8&amp;zeroDateTimeBehavior=convertToNull&amp;useSSL=false&amp;useJDBCCompliantTimezoneShift=true&amp;useLegacyDatetimeCode=false&amp;serverTimezone=UTC&amp;allowPublicKeyRetrieval=true" />
                <property name="username" value="root" />
                <property name="password" value="123456" />
            </dataSource>
        </environment>
    </environments>

<!-- 这个我放置在了项目resources目录下 -->
    <mappers>
        <mapper resource="mapper/AccountMapper.xml"/>
    </mappers>

</configuration>

AccountMapper.xml

定义数据库语句

代码语言:html复制
<?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.rookie.demo.component.AccountMapper">


    <select id="listAll" resultType="map">
        select * from account
    </select>
</mapper>

AccountMapper.class

定义 Mapper 文件

代码语言:c#复制
public interface AccountMapper {

    List<Map> listAll();

}

主方法

代码语言:text复制
 public static void main(String[] args) throws IOException {
        String resource = "config.xml";
        try(InputStream inputStream = Resources.getResourceAsStream(resource)){
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            try(SqlSession sqlSession = sqlSessionFactory.openSession()){
                AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
                List<Map> maps = mapper.listAll();
                System.out.println(maps);
            }

        }
    }

框架是对原来操作的扩展封装,接下来我们来看下 Mybatis 是如何将几行代码扩展成一个框架的。

2、Mybatis 连接分析

代码语言:text复制
// 加载 resources 目录下的 config.xml 流文件
String resource = "config.xml";
try(InputStream inputStream = Resources.getResourceAsStream(resource)){}
2.1、创建连接
代码语言:scss复制
// 构建连接工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

步入到 org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)

代码语言:typescript复制
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
            }
        }

        return var5;
    }

parser.parse() 这句代码是解析 xml 文件中的 configuration 标签节点,具体可以看 org.apache.ibatis.builder.xml.XMLConfigBuilder#parsethis.parseConfiguration(this.parser.evalNode("/configuration"));,拿到了包装了配置信息 Configuration 的DefaultSqlSessionFactory。

下一步获取一个连接 try(SqlSession sqlSession = sqlSessionFactory.openSession()){}。我们来看下 openSession 做了些什么操作。

代码语言:text复制
public SqlSession openSession() {
      // 根据配置选择 ExecutorType,空的隔离级别,不自动提交
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }


private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
// 创建一个事务
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建一个 sql 语句的执行器
            Executor executor = this.configuration.newExecutor(tx, execType);
// 将 Configuration 等这些配置包装一下返回一个 SqlSession
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: "   var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

我们执行 SQL 语句都是靠 Executor 来执行的,我们进入它的其中一个实现类 SimpleExecutor 的 doUpdate 方法。

代码语言:text复制
ublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;

        int var6;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var6 = handler.update(stmt);
        } finally {
            this.closeStatement(stmt);
        }

        return var6;
    }

stmt = this.prepareStatement(handler, ms.getStatementLog()); 这句是不是和我们上面的预处理很像?

我们来看下 newExecutor 做了什么。

代码语言:text复制
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? this.defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Object executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }

        if (this.cacheEnabled) {
            executor = new CachingExecutor((Executor)executor);
        }

        Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
        return executor;
    }

上面根据配置选择创建 Executor,如果没有配置则选择默认的 SIMPLE Executor。

Executor 分类:

代码语言:text复制
BaseExecutor :是一个抽象类,这种通过抽象的实现接口的方式是适配器设计模式之接口适配的体现,是 Executor 的默认实现,实现了大部分 Executor 接口定义的功能,降低了接口实现的难度。BaseExecutor 的子类有三个,分别是SimpleExecutor、ReuseExecutor和BatchExecutor。

SimpleExecutor: 简单执行器,是 MyBatis 中默认使用的执行器,每执行一次 update 或 select,就开启一个 Statement 对象,用完就直接关闭 Statement 对象(可以是 Statement 或者是 PreparedStatment 对象)

ReuseExecutor: 可重用执行器,这里的重用指的是重复使用 Statement,它会在内部使用一个 Map 把创建的 Statement 都缓存起来,每次执行 SQL 命令的时候,都会去判断是否存在基于该 SQL 的 Statement 对象,如果存在 Statement 对象并且对应的 connection 还没有关闭的情况下就继续使用之前的 Statement 对象,并将其缓存起来。每个SqlSession 都有一个新的 Executor 对象,所以我们缓存在 ReuseExecutor 上的Statement 作用域是同一个 SqlSession。

BatchExecutor: 批处理执行器,用于将多个SQL一次性输出到数据库

CachingExecutor: 缓存执行器,先从缓存中查询结果,如果存在,就返回;如果不存在,再委托给 Executor delegate 去数据库中取,delegate 可以是上面任何一个执行器,默认是simpleexecutor

Executor executor = (Executor)this.interceptorChain.pluginAll(executor); 这句就是执行我们自定义的插件。

进入 org.apache.ibatis.transaction.jdbc.JdbcTransaction#openConnection,这里和 Java 连接 MySQL 一样获取了一个 Connection。

代码语言:text复制
protected void openConnection() throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug("Opening JDBC Connection");
        }

        this.connection = this.dataSource.getConnection();
        if (this.level != null) {
            this.connection.setTransactionIsolation(this.level.getLevel());
        }

        this.setDesiredAutoCommit(this.autoCommit);
    }

接下来重点来了。看到这句 AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);

这句的作用就是获取 mapper 的代理对象。怎么看出是代理对象呢?看下 org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>) 的这句代码就知道了

代码语言:typescript复制
protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

然后我们看下代理类的关键代码:

代码语言:php复制
public final List listAll() {
        boolean var10000 = true;

        try {
            return (List)f.convertForCast(Proxy._jr$ig$h(this).invoke(this, m3, (Object[])null), List.class);
        } catch (RuntimeException | Error var2) {
            throw (Throwable)g.getCurrentVersion(var2);
        } catch (Throwable var3) {
            throw (Throwable)g.getCurrentVersion(new UndeclaredThrowableException(var3));
        }
    }

好吧,没啥好看的,我们看下 invoke 调用了什么吧。找到 org.apache.ibatis.binding.MapperProxy#invoke 的这行代码 return mapperMethod.execute(this.sqlSession, args);

真正的执行逻辑在这 org.apache.ibatis.binding.MapperMethod#execute

代码语言:text复制
public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: "   this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '"   this.command.getName()   " attempted to return null from a method with a primitive return type ("   this.method.getReturnType()   ").");
        } else {
            return result;
        }
    }

后续再进行内容优化……

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞