源码解析MyBatis Sharding-Jdbc SQL语句执行流程详解

result = sqlSession.selectOne(command.getName(), param);

}

} else {

throw new BindingException("Unknown execution method for: " + command.getName());

}

if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {

throw new BindingException(“Mapper method '” + command.getName()

  • " attempted to return null from a method with a primitive return type (" + method.getReturnType() + “).”);

}

return result;

}

该方法主要是根据SQL类型,insert、update、select等操作,执行对应的逻辑,本文我们以查询语句,进行跟踪,进入executeForMany(sqlSession, args)方法。

2.3 MapperMethod#executeForMany

private Object executeForMany(SqlSession sqlSession, Object[] args) {

List result;

Object param = method.convertArgsToSqlCommandParam(args);

if (method.hasRowBounds()) {

RowBounds rowBounds = method.extractRowBounds(args);

result = sqlSession.selectList(command.getName(), param, rowBounds);

} else {

result = sqlSession.selectList(command.getName(), param);

}

// issue #510 Collections & arrays support

if (!method.getReturnType().isAssignableFrom(result.getClass())) {

if (method.getReturnType().isArray()) {

return convertToArray(result);

} else {

return convertToDeclaredCollection(sqlSession.getConfiguration(), result);

}

}

return result;

}

该方法也比较简单,最终通过SqlSession调用selectList方法。

2.4 DefaultSqlSession#selectList

public List selectList(String statement, Object parameter, RowBounds rowBounds) {

try {

MappedStatement ms = configuration.getMappedStatement(statement); // @1

List result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); // @2

return result;

} catch (Exception e) {

throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);

} finally {

ErrorContext.instance().reset();

}

}

代码@1:根据资源名称获取对应的MappedStatement对象,此时的statement为资源名称,例如com.demo.UserMapper.findUser。至于MappedStatement对象的生成在上一节初始化时已详细介绍过,此处不再重复介绍。

代码@2:调用Executor的query方法。这里说明一下,其实一开始会进入到CachingExecutor#query方法,由于CachingExecutor的Executor delegate属性默认是SimpleExecutor,故最终还是会进入到SimpleExecutor#query中。

接下来我们进入到SimpleExecutor的父类BaseExecutor的query方法中。

2.5 BaseExecutor#query

public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // @1

ErrorContext.instance().resource(ms.getResource()).activity(“executing a query”).object(ms.getId());

if (closed) throw new ExecutorException(“Executor was closed.”);

if (queryStack == 0 && ms.isFlushCacheRequired()) {

clearLocalCache();

}

List list;

try {

queryStack++;

list = resultHandler == null ? (List) localCache.getObject(key) : null; // @2

if (list != null) {

handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

} else {

list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); // @3

}

} finally {

queryStack–;

}

if (queryStack == 0) {

for (DeferredLoad deferredLoad : deferredLoads) {

deferredLoad.load();

}

deferredLoads.clear(); // issue #601

if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // @4

clearLocalCache(); // issue #482

}

}

return list;

}

代码@1:首先介绍一下该方法的入参,这些类都是Mybatis的重要类:

  • MappedStatement ms

映射语句,一个MappedStatemnet对象代表一个Mapper中的一个方法,是映射的最基本对象。

  • Object parameter

SQL语句的参数列表。

  • RowBounds rowBounds

行边界对象,其实就是分页参数limit与size。

  • ResultHandler resultHandler

结果处理Handler。

  • CacheKey key

Mybatis缓存Key

  • BoundSql boundSql

SQL与参数绑定信息,从该对象可以获取在映射文件中的SQL语句。

代码@2:首先从缓存中获取,Mybatis支持一级缓存(SqlSession)与二级缓存(多个SqlSession共享)。

代码@3:从数据库查询结果,然后进入到doQuery方法,执行真正的查询动作。

代码@4:如果一级缓存是语句级别的,则语句执行完毕后,删除缓存。

2.6 SimpleExecutor#doQuery

public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {

Statement stmt = null;

try {

Configuration configuration = ms.getConfiguration();

StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // @1

stmt = prepareStatement(handler, ms.getStatementLog()); // @2

return handler.query(stmt, resultHandler); // @3

} finally {

closeStatement(stmt);

}

}

代码@1:创建StatementHandler,这里会加入Mybatis的插件扩展机制(将在下篇详细介绍),如图所示:

在这里插入图片描述

代码@2:创建Statement对象,注意,这里就是JDBC协议的java.sql.Statement对象了。

代码@3:使用Statment对象执行SQL语句。

接下来详细介绍Statement对象的创建过程与执行过程,即分布详细跟踪代码@2与代码@3。

3、Statement对象创建流程


3.1 java.sql.Connection对象创建

3.1.1 SimpleExecutor#prepareStatement

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {

Statement stmt;

Connection connection = getConnection(statementLog); // @1

stmt = handler.prepare(connection); // @2

handler.parameterize(stmt); // @3

return stmt;

}

创建Statement对象,分成三步:

代码@1:创建java.sql.Connection对象。

代码@2:使用Connection对象创建Statment对象。

代码@3:对Statement进行额外处理,特别是PrepareStatement的参数设置(ParameterHandler)。

3.1.2 SimpleExecutor#getConnection

getConnection方法,根据上面流程图所示,先是进入到org.mybatis.spring.transaction.SpringManagedTransaction,再通过spring-jdbc框架,利用DataSourceUtils获取连接,其代码如下:

public static Connection doGetConnection(DataSource dataSource) throws SQLException {

Assert.notNull(dataSource, “No DataSource specified”);

ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);

if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {

conHolder.requested();

if (!conHolder.hasConnection()) {

conHolder.setConnection(dataSource.getConnection());

}

return conHolder.getConnection();

}

// Else we either got no holder or an empty thread-bound holder here.

logger.debug(“Fetching JDBC Connection from DataSource”);

Connection con = dataSource.getConnection(); // @1

// 这里省略与事务处理相关的代码

return con;

}

代码@1:通过DataSource获取connection,那此处的DataSource是“谁”呢?看一下我们工程的配置:

在这里插入图片描述

在这里插入图片描述

故最终dataSouce.getConnection获取的连接,是从SpringShardingDataSource中获取连接。

com.dangdang.ddframe.rdb.sharding.jdbc.ShardingDataSource#getConnection

public ShardingConnection getConnection() throws SQLException {

MetricsContext.init(shardingProperties);

return new ShardingConnection(shardingContext);

}

返回的结果如下:

在这里插入图片描述

备注:这里只是返回了一个ShardingConnection对象,该对象包含了分库分表上下文,但此时并没有执行具体的分库操作(切换数据源)。

Connection的获取流程清楚后,我们继续来看一下Statemnet对象的创建。

3.2 java.sql.Statement对象创建

stmt = prepareStatement(handler, ms.getStatementLog());

上面语句的调用链:RoutingStatementHandler -》BaseStatementHandler

3.2.1 BaseStatementHandler#prepare

public Statement prepare(Connection connection) throws SQLException {

ErrorContext.instance().sql(boundSql.getSql());

Statement statement = null;

try {

statement = instantiateStatement(connection); // @1

setStatementTimeout(statement); // @2

setFetchSize(statement); // @3

return statement;

} catch (SQLException e) {

closeStatement(statement);

throw e;

} catch (Exception e) {

closeStatement(statement);

throw new ExecutorException("Error preparing statement. Cause: " + e, e);

}

}

代码@1:根据Connection对象(本文中是ShardingConnection)来创建Statement对象,其默认实现类:PreparedStatementHandler#instantiateStatement方法。

代码@2:为Statement设置超时时间。

代码@3:设置fetchSize。

3.2.2 PreparedStatementHandler#instantiateStatement

protected Statement instantiateStatement(Connection connection) throws SQLException {

String sql = boundSql.getSql();

if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {

String[] keyColumnNames = mappedStatement.getKeyColumns();

if (keyColumnNames == null) {

return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);

} else {

return connection.prepareStatement(sql, keyColumnNames);

}

} else if (mappedStatement.getResultSetType() != null) {

return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);

} else {

return connection.prepareStatement(sql);

}

}

其实Statement对象的创建,就比较简单了,既然Connection是ShardingConnection,那就看一下其对应的prepareStatement方法即可。

3.2.2 ShardingConnection#prepareStatement

public PreparedStatement prepareStatement(final String sql) throws SQLException { // sql,为配置在mybatis xml文件中的sql语句

return new ShardingPreparedStatement(this, sql);

}

ShardingPreparedStatement(final ShardingConnection shardingConnection,

final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) {

super(shardingConnection, resultSetType, resultSetConcurrency, resultSetHoldability);

preparedSQLRouter = shardingConnection.getShardingContext().getSqlRouteEngine().prepareSQL(sql);

}

在构建ShardingPreparedStatement对象的时候,会根据SQL语句创建解析SQL路由的解析器对象,但此时并不会执行相关的路由计算,PreparedStatement对象创建完成后,就开始进入SQL执行流程中。

4、SQL执行流程


接下来我们继续看SimpleExecutor#doQuery方法的第3步,执行SQL语句:

handler.query(stmt, resultHandler)。

首先会进入RoutingStatementHandler这个类中,进行Mybatis层面的路由(主要是根据Statement类型)

在这里插入图片描述

然后进入到PreparedStatementHandler#query中。

4.1 PreparedStatementHandler#query

public List query(Statement statement, ResultHandler resultHandler) throws SQLException {

PreparedStatement ps = (PreparedStatement) statement;

ps.execute(); // @1

return resultSetHandler. handleResultSets(ps); // @2

}

代码@1:调用PreparedStatement的execute方法,由于本例是使用了Sharding-jdbc分库分表,此时调用的具体实现为:ShardingPreparedStatement。

代码@2:处理结果。

我们接下来分别来跟进execute与结果处理方法。

4.2 ShardingPreparedStatement#execute

public boolean execute() throws SQLException {

try {

return new PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(), routeSQL()).execute(); // @1

} finally {

clearRouteContext();

}

}

这里奥妙无穷,其关键点如下:

1)创造PreparedStatementExecutor对象,其两个核心参数:

  • ExecutorEngine executorEngine:shardingjdbc执行引擎。

  • Collection< PreparedStatementExecutorWrapper> preparedStatemenWrappers

一个集合,每一个集合是PreparedStatement的包装类,这个集合如何而来?

2)preparedStatemenWrappers是通过routeSQL方法产生的。

3)最终调用PreparedStatementExecutor方法的execute来执行。

接下来分别看一下routeSQL与execute方法。

4.3 ShardingPreparedStatement#routeSQL

private List routeSQL() throws SQLException {

List result = new ArrayList<>();

SQLRouteResult sqlRouteResult = preparedSQLRouter.route(getParameters()); // @1

MergeContext mergeContext = sqlRouteResult.getMergeContext();

setMergeContext(mergeContext);

setGeneratedKeyContext(sqlRouteResult.getGeneratedKeyContext());

for (SQLExecutionUnit each : sqlRouteResult.getExecutionUnits()) { // @2

PreparedStatement preparedStatement = (PreparedStatement) getStatement(getShardingConnection().getConnection(each.getDataSource(), sqlRouteResult.getSqlStatementType()), each.getSql()); // @3

replayMethodsInvocation(preparedStatement);

getParameters().replayMethodsInvocation(preparedStatement);

result.add(wrap(preparedStatement, each));

}

return result;

}

代码@1:根据SQL参数进行路由计算,本文暂不关注其具体实现细节,这些将在具体分析Sharding-jdbc时具体详解,在这里就直观看一下其结果:

代码@2、@3:对分库分表的结果进行遍历,然后使用底层Datasource来创建Connection,创建PreparedStatement 对象。

routeSQL就暂时讲到这,从这里我们得知,会在这里根据路由结果,使用底层的具体数据源创建对应的Connection与PreparedStatement 对象。

4.4 PreparedStatementExecutor#execute

public boolean execute() {

Context context = MetricsContext.start(“ShardingPreparedStatement-execute”);

eventPostman.postExecutionEvents();

final boolean isExceptionThrown = ExecutorExceptionHandler.isExceptionThrown();

final Map<String, Object> dataMap = ExecutorDataMap.getDataMap();

try {

if (1 == preparedStatementExecutorWrappers.size()) { // @1

PreparedStatementExecutorWrapper preparedStatementExecutorWrapper = preparedStatementExecutorWrappers.iterator().next();

return executeInternal(preparedStatementExecutorWrapper, isExceptionThrown, dataMap);

}

List result = executorEngine.execute(preparedStatementExecutorWrappers, new ExecuteUnit<PreparedStatementExecutorWrapper, Boolean>() { // @2
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

机会是留给有准备的人,大家在求职之前应该要明确自己的态度,熟悉求职流程,做好充分的准备,把一些可预见的事情做好。

对于应届毕业生来说,校招更适合你们,因为绝大部分都不会有工作经验,企业也不会有工作经验的需求。同时,你也不需要伪造高大上的实战经验,以此让自己的简历能够脱颖而出,反倒会让面试官有所怀疑。

你在大学时期应该明确自己的发展方向,如果你在大一就确定你以后想成为Java工程师,那就不要花太多的时间去学习其他的技术语言,高数之类的,不如好好想着如何夯实Java基础。下图涵盖了应届生乃至转行过来的小白要学习的Java内容:

请转发本文支持一下

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

机会是留给有准备的人,大家在求职之前应该要明确自己的态度,熟悉求职流程,做好充分的准备,把一些可预见的事情做好。

对于应届毕业生来说,校招更适合你们,因为绝大部分都不会有工作经验,企业也不会有工作经验的需求。同时,你也不需要伪造高大上的实战经验,以此让自己的简历能够脱颖而出,反倒会让面试官有所怀疑。

你在大学时期应该明确自己的发展方向,如果你在大一就确定你以后想成为Java工程师,那就不要花太多的时间去学习其他的技术语言,高数之类的,不如好好想着如何夯实Java基础。下图涵盖了应届生乃至转行过来的小白要学习的Java内容:

请转发本文支持一下

[外链图片转存中…(img-OTbNYoJ5-1713751560940)]

[外链图片转存中…(img-fXQMPvAW-1713751560941)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!关于Spring Boot、MyBatis Plus和Sharding-JDBC的集成问题,我可以提供一些基本指导。首先,确保您已经在Spring Boot项目中成功集成了MyBatis Plus和Sharding-JDBC的依赖。 在pom.xml文件中添加MyBatis Plus和Sharding-JDBC的依赖: ```xml <!-- MyBatis Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!-- Sharding-JDBC --> <dependency> <groupId>io.shardingjdbc</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>${sharding-jdbc.version}</version> </dependency> ``` 接下来,您需要配置Sharding-JDBC。在application.yml(或application.properties)文件中添加相应的配置信息。以下是一个示例: ```yaml spring: shardingsphere: datasource: names: ds0, ds1 # 数据源名称 ds0: url: jdbc:mysql://localhost:3306/db0?serverTimezone=UTC username: root password: root driver-class-name: com.mysql.jdbc.Driver ds1: url: jdbc:mysql://localhost:3306/db1?serverTimezone=UTC username: root password: root driver-class-name: com.mysql.jdbc.Driver sharding: tables: user: actualDataNodes: ds$->{0..1}.user tableStrategy: inline: shardingColumn: id algorithmExpression: user$->{id % 2} keyGenerator: type: SNOWFLAKE ``` 这是一个简单的配置示例,其中包含了两个数据源(ds0和ds1),以及一个名为user的分片表的配置。您可以根据您的实际需求进行调整。 最后,您可以在MyBatis Plus的Mapper接口中使用Sharding-JDBC提供的分片功能。例如: ```java @Mapper public interface UserMapper extends BaseMapper<User> { // 自定义SQL查询方法 List<User> selectUserById(@Param("id") Long id); } ``` 这只是一个简单的示例,您可以根据自己的业务需求进行扩展和定制。 希望这些信息对您有所帮助!如果您还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值