一、前言
二、目标
三、设计
四、实现
五、测试
六、总结
一、前言
时间再多一件事情也不可能做的完美,但总有时间做完一件事情!
在我们的生活中,要经历很多重要的阶段,包括;高考、求婚、面试、述职,在所有事情发生之前,我们都在做着大量的准备,甚至像为了高考的这样的时间点,要准备好几年。可即使有这样的大量的准备时间,我们几乎也没有办法把最终的结果做到完美,只能是把结果做完。没有遗憾的人生,才是遗憾!
其实我们的系统设计开发也是一样的,系统越做越复杂,功能越做越多,但人的智力是有上限的,永远无法完全评估未发生的事情。所以对于一个复杂的分布式系统,我们几乎永远不可能找到并修复所有的 bug,有时候解决方法也不是完全找出所有问题并消灭,而是能容忍部分小问题,并在这些问题发生时可以自动恢复,做到最终补偿处理。而这样的设计也称为高可用和弹性设计。
二、目标
前面各个章节的内容,渐进式的逐步为我们实现了一个基本的框架结构,能满足我们对一个 DAO 方法的查询操作,以及处理对应的参数和返回结果。
那么目前这个框架中所提供的 SQL 处理仅有一个 select 查询操作,还没有其他我们日常常用的 insert、update、delete,以及 select 查询返回的集合类型数据。
其实这一部分新增处理 SQL 的内容,也就是在 SqlSession 需要定义新的接口,通知让这些接口被映射器类方法 MapperMethod 进行调用处理。如图 12-1 所示 SqlSession 定义操作SQL方法
图 12-1 SqlSession 定义操作SQL方法
结合着我们目前框架的开发结构,对于扩展 insert/update/delete 这部分功能来说,并不会太复杂的。因为从 XML 对方法的解析、参数的处理、结果的封装,都已经是成型的结构。而我们只是对把这部分新增逻辑从前到后串联到 ORM 框架中就可以实现对数据库的新增、修改和删除操作了。
所以读者在阅读这部分代码的时候,可以结合 XMLMapperBuilder 新增解析 insert/update/delete 和 SqlSession 调用执行入口进行代码调试,这样就能串联出整个功能链路了。
三、设计
假定这就是你正在承接的业务功能需求,你要在现在有的框架中完成对 insert/update/delete 方法的扩展。那么这个时候你需要思考,哪里是这个流程的开始,之后从流程的开始进行梳理。
那么这里显而易见,我们需要首先解决的是对 XML 的解析,由于之前在 ORM 框架的开发中,仅是处理了 select 的 SQL 信息,现在则需要把 insert/update/delete 的语句也按照解析 select 的方式进行处理。如图 12-2 所示,新增解析内容
图 12-2 新增解析内容
在添加了解析新类型 SQL 操作前提下,后续 DefaultSqlSession 中新增的执行 SQL 方法 insert/update/delete 就可以通过 Configuration 配置项拿到对应的映射器语句,并执行后续的处理流程。具体设计,如图 12-3 所示,解析XML 并处理 SQL 语句
图 12-3 解析XML 并处理 SQL 语句
在执行 sqlSession.getMapper(IUserDao.class) 获取 Mapper 以后,后续的流程会依次串联到映射器工厂、映射器,以及获取对应的映射器方法,从 MapperMethod 映射器方法开始,调用的就是 DefaultSqlSession 了。
那么这里要注意,除了我们已经开发完的 DefaultSqlSession#select 方法,其他定义的 insert、delete、update,都是调用内部的 update 方法,这也是 Mybatis ORM 框架对此类语句处理的一个包装。因为除了 select 方法,insert、delete、update,都是共性处理逻辑,所以可以被包装成一个逻辑来处理。
四、实现
1. 工程结构
mybatis-step-11 └── src ├── main │ └── java │ └── cn.bugstack.mybatis │ ├── binding │ │ ├── MapperMethod.java │ │ ├── MapperProxy.java │ │ ├── MapperProxyFactory.java │ │ └── MapperRegistry.java │ ├── builder │ │ ├── xml │ │ │ ├── XMLConfigBuilder.java │ │ │ ├── XMLMapperBuilder.java │ │ │ └── XMLStatementBuilder.java │ │ ├── BaseBuilder.java │ │ ├── MapperBuilderAssistant.java │ │ ├── ParameterExpression.java │ │ ├── SqlSourceBuilder.java │ │ └── StaticSqlSource.java │ ├── datasource │ ├── executor │ │ ├── parameter │ │ │ └── ParameterHandler.java │ │ ├── result │ │ │ ├── DefaultResultContext.java │ │ │ └── DefaultResultHandler.java │ │ ├── resultset │ │ │ ├── DefaultResultSetHandler.java │ │ │ └── ResultSetHandler.java │ │ │ └── ResultSetWrapper.java │ │ ├── statement │ │ │ ├── BaseStatementHandler.java │ │ │ ├── PreparedStatementHandler.java │ │ │ ├── SimpleStatementHandler.java │ │ │ └── StatementHandler.java │ │ ├── BaseExecutor.java │ │ ├── Executor.java │ │ └── SimpleExecutor.java │ ├── io │ ├── mapping │ │ ├── BoundSql.java │ │ ├── Environment.java │ │ ├── MappedStatement.java │ │ ├── ParameterMapping.java │ │ ├── ResultMap.java │ │ ├── ResultMapping.java │ │ ├── SqlCommandType.java │ │ └── SqlSource.java │ ├── parsing │ ├── reflection │ ├── session │ │ ├── defaults │ │ │ ├── DefaultSqlSession.java │ │ │ └── DefaultSqlSessionFactory.java │ │ ├── Configuration.java │ │ ├── ResultContext.java │ │ ├── ResultHandler.java │ │ ├── RowBounds.java │ │ ├── SqlSession.java │ │ ├── SqlSessionFactory.java │ │ ├── SqlSessionFactoryBuilder.java │ │ └── TransactionIsolationLevel.java │ ├── transaction │ └── type └── test ├── java │ └── cn.bugstack.mybatis.test.dao │ ├── dao │ │ └── IUserDao.java │ ├── po │ │ └── User.java │ └── ApiTest.java └── resources ├── mapper │ └──User_Mapper.xml └── mybatis-config-datasource.xml
工程源码 :公众号「bugstack虫洞栈」,回复:手写Mybatis,获取完整源码
完善ORM框架,增删改查操作核心类关系,如图 12-4 所示
图 12-4 完善ORM框架,增删改查操作核心类关系
首先在 XML 映射器构建器中,扩展 XMLMapperBuilder#configurationElement 方法,添加对 insert/update/delete 的解析操作。这部分不需要太多的处理,只要添加上解析类型,就能满足当前章节的诉求。同样这里的解析信息都会存放到 Configuration 配置项的映射语句Map集合 mappedStatements 中,供后续 DefaultSqlSession 执行SQL获取配置信息时使用。
接下来是对 MapperMethod 映射器方法的改造,在前面章节我们只是处理了 MapperMethod#execute 中 SELECT 类型的语句,这一章节需要在这里扩展 INSERT、DELETE、UPDATE,同时还需要对 SELECT 进行扩展查询出多个结果集的方法。
所需要扩展的这些信息,都是有 DefaultSqlSession 调用执行器 Executor 进行处理的,这里你会看到 Executor 中只有 update 这个新增方法,并没有 insert、delete,因为这两个方法也是调用的 update 进行处理的。
以上这些内容实现完成后,所有新增方法的调用,都会按照前面章节实现的语句执行、参数处理、结果封装等步骤,把流程执行完毕,并返回最终的结果。
2. 扩展解析元素
首先我们需要先解决新增 SQL 类型的 XML 语句,把 insert、update、delete,几种类型的 SQL 解析完成后,存放到 Configuration 配置项的映射器语句中。
源码详见 :cn.bugstack.mybatis.builder.xml.XMLMapperBuilder
public class XMLMapperBuilder extends BaseBuilder { // 省略部分未改变代码,可参考对应的源码学习... // 配置mapper元素 // <mapper namespace="org.mybatis.example.BlogMapper"> // <select id="selectBlog" parameterType="int" resultType="Blog"> // select * from Blog where id = #{id} // </select> // </mapper> private void configurationElement (Element element) { // 1.配置namespace String namespace = element.attributeValue("namespace" ); if (namespace.equals("" )) { throw new RuntimeException("Mapper's namespace cannot be empty" ); } builderAssistant.setCurrentNamespace(namespace); // 2.配置select|insert|update|delete buildStatementFromContext(element.elements("select" ), element.elements("insert" ), element.elements("update" ), element.elements("delete" ) ); } // 配置select|insert|update|delete @SafeVarargs private final void buildStatementFromContext (List<Element>... lists) { for (List<Element> list : lists) { for (Element element : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, element); statementParser.parseStatementNode(); } } } }
与前面章节相比,这里改造 buildStatementFromContext 方法的入参类型为 list 的集合,也就是处理所传递到方法中的所有语句的集合。
之后在 XMLMapperBuilder#configurationElement 调用层,传递 element.elements("select")、element.elements("insert")、element.elements("update")、element.elements("delete") 四个类型的方法,就可以把配置到 Mapper XML 中的不同 SQL 解析存放起来了。
3. 新增执行方法
在 Mybatis 的 ORM 框架中,DefaultSqlSession 中最终的 SQL 执行都会调用到 Executor 执行器的,所以这里我们先来看下关于执行器中新增方法的变化。
3.1 update接口定义
源码详见 :cn.bugstack.mybatis.executor.Executor
public interface Executor { ResultHandler NO_RESULT_HANDLER = null ; int update (MappedStatement ms, Object parameter) throws SQLException ; <E> List<E> query (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException ; // ...省略部分代码 }
update 是 Executor 执行接口新增的方法,在这次功能扩展中,Executor 执行器也就只增加了这么一个 update 方法。因为其他两个方法 insert、delete 的调用,也都是调用 update 就够了,所以这里 Mybatis 并没有在执行器中定义新的方法。
3.3 update接口实现
源码详见 :cn.bugstack.mybatis.executor.SimpleExecutor
public class SimpleExecutor extends BaseExecutor { public SimpleExecutor (Configuration configuration, Transaction transaction) { super (configuration, transaction); } @Override protected int doUpdate (MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null ; try { Configuration configuration = ms.getConfiguration(); // 新建一个 StatementHandler StatementHandler handler = configuration.newStatementHandler(this , ms, parameter, RowBounds.DEFAULT, null , null ); // 准备语句 stmt = prepareStatement(handler); // StatementHandler.update return handler.update(stmt); } finally { closeStatement(stmt); } } @Override protected <E> List<E> doQuery (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null ; try { Configuration configuration = ms.getConfiguration(); // 新建一个 StatementHandler StatementHandler handler = configuration.newStatementHandler(this , ms, parameter, rowBounds, resultHandler, boundSql); // 准备语句 stmt = prepareStatement(handler); // 返回结果 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } }
SimpleExecutor#doUpdate 方法是 BaseExecutor 抽象类实现 Executor#update 接口后,定义的抽象方法。
这个抽象方法中,和 doQuery 方法几乎类似,都是创建一个新的 StatementHandler 语句处理器,之后准备语句,执行处理。
但这里有一点需要注意,doUpdate 创建 StatementHandler 语句处理器的时候,是没有 resultHandler、boundSql 两个参数的,所以在创建的过程中,是需要对有必要使用的 boundSql 进行判断处理的。 这部分内容主要体现在 BaseStatementHandler 的构造函数中,关于 boundSql 的判断和实例化处理
3.4 语句处理器实现
语句处理器的实现,主要变化在 BaseStatementHandler 的构造函数中添加了 boundSql 的初始化,代码如下;
public abstract class BaseStatementHandler implements StatementHandler { // ... 省略部分代码 protected BoundSql boundSql; public BaseStatementHandler (Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 新增判断,因为 update 不会传入 boundSql 参数,所以这里要做初始化处理 if (boundSql == null ) { boundSql = mappedStatement.getBoundSql(parameterObject); } } }
因为只有获取了 BoundSql 的参数,才能方便的执行后续对 SQL 处理的操作。所以在执行 update 方法,没有传入 BoundSql 的时候,则需要这里进行判断以及自己获取的处理操作。接下来是对抽象类的实现,具体的处理 update 方法。
源码详见 :cn.bugstack.mybatis.executor.statement.PreparedStatementHandler
public class PreparedStatementHandler extends BaseStatementHandler { @Override public int update (Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return ps.getUpdateCount(); } @Override public <E> List<E> query (Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); } // ... 省略部分代码 }
在 PreparedStatementHandler 预处理语句处理器中,实现了 update 方法,相对于 query 方法的实现,其实只是相当于 JDBC 操作数据库返回结果集的变化,update 处理要返回 SQL 的操作影响了多少条数据的数量。
4. SqlSession 定义和实现CRUD接口
在 SqlSession 中需要新增出处理数据库的接口,包括:selectList、insert、update、delete,这里我们来看下 DefaultSqlSession 对 SqlSession 接口方法的具体实现。
源码详见 :cn.bugstack.mybatis.session.defaults.DefaultSqlSession
public class DefaultSqlSession implements SqlSession { private Logger logger = LoggerFactory.getLogger(DefaultSqlSession.class ) ; private Configuration configuration; private Executor executor; public DefaultSqlSession (Configuration configuration, Executor executor) { this .configuration = configuration; this .executor = executor; } @Override public <T> T selectOne (String statement) { return this .selectOne(statement, null ); } @Override public <T> T selectOne (String statement, Object parameter) { List<T> list = this .<T>selectList(statement, parameter); if (list.size() == 1 ) { return list.get(0 ); } else if (list.size() > 1 ) { throw new RuntimeException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null ; } } @Override public <E> List<E> selectList (String statement, Object parameter) { logger.info("执行查询 statement:{} parameter:{}" , statement, JSON.toJSONString(parameter)); MappedStatement ms = configuration.getMappedStatement(statement); try { return executor.query(ms, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, ms.getSqlSource().getBoundSql(parameter)); } catch (SQLException e) { throw new RuntimeException("Error querying database. Cause: " + e); } } @Override public int insert (String statement, Object parameter) { // 在 Mybatis 中 insert 调用的是 update return update(statement, parameter); } @Override public int update (String statement, Object parameter) { MappedStatement ms = configuration.getMappedStatement(statement); try { return executor.update(ms, parameter); } catch (SQLException e) { throw new RuntimeException("Error updating database. Cause: " + e); } } @Override public Object delete (String statement, Object parameter) { return update(statement, parameter); } // ... 省略部分代码 }
在 DefaultSqlSession 的具体实现中可以看到,update 方法调用了具体的执行器封装成方法以后,insert、delete 都是调用的这个 update 方法进行操作的。 接口定义的是单一执行,接口实现是做了适配封装
另外这里单独提供了 selectList 方法,所以把之前在 selectOne 关于 executor.query 的执行处理,都迁移到 selectList 方法中。之后在 selectOne 中调用 selectList 方法,并给出相应的判断处理。
5. 映射器命令执行调度
以上这些所实现的语句执行器、SqlSession 包装,最终都会交给 MapperMethod 映射器方法根据不同的 SQL 命令调用不同的 SqlSession 方法进行执行。
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod (Class<?> mapperInterface, Method method, Configuration configuration) { this .command = new SqlCommand(configuration, mapperInterface, method); this .method = new MethodSignature(configuration, method); } public Object execute (SqlSession sqlSession, Object[] args) { Object result = null ; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.insert(command.getName(), param); break ; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.delete(command.getName(), param); break ; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.update(command.getName(), param); break ; } case SELECT: { Object param = method.convertArgsToSqlCommandParam(args); if (method.returnsMany) { result = sqlSession.selectList(command.getName(), param); } else { result = sqlSession.selectOne(command.getName(), param); } break ; } default : throw new RuntimeException("Unknown execution method for: " + command.getName()); } return result; } // 省略 SQL指令和方法前面代码块,可以参考源码 }
映射器方法 MapperMethod#execute 会根据不同的 SqlCommand 指令调用到不同的方法上去,INSERT、DELETE、UPDATE 分别按照对应的方法调用即可。这里 SELECT 进行了扩展,因为需要按照不同的方法出参类型,调用不同的方法,主要是 selectList、selectOne 的区别。
另外这里 method.returnsMany 来自于 MapperMethod.MethodSignature 方法签名中进行通过,返回类型进行获取的,代码如图 12-5 所示。
图 12-5 方法签名获取方法的返回参数类型
五、测试
1. 事先准备
1.1 创建库表
创建一个数据库名称为 mybatis 并在库中创建表 user 以及添加测试数据,如下:
CREATE TABLE USER ( id bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID' , userId VARCHAR (9 ) COMMENT '用户ID' , userHead VARCHAR (16 ) COMMENT '用户头像' , createTime TIMESTAMP NULL COMMENT '创建时间' , updateTime TIMESTAMP NULL COMMENT '更新时间' , userName VARCHAR (64 ), PRIMARY KEY (id ) ) ENGINE =InnoDB DEFAULT CHARSET =utf8; insert into user (id , userId, userHead, createTime, updateTime, userName) values (1 , '10001' , '1_04' , '2022-04-13 00:00:00' , '2022-04-13 00:00:00' , '小傅哥' );
1.2 配置数据源
<environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="com.mysql.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8" /> <property name ="username" value ="root" /> <property name ="password" value ="123456" /> </dataSource > </environment > </environments >
通过 mybatis-config-datasource.xml 配置数据源信息,包括:driver、url、username、password
在这里 dataSource 可以按需配置成 DRUID、UNPOOLED 和 POOLED 进行测试验证。
1.3 配置Mapper
<select id ="queryUserInfoById" parameterType ="java.lang.Long" resultType ="cn.bugstack.mybatis.test.po.User" > SELECT id, userId, userName, userHead FROM user where id = #{id}</select > <select id ="queryUserInfo" parameterType ="cn.bugstack.mybatis.test.po.User" resultType ="cn.bugstack.mybatis.test.po.User" > SELECT id, userId, userName, userHead FROM user where id = #{id} and userId = #{userId}</select > <select id ="queryUserInfoList" resultType ="cn.bugstack.mybatis.test.po.User" > SELECT id, userId, userName, userHead FROM user</select > <update id ="updateUserInfo" parameterType ="cn.bugstack.mybatis.test.po.User" > UPDATE user SET userName = #{userName} WHERE id = #{id}</update > <insert id ="insertUserInfo" parameterType ="cn.bugstack.mybatis.test.po.User" > INSERT INTO user (userId, userName, userHead, createTime, updateTime) VALUES (#{userId}, #{userName}, #{userHead}, now(), now())</insert > <delete id ="deleteUserInfoByUserId" parameterType ="java.lang.String" > DELETE FROM user WHERE userId = #{userId}</delete >
本章节已经把 ORM 框架的基本功能全部开发完成了,所以这里可以分别配置测试不同类型的 SQL 语句,包括:insert、delete、update、select
2. 单元测试
IUserDao 配置相应的方法,与 Mapper XML 匹配。
public interface IUserDao { User queryUserInfoById (Long id) ; User queryUserInfo (User req) ; List<User> queryUserInfoList () ; int updateUserInfo (User req) ; void insertUserInfo (User req) ; int deleteUserInfoByUserId (String userId) ; }
2.1 插入测试
@Test public void test_insertUserInfo () { // 1. 获取映射器对象 IUserDao userDao = sqlSession.getMapper(IUserDao.class ) ; // 2. 测试验证 User user = new User(); user.setUserId("10002" ); user.setUserName("小白" ); user.setUserHead("1_05" ); userDao.insertUserInfo(user); logger.info("测试结果:{}" , "Insert OK" ); // 3. 提交事务 sqlSession.commit(); }
测试结果
14 :45 :25.166 [main] INFO c.b.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"10002" 14 :45 :25.166 [main] INFO c.b.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"小白" 14 :45 :25.166 [main] INFO c.b.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"1_05" 14 :45 :25.171 [main] INFO cn.bugstack.mybatis.test.ApiTest - 测试结果:Insert OK Process finished with exit code 0
从测试日志信息和数据库的截图,可以看到数据已经插入到数据库,验证通过。
另外这里需要注意,我们执行完 SQL 以后,还执行力一次 sqlSession.commit(); 这是因为在 DefaultSqlSessionFactory#openSession 开启 Session 创建事务工厂的时候,传入给事务工厂构造函数的事务是否自动提交为 false 所以这里就需要我们自己去手动提交事务,否则是不会插入到数据库的。下面几个测试也是同样的处理方式。
2.2 查询测试(多条数据)
@Test public void test_queryUserInfoList () { // 1. 获取映射器对象 IUserDao userDao = sqlSession.getMapper(IUserDao.class ) ; // 2. 测试验证:对象参数 List<User> users = userDao.queryUserInfoList(); logger.info("测试结果:{}" , JSON.toJSONString(users)); }
测试结果
14 :50 :19.063 [main] INFO cn.bugstack.mybatis.test.ApiTest - 测试结果:[{"id" :1 ,"userHead" :"1_04" ,"userId" :"10001" ,"userName" :"小傅哥" },{"id" :4 ,"userHead" :"1_05" ,"userId" :"10002" ,"userName" :"小白" }] Process finished with exit code 0
现在我们再查询结果的时候,就可以查询到2条记录的集合了,这说明我们添加的 MapperMethod#execute 调用 sqlSession.selectList(command.getName(), param); 是测试通过的。读者伙伴也可以根据这个测试的代码,进行断掉调试。
2.3 修改测试
@Test public void test_updateUserInfo () { // 1. 获取映射器对象 IUserDao userDao = sqlSession.getMapper(IUserDao.class ) ; // 2. 测试验证 int count = userDao.updateUserInfo(new User(1L , "10001" , "叮当猫" )); logger.info("测试结果:{}" , count); // 3. 提交事务 sqlSession.commit(); }
测试结果
14 :52 :09.550 [main] INFO c.b.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"叮当猫" 14 :52 :09.550 [main] INFO c.b.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:1 14 :52 :09.553 [main] INFO cn.bugstack.mybatis.test.ApiTest - 测试结果:1
这里测试验证把ID=1的用户,userName 修改为 叮当猫,通过测试日志和数据库截图,可以看出,测试已经通过。
2.4 删除测试
@Test public void test_deleteUserInfoByUserId () { // 1. 获取映射器对象 IUserDao userDao = sqlSession.getMapper(IUserDao.class ) ; // 2. 测试验证 int count = userDao.deleteUserInfoByUserId("10002" ); logger.info("测试结果:{}" , count == 1 ); // 3. 提交事务 sqlSession.commit(); }
测试结果
14 :57 :39.536 [main] INFO c.b.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"10002" 14 :57 :39.539 [main] INFO cn.bugstack.mybatis.test.ApiTest - 测试结果:true Process finished with exit code 0
这里我们把数据库表中 userId = ”10002“ 的用户删除掉,通过测试日志和数据库截图,可以看出测试通过。
六、总结
到本章节我们就把 Mybatis 的全部主干流程串联实现完成了,可以执行对数据库的增删改查操作,读者伙伴也可以发现,本章节在原有的内容下,进行扩展的时候也是非常方便的,甚至不要多大的代码改动。这主要也得益于框架在设计实现过程中,合理运用设计原则和设计模式的好处。
读者在学习的过程中,可以调试源码中的一些参数,比如像事务是否自动提交,查询出来的参数是否可以添加其他类型,在增删改查中,是否还有其他情况的处理。这些小的功能点,都可以进行添加练习,如果你能正确的添加并走完流程验证公国,那么说明你是真的学习会了。
在本章节全部基础功能链路串联完毕以后,关于 Mybatis 的框架中,还有一些额外扩展的知识点,比如:插入时返回当前ID、Map 类型映射、一级二级缓存、插件模块等,后续的章节中我们会找一些有代表性的内容,进行扩展开发学习。读者伙伴也可以按照目前的框架结构,自行添加这写模块进行练习学习。