手写MyBatis第34弹-深入MyBatis执行引擎:增删改操作如何影响数据库并返回结果

#AIcoding·八月创作之星挑战赛#

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥  有兴趣可以联系我。

我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。 

目录

正文

一、增删改操作的特殊性:与查询的本质区别

二、架构扩展:支持多种SQL命令类型

三、MapperProxy的增强:识别并路由增删改操作

四、Executor的update方法实现:处理增删改操作

五、主键回写:MyBatis的巧妙设计

六、事务处理与性能考量

七、总结与最佳实践


  1. MyBatis增删改查全解析:从注解到执行的完整链路实现

  2. 手写MyBatis的CRUD扩展:支持@Insert、@Update、@Delete注解的原理与实现

  3. 深入MyBatis执行引擎:增删改操作如何影响数据库并返回结果

  4. 数据库操作进阶:MyBatis如何优雅处理主键回写和受影响行数

  5. 从注解到SQL执行:揭秘MyBatis增删改操作的完整生命周期


正文

在前几篇文章中,我们实现了MyBatis的查询功能,搭建了基本的框架结构。然而,一个完整的ORM框架必须支持完整的CRUD(增删改查)操作。今天,我们将深入探讨MyBatis如何支持@Insert@Update@Delete注解以及XML中的增删改标签,并解析其中的关键技术细节。

一、增删改操作的特殊性:与查询的本质区别

增删改操作与查询操作在多个层面存在根本性差异:

  1. 返回值类型:查询操作返回结果集对象,而增删改操作返回受影响的行数(int类型)

  2. SQL命令类型:需要明确区分SELECT、INSERT、UPDATE、DELETE四种操作类型

  3. 主键处理:插入操作通常需要处理自增主键的回写问题

  4. 事务敏感性:增删改操作对事务的要求更加严格

这些差异要求我们在框架设计中做出相应的扩展和调整。

二、架构扩展:支持多种SQL命令类型

首先,我们需要在架构层面支持不同的SQL命令类型。以下是增删改操作在MyBatis框架中的完整执行流程:


MapperProxySqlSessionExecutorStatementHandlerDatabase调用update/insert/delete方法执行update操作准备语句参数绑定执行SQL返回受影响行数返回结果处理主键生成(可选)返回受影响行数返回结果MapperProxySqlSessionExecutorStatementHandlerDatabase

第一步:定义SQL命令类型枚举

 public enum SqlCommandType {
     UNKNOWN, INSERT, UPDATE, DELETE, SELECT;
     
     public static SqlCommandType valueOf(Class<? extends Annotation> annotationType) {
         if (annotationType == Select.class) {
             return SELECT;
         } else if (annotationType == Insert.class) {
             return INSERT;
         } else if (annotationType == Update.class) {
             return UPDATE;
         } else if (annotationType == Delete.class) {
             return DELETE;
         }
         return UNKNOWN;
     }
 }

第二步:扩展MappedStatement记录命令类型

 public class MappedStatement {
     private String id;
     private SqlCommandType sqlCommandType;
     private String sql;
     private Class<?> parameterType;
     private Class<?> resultType;
     private boolean useGeneratedKeys = false;
     private String keyProperty;
     
     // 构造方法、getter、setter
 }
三、MapperProxy的增强:识别并路由增删改操作

MapperProxy中,我们需要根据方法注解类型来决定调用SqlSession的哪个方法:

 public class MapperProxy<T> implements InvocationHandler {
     // ... 其他代码
     
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         // 处理Object类的方法
         if (Object.class.equals(method.getDeclaringClass())) {
             return method.invoke(this, args);
         }
         
         String statementId = mapperInterface.getName() + "." + method.getName();
         MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(statementId);
         
         // 根据SQL命令类型路由到不同的SqlSession方法
         switch (ms.getSqlCommandType()) {
             case SELECT:
                 // 处理查询...
                 break;
             case INSERT:
             case UPDATE:
             case DELETE:
                 return executeUpdate(sqlSession, ms, args);
             default:
                 throw new RuntimeException("Unknown SQL command type: " + ms.getSqlCommandType());
         }
     }
     
     private Object executeUpdate(SqlSession sqlSession, MappedStatement ms, Object[] args) {
         Object parameter = getParameter(args);
         int affectedRows = sqlSession.update(ms.getId(), parameter);
         
         // 处理返回值类型
         Class<?> returnType = method.getReturnType();
         if (returnType == void.class || returnType == Void.class) {
             return null;
         } else if (returnType == int.class || returnType == Integer.class) {
             return affectedRows;
         } else if (returnType == boolean.class || returnType == Boolean.class) {
             return affectedRows > 0;
         } else {
             throw new RuntimeException("Unsupported return type for update operation: " + returnType);
         }
     }
 }
四、Executor的update方法实现:处理增删改操作

Executorupdate方法需要处理SQL执行并返回受影响的行数:

 public class SimpleExecutor extends BaseExecutor {
     
     @Override
     public int update(MappedStatement ms, Object parameter) {
         Statement stmt = null;
         try {
             Configuration configuration = ms.getConfiguration();
             StatementHandler handler = configuration.newStatementHandler(this, ms, parameter);
             
             stmt = prepareStatement(handler);
             int rows = handler.update(stmt);
             
             // 处理主键生成
             if (ms.isUseGeneratedKeys() && ms.getKeyProperty() != null) {
                 handleGeneratedKeys(ms, parameter, stmt);
             }
             
             return rows;
         } finally {
             closeStatement(stmt);
         }
     }
     
     private void handleGeneratedKeys(MappedStatement ms, Object parameter, Statement stmt) {
         try {
             ResultSet generatedKeys = stmt.getGeneratedKeys();
             if (generatedKeys.next()) {
                 Object key = generatedKeys.getObject(1);
                 
                 // 使用反射将生成的主键值设置到参数对象中
                 String keyProperty = ms.getKeyProperty();
                 setValueToObject(parameter, keyProperty, key);
             }
         } catch (SQLException e) {
             throw new RuntimeException("Error getting generated keys", e);
         }
     }
     
     private void setValueToObject(Object obj, String property, Object value) {
         // 使用反射设置属性值
         try {
             Field field = obj.getClass().getDeclaredField(property);
             field.setAccessible(true);
             field.set(obj, value);
         } catch (Exception e) {
             throw new RuntimeException("Error setting generated key to property: " + property, e);
         }
     }
 }
五、主键回写:MyBatis的巧妙设计

主键回写是插入操作中的一个重要特性,MyBatis提供了多种方式来处理:

1. XML配置方式:

 <insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
     INSERT INTO user(name, email) VALUES(#{name}, #{email})
 </insert>

2. 注解配置方式:

 @Insert("INSERT INTO user(name, email) VALUES(#{name}, #{email})")
 @Options(useGeneratedKeys = true, keyProperty = "id")
 int insertUser(User user);

3. SelectKey方式(适用于非自增主键):

 <insert id="insertUser" parameterType="User">
     <selectKey keyProperty="id" resultType="long" order="BEFORE">
         SELECT nextval('user_id_seq')
     </selectKey>
     INSERT INTO user(id, name, email) VALUES(#{id}, #{name}, #{email})
 </insert>
六、事务处理与性能考量

增删改操作对事务有更高的要求,我们需要确保:

  1. 事务一致性:在同一个SqlSession中的多个操作应该共享同一个事务

  2. 自动提交控制:根据配置决定是否自动提交事务

  3. 批量操作优化:支持批处理执行器提高批量操作的性能

 public class BatchExecutor extends SimpleExecutor {
     private List<Statement> statementList = new ArrayList<>();
     private List<BatchResult> batchResults = new ArrayList<>();
     
     @Override
     public int update(MappedStatement ms, Object parameter) {
         // 批量处理逻辑
         // ...
     }
     
     @Override
     public List<BatchResult> flushStatements() {
         // 执行批量操作
         // ...
     }
 }
七、总结与最佳实践

通过本次扩展,我们的MyBatis框架已经支持了完整的CRUD操作。在这个过程中,我们解决了几个关键问题:

  1. 命令类型识别:通过SqlCommandType区分不同操作

  2. 返回值处理:正确处理受影响行数和各种返回类型

  3. 主键回写:支持自动主键回写到实体对象

  4. 事务管理:确保增删改操作的事务一致性

最佳实践建议:

  • 对于插入操作,尽量使用useGeneratedKeys来简化主键处理

  • 批量操作时使用BatchExecutor显著提升性能

  • 明确方法的返回值类型(void、int、boolean),保持一致性

  • 在事务边界清晰的地方手动控制事务提交

增删改操作的实现不仅完善了框架功能,更让我们深入理解了MyBatis的设计哲学:通过简洁的API隐藏复杂的实现细节。这种设计使得开发者可以专注于业务逻辑,而无需关心底层的数据库操作细节。

下次当你使用@Insert注解时,不妨想一想背后的复杂流程:从注解解析到SQL生成,从参数绑定到主键回写,这一切都在MyBatis的精心设计下变得如此简单而优雅。


💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!

💖常来我家多看看,
📕我是程序员扣棣,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!

💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值