dubbo源码深度解析_mybatis 3.x源码深度解析(六)

32d97c870df3445a6984fe5d74359afb.png

4 SQL语句的执行流程

4.1 传统JDBC用法

  在原生jdbc中,我们要执行一个sql语句,它的流程是这样的:

  1. 注册驱动;
  2. 获取jdbc连接;
  3. 创建参数化预编译SQL;
  4. 绑定参数;
  5. 发送SQL给数据库进行执行;
  6. 对于查询,获取结果集到应用;

  我们先回顾下典型JDBC的用法:

package org.mybatis.internal.example;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;

public class JdbcHelloWord {

    /**
     * 入口函数
     * @param arg
     */
    public static void main(String arg[]) {
        try {
            Connection con = null; //定义一个MYSQL链接对象
            Class.forName("com.mysql.jdbc.Driver").newInstance(); //MYSQL驱动
            con = DriverManager.getConnection("jdbc:mysql://10.7.12.4:3306/lfBase?useUnicode=true", "lfBase", "eKffQV6wbh3sfQuFIG6M"); //链接本地MYSQL

            //更新一条数据
            String updateSql = "UPDATE LfParty SET remark1 = 'mybatis internal example' WHERE lfPartyId = ?";
            PreparedStatement pstmt = con.prepareStatement(updateSql);
            pstmt.setString(1, "1");
            long updateRes = pstmt.executeUpdate();
            System.out.print("UPDATE:" + updateRes);

            //查询数据并输出
            String sql = "select lfPartyId,partyName from LfParty where lfPartyId = ?";
            PreparedStatement pstmt2 = con.prepareStatement(sql);
            pstmt2.setString(1, "1");
            ResultSet rs = pstmt2.executeQuery();
            while (rs.next()) { //循环输出结果集
                String lfPartyId = rs.getString("lfPartyId");
                String partyName = rs.getString("partyName");
                System.out.print("rnrn");
                System.out.print("lfPartyId:" + lfPartyId + "partyName:" + partyName);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.2 mybatis执行SQL语句

  同样的,在mybatis中,要执行sql语句,首先要拿到代表JDBC底层连接的一个对象,这在mybatis中的实现就是SqlSession。mybatis提供了下列实现:

efcc2b72f2e3b054e14e255ad733b857.png


  获取SqlSession的API如下:

SqlSession session = SqlSessionFactory.openSession();
try {
    User user = (User) session.selectOne("org.mybatis.internal.example.mapper.UserMapper.getUser", 1);
    System.out.println("sql from xml:" + user.getLfPartyId() + "," + user.getPartyName());
    UserMapper2 mapper = session.getMapper(UserMapper2.class);
    List<User> users = mapper.getUser2(293);
    System.out.println("sql from mapper:" + users.get(0).getLfPartyId() + "," + users.get(0).getPartyName());
} finally {
    session.close();
}

  同样,首先调用SqlSessionFactory.openSession()拿到一个session,然后在session上执行各种CRUD操作。简单来说,SqlSession就是jdbc连接的代表,openSession()就是获取jdbc连接(当然其背后可能是从jdbc连接池获取);session中的各种selectXXX方法或者调用mapper的具体方法就是集合了JDBC调用的第3、4、5、6步。SqlSession接口的定义如下:

efcc2b72f2e3b054e14e255ad733b857.png


  可知,绝大部分的方法都是泛型方法,也可以说采用了模板方法实现。

4.2.1 获取openSession

  获取openSession的总体流程为:

efcc2b72f2e3b054e14e255ad733b857.png


  我们先来看openSession的具体实现。mybatis提供了两个SqlSessionFactory实现:SqlSessionManager和DefaultSqlSessionFactory,默认返回的是DefaultSqlSessionFactory,它们的区别我们后面会讲到。我们先来看下SqlSessionFactory的接口定义:

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}
主要有多种形式的重载,除了使用默认设置外,可以指定自动提交模式、特定的jdbc连接、事务隔离级别,以及指定的执行器类型。关于执行器类型,mybatis提供了三种执行器类型:SIMPLE, REUSE, BATCH。后面我们会详细分析每种类型的执行器的差别以及各自的适用场景。我们以最简单的无参方法切入(按照一般的套路,如果定义了多个重载的方法或者构造器,内部实现一定是设置作者认为最合适的默认值,然后调用次多参数的方法,直到最后),它的实现是这样的:
public class DefaultSqlSessionFactory implements SqlSessionFactory {

  ...

  @Override
  public SqlSession openSession() {
    // 使用默认的执行器类型(默认是SIMPLE),默认隔离级别,非自动提交 委托给openSessionFromDataSource方法
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  ...

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      // 获取事务管理器, 支持从数据源或者直接获取
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

      // 从数据源创建一个事务, 同样,数据源必须配置, mybatis内置了JNDI、POOLED、UNPOOLED三种类型的数据源,其中POOLED对应的实现为org.apache.ibatis.datasource.pooled.PooledDataSource,它是mybatis自带实现的一个同步、线程安全的数据库连接池 一般在生产中,我们会使用dbcp或者druid连接池
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }
  ...
  private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    // 如果没有配置environment或者environment的事务管理器为空,则使用受管的事务管理器
    // 除非什么都没有配置,否则在mybatis-config里面,至少要配置一个environment,此时事务工厂不允许为空
    // 对于jdbc类型的事务管理器,则返回JdbcTransactionFactory,其内部操作mybatis的JdbcTransaction实现(采用了Facade模式),后者对jdbc连接操作
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
  }
}

我们来看下transactionFactory.newTransaction的实现,还是以jdbc事务为例子。

public class JdbcTransactionFactory implements TransactionFactory {
  ...

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

newTransaction的实现逻辑很简单,但是此时返回的事务不一定是有底层连接的。
拿到事务后,根据事务和执行器类型创建一个真正的执行器实例。获取执行器的逻辑如下:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor 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 (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

  如果没有配置执行器类型,默认是简单执行器。如果启用了缓存,则使用缓存执行器。
  拿到执行器之后,new一个DefaultSqlSession并返回,这样一个SqlSession就创建了,它从逻辑上代表一个封装了事务特性的连接,如果在此期间发生异常,则调用关闭事务(因为此时事务底层的连接可能已经持有了,否则会导致连接泄露)。
  DefaultSqlSession的构造很简单,就是简单的属性赋值:

public class DefaultSqlSession implements SqlSession {

  private Configuration configuration;
  private Executor executor;

  private boolean autoCommit;
  // 含义是TODO
  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;
  }
  ...
}

  具体的对外API我们后面专门讲解。

  根据sql语句使用xml进行维护或者在注解上配置,sql语句执行的入口分为两种:
  第一种,调用org.apache.ibatis.session.SqlSession的crud方法比如selectList/selectOne传递完整的语句id直接执行;
  第二种,先调用SqlSession的getMapper()方法得到mapper接口的一个实现,然后调用具体的方法。除非早期,现在实际开发中,我们一般采用这种方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值