前言
最近在和朋友聊天的時候被問到jdbc和MyBatis底層實現這一塊的問題,而且還不止一個小伙伴問到,于是我似乎認識到了問題的嚴重性,我花了兩天時間整理了一下自己的認識和網上查閱的資料寫了這篇文章,話不多說,滿滿的干貨都在下面了。
在說mybatis底層實現之前,先看下基本的知識點jdbc
jdbc是連接數據庫的最基本實現,任何對數據庫的操作都是基于jdbc
1. 注冊驅動
Class.forName("com.MySQL.jdbc.Driver");
2.獲取數據庫連接
Connection conn =DriverManager.getConnection(url,user,p);
3.創建向數據發送sql 的statement對象
Statement stmt = conn.CreateStatement();
4. 向數據庫發送sql
ResultSet rs = stmt.executeQuery(sql)//select語句
int updateaSum = stmt.executeUpdate(sql)//insert,update delete語句
5. 處理結果集
while(rs.next()){
rs.getString(列名)
rs.getInt(列名)
}
6. 關閉資源
rs.close();
stmt.close();
conn.close();
Mybatis之Sqlsession、Connection和Transaction解析關系與原理
Connection JDBC 是我們用來與數據庫交互最基礎的API。 Connection 作為一個特定數據庫的會話,在一個連接的上下文中,sql語句被執行,然后結果被返回。 我們先看下使用sqlsession進行數據庫操作的基本流程 SqlSession 可以看作是對Connection 更加高級的抽象,從其方法上更加可以看出他具有更加明顯的操作特征。 Transaction 事務(Transaction) ,正是對N(N>=1)個操作執行時,同時成功或同時失敗的 關系 的具象。
我們先看下sqlsession是如何進行數據庫操作的:
String resource = "mybatis-config.xml";
//獲取數據配置流
InputStream inputStream = Resources.getResourceAsStream(resource);
//通過SqlSessionFactoryBuilder獲取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通過sqlSessionFactory獲取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//對數據庫執行操作
try {
TbUserMApper userMapper = sqlSession.getMapper(TbUserMapper.class);
TbUser user = new TbUser("liybk", "liybk","186..","123");
userMapper.insertUser(user);
sqlSession.commit();// 這里一定要提交,不然數據進不去數據庫中
} finally {
sqlSession.close();
}
我們先看SqlSessionFactoryBuilder().build(inputStream)方法:
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;
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
最終執行通過一系列的xml文件解析,返回了DefaultSqlSessionFactory,進入DefaultSqlSessionFactory構造函數
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
構造函數只是初始化了其configuration 屬性,這個configuration 里面包含了一個Environment屬性,而Environment屬性里又有數據源,connect,事務等等一系列關于數據庫操作的基本屬性
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
....
}
好的,我們回到主步驟SqlSession sqlSession = sqlSessionFactory.openSession()方法 其執行類是DefaultSqlSessionFactory,目的是獲取sqlSession
public SqlSession openSession() {
return
//調用該類的openSessionFromDataSource方法
this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
//openSessionFromDataSource方法
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
//獲取Environment
Environment environment = this.configuration.getEnvironment();
//從Environment中取得TransactionFactory;
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
//在取得的數據庫連接上創建事務對象Transaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 創建Executor對象
Executor executor = this.configuration.newExecutor(tx, execType);
//創建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;
}
可以看到mybatis創建sqlsession經過了以下幾個主要步驟:
從核心配置文件mybatis-config.xml中獲取Environment(這里面是數據源); 從Environment中取得DataSource; 從Environment中取得TransactionFactory; 從DataSource里獲取數據庫連接對象Connection; 在取得的數據庫連接上創建事務對象Transaction; 創建Executor對象(該對象非常重要,事實上sqlsession的所有操作都是通過它完成的); 創建sqlsession對象。
我們對Executor對象著重講下,因為該對象是執行sql的實現類: 進入 Executor executor = this.configuration.newExecutor(tx, execType)方法
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;
}
可以看出,如果開啟cache的話,會創建CachingExecutor,否則創建普通Executor,普通Executor有3個基礎類型,BatchExecutor專門用于執行批量sql操作,ReuseExecutor會重用statement執行sql操作,SimpleExecutor只是簡單執行sql沒有什么特別的。而CachingExecutor在查詢數據庫前先查找緩存,若沒找到的話調用delegate(就是構造時傳入的Executor對象)從數據庫查詢,并將查詢結果存入緩存中。 Executor對象是可以被插件攔截的,如果定義了針對Executor類型的插件,最終生成的Executor對象是被各個插件插入后的代理對象。 我們簡單的看下其3個基本基礎類型中最簡單的SimpleExecutor 是怎么執行sql的
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
int var6;
try {
//拿到Configuration 屬性
Configuration configuration = ms.getConfiguration();
//拿到StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
//拿到prepareStatement
stmt = this.prepareStatement(handler, ms.getStatementLog());
//prepareStatement執行sql
var6 = handler.update(stmt);
} finally {
this.closeStatement(stmt);
}
return var6;
}
StatementHandler
可以看出,Executor本質上也是個甩手掌柜,具體的事情原來是StatementHandler來完成的。 當Executor將指揮棒交給StatementHandler后,接下來的工作就是StatementHandler的事了。我們先看看StatementHandler是如何創建的。
publicStatementHandler newStatementHandler(Executor executor, MappedStatementmappedStatement,
ObjectparameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
StatementHandler statementHandler = newRoutingStatementHandler(executor, mappedStatement,parameterObject,rowBounds, resultHandler);
statementHandler= (StatementHandler) interceptorChain.pluginAll(statementHandler);
returnstatementHandler;
}
可以看到每次創建的StatementHandler都是RoutingStatementHandler,它只是一個分發者,他一個屬性delegate用于指定用哪種具體的StatementHandler??蛇x的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三種。選用哪種在mapper配置文件的每個statement里指定,默認的是PreparedStatementHandler。同時還要注意到StatementHandler是可以被攔截器攔截的,和Executor一樣,被攔截器攔截后的對象是一個代理對象。例如像實現數據庫的物理分頁,眾多物理分頁的實現都是在這個地方使用攔截器實現的
看完了Executor具體執行過程,還沒結束,我們還不知道在執行前一步,就是代碼塊前兩步,到底做了什么關聯,再一次貼出來:
.....
//通過sqlSessionFactory獲取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
1、 TbUserMapper userMapper = sqlSession.getMapper(TbUserMapper.class);
2、 TbUser user = new TbUser("liybk", "liybk","186..","123");
3、 userMapper.insertUser(user);
那么這個mapper作用到底是什么呢,它是如何創建的呢,它又是怎么與sqlsession等關聯起來的呢? 我們進入方法:
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
最終調用的是configuration的mapperRegistry方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
//mapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
//mapperProxyFactory
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
可以看到,mapper是一個代理對象,它實現的接口就是傳入的type,這就是為什么mapper對象可以通過接口直接訪問。同時還可以看到,創建mapper代理對象時傳入了sqlsession對象,這樣就把sqlsession也關聯起來了。 我們進入Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy)方法,注意這個方法傳入的參數mapperProxy
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
//拿到mapper接口類
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
//進行權限檢查
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
//查找/生成代理類
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//獲取構造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//將mapperProxy參數轉為InvocationHandler 傳入
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
mapperProxy是InvocationHandler的子類,再進入cons.newInstance(new Object[]{h})方法
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
結果是將mapperProxy作為構造參數返回了一個代理實現類,我們再看下mapperProxy這個類的主要方法 invoke 我們知道對被代理對象的方法的訪問都會落實到代理者的invoke上來,MapperProxy的invoke如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
可以看到invoke把執行權轉交給了MapperMethod,我們來看看MapperMethod里又是怎么運作的:
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;
}
}
可以看到,MapperMethod就像是一個分發者,他根據參數和返回值類型選擇不同的sqlsession方法來執行。這樣mapper對象與sqlsession就真正的關聯起來了。
作者:前程有光
鏈接:https://juejin.im/post/5f016a10f265da22e56dfa03