深度分析:mybatis的底层实现原理,看完你学会了吗?
前言
最近与粉丝交流时,被他们问及关于JDBC和MyBatis底层实现的问题,不止一个小伙伴也有同样的疑问,意识到问题的重要性后,我用了两天时间梳理了自己的思路并结合相关资料撰写了这篇文章,不卖关子,干货十足
在说mybatis底层实现之前,先看下基本的知识点jdbc
jdbc是连接数据库的最基本实现,任何对数据库的操作都是基于jdbc
注册驱动
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();
AI写代码
Mybatis之Sqlsession、Connection和Transaction解析关系与原理
JDK 是我们与数据库交互的基础API。
在与特定数据库建立会话时使用的Connection,在这个连接上下文中执行SQL语句并返回结果。
我们先来了解使用SqlSession进行数据库操作的基本流程。
SqlSession相较于Connection具有更高级别的抽象性,在其方法上更能体现出明显的操作特点。
事务(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();
}
AI写代码
我门先看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);
}
AI写代码
最终执行经过多个XML文件的详细解析过程,并成功返回DefaultSqlSessionFactory实例后被调用其构造函数
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
AI写代码
在构造函数中仅初始化了其configuration属性,在这个configuration内部包含了Environment属性。然而,在Environment属性内部则包含了数据源、连接操作、事务管理等核心功能。
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
....
}
AI写代码
接下来我们将聚焦于 SqlSession = sqlSessionFactory.openSession() 这一主要步骤,并探讨其中其执行类为 DefaultSqlSessionFactory 的主要功能及其作用。
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;
}
AI写代码
可以看到mybatis创建sqlsession经过了以下几个主要步骤:
- 通过访问核心配置文件mybatis-config.xml来提取出Environment变量(该变量代表数据源);
- 在获得的Environment变量基础上提取出DataSource对象;
- 通过环境变量获取TransactionFactory实例;
- 利用提取到的DataSource创建数据库连接对象Connection;
- 基于生成的数据库连接建立事务对象Transaction;
- 构造一个Executor类实例(这一操作至关重要);
- 生成sqlsession对象实例。
我们对Executor对象进行详细讲解:因为该对象是负责执行SQL操作的核心类。 通过调用this.configuration.newExecutor(tx, execType)方法来创建Executor实例
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;
}
AI写代码
可知,在启用缓存功能时(即开启cache),系统将创建一个CachingExecutor;若不启用缓存,则将生成普通Executor。普通Executor主要有三个基本类型:BatchExecutor、ReuseExecutor以及SimpleExecutor。其中BatchExecutor专门设计用于处理大批量SQL操作;而ReuseExecutor则能够利用已存在的statement来进行SQL操作。需要注意的是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;
}
AI写代码
StatementHandler
从其本质角度来看,在某些情况下Executor也扮演着甩手掌柜的角色
publicStatementHandler newStatementHandler(Executor executor, MappedStatementmappedStatement,
ObjectparameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
StatementHandler statementHandler = newRoutingStatementHandler(executor, mappedStatement,parameterObject,rowBounds, resultHandler);
statementHandler= (StatementHandler) interceptorChain.pluginAll(statementHandler);
returnstatementHandler;
}
AI写代码
可以看出每次创建的StatementHandler都是RoutingStatementHandler它仅作为一个分发者存在其 delegate 属性用于指定使用的具体类型可选类型包括SimpleStatementHandlerPreparedStatementHandler和CallableStatementHandler这三种选项在mapper配置文件中的statement设置下可以选择其中一种而默认情况下则选择PreparedStatementHandler值得注意的是每当StatementHandler被调用时都会被interceptor进行拦截并返回一个代理对象例如像实现数据库物理分页的各种分页算法都是基于此机制开发的
阅读了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);
AI写代码
那么这个mapper的具体作用到底是什么呢?它是如何被创建起来的?又它是如何与sqlsession等其他组件产生联系的呢?
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
AI写代码
最终调用的是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);
}
AI写代码
可以观察到,在Java反射机制中,mapper充当了一个代理对象的角色。它所实现的接口正是传入的那个类(type),因此可以说mapper对象可以直接通过接口来访问相关的功能或属性。此外,在创建mapper代理对象的过程中,默认会传递一个sqlsession对象进来,这也就实现了对sqlsession对象的有效连接和引用。
在Java反射机制中,我们可以通过以下代码来创建一个新式的Proxy实例:
Proxy newProxyInstance = 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);
}
}
AI写代码
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;
}
AI写代码
该结果表明,在构造过程中将mapperProxy作为参数传递给一个代理实现类。我们进一步关注mapperProxy这个类的主要功能体现在其invoke方法上。实际上,所有对被代理对象的方法访问都会最终由其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);
}
AI写代码
可以看到该方法实现了将执行权限转移给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;
}
}
AI写代码
可以看到,在MapperMethod中扮演着分发者的角色,在处理过程中它根据接收的参数和返回值类型来决定执行哪个具体的sqlsession方法。因此,在这种机制下,mapper对象与sqlsession之间实现了它们之间的真正联系。
最后:
如果有疑问的话,请随时在下面评论区留言。另外,在线沟通也是一种选择。通常我会在下班后及时回复。偶尔因为工作繁忙可能看不到您的消息抱歉给您带来不便在此表示歉意。
