万字长文,带你从源码认识Spring事务原理,让Spring事务不再是面试噩梦
来到golang学习网的大家,相信都是编程学习爱好者,希望在这里学习数据库相关编程知识。下面本篇文章就来带大家聊聊《万字长文,带你从源码认识Spring事务原理,让Spring事务不再是面试噩梦》,介绍一下MySQL、Redis、Java、springboot、后端,希望对大家的知识积累有所帮助,助力实战开发!


2.创建对应数据库表的 PO 3.创建表与实体间的映射 在使用 4.创建数据操作接口 5.创建数据操作接口实现类 跟书中例子不一样,没有在接口上加入事务注解,而是在公共方法上进行添加,可以在每个方法上自定义传播事件、隔离级别。 6.创建配置文件 7.添加依赖 记得添加数据库连接和 8.启动代码 通过上面的代码,我做了两个测试: 根据上面的方法, 本篇笔记主要围绕着默认实现方式,动态 在这里注册了代理类和三个 在这一步中,注册了一个 其中关于 我们在前面看到, 在匹配 那它到底到哪一步解析事务注解的呢,继续跟踪代码,答案是: 在这一步中,遍历注册的注解解析器进行解析,由于我们关注的是事务解析,所以直接定位到事务注解的解析器: 首先判断是否含有 通过上面的步骤,完成了对应类或者方法的事务属性解析。 主要步骤在于寻找增强器,以及判断这些增强器是否与方法或者类匹配。 如果某个 实际调用了父类的方法: 贴出的代码有删减,简略了错误异常的 由于最开始我们在 在创建事务方法中,主要完成以下三件事: 其中在同一个线程中,判断是否有重复的事务,是在 结论: 在配置中配置设定为 如果有事务存在,将事务挂起,而不是抛出异常: 对于挂起操作,主要目的是记录原有事务的状态,以便于后续操作对事务的恢复: 实际上, 最后调用的关键方法是 看了第七条参考资料中的文章,结合代码理解了事务挂起的操作:移除当前线程、数据源活动事务对象的一个过程 那它是如何实现事务挂起的呢,答案是在 与前一个方法相同的是,在 学习过数据库的朋友应该清楚 在声明式的事务处理中,主要有以下几个处理步骤: 这两步操作,主要调用了底层数据库连接的 本篇文章简单记录了如何使用 在之前的使用场景中,只用到了默认配置的声明式事务 所以,经过这一次学习和总结,在下一次使用时,就能够知道不同属性设置能解决什么问题,例如修改广播特性 终于介绍完啦!小伙伴们,这篇关于《万字长文,带你从源码认识Spring事务原理,让Spring事务不再是面试噩梦》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布数据库相关知识,快来关注吧!create table test.user(
id int auto_increment
primary key,
name varchar(20) null, age int(3) null)
engine=InnoDB charset=utf8;
public class JdbcUser {
private Integer id;
private String name;
private Integer age;
...(使用 ctrl + N 进行代码补全 setter 和 getter)
}
public class UserRowMapper implements RowMapper {
@Override
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
JdbcUser user = new JdbcUser();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
return user;
}
}
public interface UserDao {
/**
* 插入
* @param user 用户信息
*/
void insertUser(JdbcUser user);
/**
* 根据 id 进行删除
* @param id 主键
*/
void deleteById(Integer id);
/**
* 查询
* @return 全部
*/
List
public class UserJdbcTemplate implements UserDao {
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void insertUser(JdbcUser user) {
String sql = "insert into user (id, name, age) values (?, ?, ?)";
jdbcTemplate.update(sql, user.getId(), user.getName(), user.getAge());
System.out.println("Create record : " + user.toString());
}
@Override
@Transactional
public void deleteById(Integer id) {
String sql = "delete from user where id = ?";
jdbcTemplate.update(sql, id);
System.out.println("Delete record, id = " + id);
// 事务测试,抛出异常,让上面的插入操作回滚
throw new RuntimeException("aa");
}
@Override
public List
optional(project(":spring-jdbc")) // for Quartz support
optional(project(":spring-tx")) // for Quartz support
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.6'
public class TransactionBootstrap {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("transaction/transaction.xml");
UserJdbcTemplate jdbcTemplate = (UserJdbcTemplate) context.getBean("userJdbcTemplate");
System.out.println("--- Records Creation start ----");
JdbcUser user = new JdbcUser(4, "test", 21);
jdbcTemplate.insertUser(user);
}
}
@Override
public void init() {
registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
// 使用 AnnotationDrivenBeanDefinitionParser 解析器,解析 annotation-driven 标签
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
}
public BeanDefinition parse(Element element, ParserContext parserContext) {
registerTransactionalEventListenerFactory(parserContext);
String mode = element.getAttribute("mode");
// AspectJ 另外处理
if ("aspectj".equals(mode)) {
// mode="aspectj"
registerTransactionAspect(element, parserContext);
if (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader())) {
registerJtaTransactionAspect(element, parserContext);
}
}
else {
// mode="proxy"
AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
}
return null;
}
private static class AopAutoProxyConfigurer {
public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
// 注册 InfrastructureAdvisorAutoProxyCreator 自动创建代理器
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
// txAdvisorBeanName = org.springframework.transaction.config.internalTransactionAdvisor
String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
Object eleSource = parserContext.extractSource(element);
// Create the TransactionAttributeSource definition.
// 创建 TransactionAttributeSource 的 bean
RootBeanDefinition sourceDef = new RootBeanDefinition(
"org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
// 注册 bean,并使用 Spring 中的定义规则生成 beanName
String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
// 创建 TransactionInterceptor 的 bean
RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
// 创建 TransactionAttributeSourceAdvisor 的 bean
RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
// 将 sourceName 的 bean 注入 advisor 的 transactionAttributeSource 属性中
advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
// 将 interceptorName 的 bean 注入到 advisor 的 adviceBeanName 属性中
advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
if (element.hasAttribute("order")) {
// 如果配置了 order 属性,则加入到 bean 中
advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
}
// 以 txAdvisorBeanName 名字注册 advisorDef
parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);
// 创建 CompositeComponentDefinition
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
parserContext.registerComponent(compositeDef);
}
}
}
public static void registerAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
registerComponentIfNecessary(beanDefinition, parserContext);
}
private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {
if (beanDefinition != null) {
// 注册的 beanName 是 org.springframework.aop.config.internalAutoProxyCreator
parserContext.registerComponent(
new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME));
}
}
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
// 组装 key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 如果适合被代理,则需要封装指定的 bean
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
public static boolean canApply(Advisor advisor, Class> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}
private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
@Override
@Nullable
protected TransactionAttributeSource getTransactionAttributeSource() {
// 实现父类的方法,在子类中进行了扩展,返回之前在标签注册时的AnnotationTransactionAttributeSource
return transactionAttributeSource;
}
};
匹配标签 match
@Override
public boolean matches(Method method, Class> targetClass) {
// 事务切点匹配的方法
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
AnnotationTransactionAttributeSource#determineTransactionAttribute
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
for (TransactionAnnotationParser parser : this.annotationParsers) {
TransactionAttribute attr = parser.parseTransactionAnnotation(element);
if (attr != null) {
return attr;
}
}
return null;
}
SpringTransactionAnnotationParser#parseTransactionAnnotation(AnnotatedElement)
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
// 解析事务注解的属性
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
element, Transactional.class, false, false);
if (attributes != null) {
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
// 注释 9.4 解析事务注解的每一个属性
Propagation propagation = attributes.getEnum("propagation");
rbta.setPropagationBehavior(propagation.value());
Isolation isolation = attributes.getEnum("isolation");
rbta.setIsolationLevel(isolation.value());
rbta.setTimeout(attributes.getNumber("timeout").intValue());
rbta.setReadOnly(attributes.getBoolean("readOnly"));
rbta.setQualifier(attributes.getString("value"));
List
小结
public Object invoke(MethodInvocation invocation) throws Throwable {
// 注释 9.5 执行事务拦截器,完成整个事务的逻辑
Class> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
protected Object invokeWithinTransaction(Method method, @Nullable Class> targetClass,
final InvocationCallback invocation) throws Throwable {
// 如果transaction属性为null,则该方法是非事务性的
TransactionAttributeSource tas = getTransactionAttributeSource();
// 获取对应事务属性
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
// 获取事务管理器
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
// 构造方法唯一标识(类.方法)
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 声明式事务处理
// 标准事务划分 : 使用 getTransaction 和 commit / rollback 调用
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
//传入的是回调函数对象: invocation.proceed。 执行被增强的方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 异常回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 清除信息
cleanupTransactionInfo(txInfo);
}
// 提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
// 编程式事务处理
final ThrowableHolder throwableHolder = new ThrowableHolder();
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
...
return result;
}
}
}
protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
// Do not attempt to lookup tx manager if no tx attributes are set
// 注释 9.6 寻找事务管理器
if (txAttr == null || this.beanFactory == null) {
// 如果没有事务属性或者 BeanFactory 为空时,从缓存里面寻找
return asPlatformTransactionManager(getTransactionManager());
}
String qualifier = txAttr.getQualifier();
// 如果注解配置中指定了事务管理器,直接取出使用
if (StringUtils.hasText(qualifier)) {
return determineQualifiedTransactionManager(this.beanFactory, qualifier);
}
else if (StringUtils.hasText(this.transactionManagerBeanName)) {
return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
}
else {
// 上面步骤都没找到,最后才去容器中,根据 className 来寻找
PlatformTransactionManager defaultTransactionManager = asPlatformTransactionManager(getTransactionManager());
...
return defaultTransactionManager;
}
}
protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
// 如果没有名称指定则使用方法唯一标识,并使用 DelegatingTransactionAttribute 包装 txAttr
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
// 获取 TransactionStatus
status = tm.getTransaction(txAttr);
}
}
// 根据指定的属性与 status 准备一个 TransactionInfo
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
// 如果当前线程已经记录数据库链接则使用原有链接
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
// false 表示非新创建连接
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
private static final ThreadLocal
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
// 当前线程存在事务,分情况进行处理
return handleExistingTransaction(def, transaction, debugEnabled);
}
PROPAGATION_NEVER
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
PROPAGATION_NOT_SUPPORTED
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
事务挂起
protected Object doSuspend(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// 将数据库连接设置为 null
txObject.setConnectionHolder(null);
return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
private static Object doUnbindResource(Object actualKey) {
Map
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 新事务的建立
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
// 嵌入式事务的处理
if (useSavepointForNestedTransaction()) {
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
// 创建 savepoint
status.createAndHoldSavepoint();
return status;
}
}
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
// 设置隔离级别
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// configured the connection pool to set it already).
// 更改自动提交设置,由 spring 进行控制
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
con.setAutoCommit(false);
}
// 准备事务连接
prepareTransactionalConnection(con, definition);
// 设置判断当前线程是否存在事务的依据
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
// 将当前获取到的连接绑定到当前线程
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
}
结论:Spring 事务的开启,就是将数据库自动提交属性设置为 false
小结
tas.getTransactionAttribute(method, targetClass)
TransactionManager
:determineTransactionManager(txAttr);
createTransactionIfNecessary(tm, txAttr, joinpointIdentification)
invocation.proceed()
completeTransactionAfterThrowing(txInfo, ex);
cleanupTransactionInfo(txInfo)
commitTransactionAfterReturning(txInfo)
事务回滚 & 提交
API
,所以没有细说。总结
Spring
的事务,以及在代码中如何实现。@Transactional
,不了解其它属性设置的含义,也不知道在默认配置下,如果是同一个类中的方法自调用是不支持事务。PROPAGATION
,让事务支持方法自调用,还有设置事务超时时间timeout
、隔离级别等属性。看到这儿了,说明你是个坚持努力的人,求个点赞关注分享,顺便关注下公众号,里面有4000G免费视频资源,等你来拿哦。


聊聊rocketmq-mysql的ColumnParser
- 上一篇
- 聊聊rocketmq-mysql的ColumnParser
- 下一篇
- CRM是怎样帮助企业带来效益的?
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 6116次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 6531次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 6345次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 8307次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 6938次使用
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- 分享Redis高可用架构设计实践
- 2023-01-24 286浏览
-
- 搞一个自娱自乐的博客(二) 架构搭建
- 2023-02-16 244浏览
-
- B-Tree、B+Tree以及B-link Tree
- 2023-01-19 235浏览
-
- mysql面试题
- 2023-01-17 157浏览

