4 SQL语句的执行流程
4.1 传统JDBC用法
在原生jdbc中,我们要执行一个sql语句,它的流程是这样的:
- 注册驱动;
- 获取jdbc连接;
- 创建参数化预编译SQL;
- 绑定参数;
- 发送SQL给数据库进行执行;
- 对于查询,获取结果集到应用;
我们先回顾下典型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提供了下列实现:
获取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接口的定义如下:
可知,绝大部分的方法都是泛型方法,也可以说采用了模板方法实现。
4.2.1 获取openSession
获取openSession的总体流程为:
我们先来看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接口的一个实现,然后调用具体的方法。除非早期,现在实际开发中,我们一般采用这种方式。