前言
可能领导也都觉得可能就是码农不爱说话,其实不爱说话是一方面,但还有另外一方面是有些领导对于码农提出的问题,给出的回复往往是:“你提出这个问题,你就要给出这个问题的解决办法!”
所以不同的岗位要执行各自的职责,无论领导、产品、研发、测试、业务,都应该环环相扣,各司其职,不过分包装结果,正确看待问题。才可能做出像苹果那样的优秀产品,而不是那么割裂的独立功能。
目标
在日常业务需求开发中,研发人员使用 Mybatis 框架的时候,除了可以基于 Mapper XML 方式配置执行 SQL 信息以外,也可以通过注解的方式在 DAO 接口方法上配置执行 SQL 语句。
大部分时候一些研发规范中,都比较倾向于将 SQL 语句维护在 XML 中,因为这样可以进行统一管理,也能在发包后需要做一些修改 SQL 配置进行测试和验证时,基于 XML 配置变更 SQL 语句。如果是基于方法注解那么就需要重新打包,上传部分文件或者全部文件,才能进行这样的验证了。
如下的用法
对应研发人员来说,日常使用 Mybatis 框架,对于 XML 和注解配置也都是可以共用的,主要基于配置文件 mappers 中,引入的是哪类资源。
在之前本章节之前我们只实现了 mapper 中是 resource=“mapper/User_Mapper.xml” 的配置类型,那么本章节因为需要支持注解配置 SQL 语句,所以这里还需要支持 class=“com.lm.mybatis.test.dao.IUserDao” 这样配置到 DAO 接口类上的方式,解析 SQL 语句。
设计
那么这一部分的代码逻辑变动,主要以 XMLConfigBuilder 配置构建器的 Mapper 解析开始,因为只有从这里开始才是判断一个 Mapper 到底是使用了 XML 配置还是注解配置。
通过注解配置执行SQL语句核心类关系。
XMLConfigBuilder 配置构建器是解析 Mapper 的入口,以这条流程线中的方法 mapperElement 开始,处理 XML 解析的基础上,扩展注解的解析处理。
那么这里会通过从 xml 读取到的 class 配置,通过 Configuration 配置项类的添加 Mapper 方法,启动解析注解类语句的操作。也就是在 MapperRegistry 映射器注册机,随着 Configuration 配置项调用 addMapper 时所做的注解解析操作。
1:脚本语言驱动器
LanguageDriver 脚本语言驱动,是我们在前面章节已经实现的功能,给配置在 Mapper XML 中的 SQL 语句进行解析创建 SqlSource 信息使用的。接口如下;
从方法的入参可以看到,script 的参数是 Element 用于解析 XML 文件的,那么本章节为了可以处理注解类型的 SQL 配置,则需要添加新的接口方法。
通过重载一个 createSqlSource 接口,把 script 的入参设置为 String 类型,来解析注解 SQL 的配置。具体的接口实现如下;
用于解析注解方式的 createSqlSource 方法,其与 XML 解析来说,更加简单了。因为这里不需要提供专门的 XML 脚本构建器。而是直接按照 SQL 的入参信息,创建 RawSqlSource 即可。
另外这里相对 Mybatis 源码是省略了部分流程的,把核心结构展示给读者。在学习的过程中掌握了主链路以后,也可以参照源码,来学习这部分的实现细则。
注解配置构建器
在 Mybatis 框架的实现中,有专门一个 annotations 注解包,来提供用于配置到 DAO 方法上的注解,这些注解包括了所有的增删改查操作,同时可以设定一些额外的返回参数等。
定义注解
本章节主要以带着读者贯穿整个使用注解下,Mybatis 对此类配置的处理和执行过程,所以我们这里只添加四个注解 @Insert、@Delete、@Update、@Select
在 Mybatis 框架中除了以上这四个注解,还有如;@Arg、@InsertProvider、@Param、@ResultMap 等注解参数配置。读者学习本章节以后,可以尝试参照 Mybatis 源码添加其他注解功能的开发,学习整个流程的处理过程。
配置解析
加载配置注解和提供解析方法
自定义注解的解析配置,主要在 MapperAnnotationBuilder 类中完成,整个类在构造函数中配置需要解析的注解,并提供解析方法处理语句的解析。而这个类的解析操作,基本都是基于 Method 来获取参数类型、返回类型、注解方法等,完成解析过程。
解析语句 Statement
整个解析的核心流程包括;根据 Method#getParameterTypes 方法获取入参类型、从 Configuration 配置项中获取默认的 LanguageDriver 脚本语言驱动、以及基于注解所提供的配置信息,也就是 value 值中的 SQL 来创建 SqlSource 语句。
Mapper XML 解析调用
注解配置、解析处理,这些工作完成以后,接下来就是把解析放到哪一环来处理了。在 Mybatis 的源码中,是基于 XML 配置构建器解析 Mapper 时候进行判断处理,是 xml 还是注解。如果是注解则会调用到 MapperRegistry#addMapper 方法,并开始执行解析注解的相关操作。
在 XMLConfigBuilder 配置构建器的 Mapper 解析处理中,根据从 XML 配置获取到的 resource、class 分别进行判断解析。
如果 resource 为空,mapperClass 不为空,则进行注解的解析处理。在这段代码中则是根据 mapperClass 获取对应的接口,并通过 Configuration#addMapper 方法,添加到配置项中。而这个 Mapper 的添加会调用到 MapperRegistry 进而调用注解解析操作。
解析注解调用
以 XMLConfigBuilder#mapperElement 解析调用 configuration.addMapper 方法开始,则会调用到 mapperRegistry.addMapper(type); 方法。那么接下来就到了处理注解类解析的操作。
在 addMapper 方法中,根据 Class 注册完映射器代理工厂后,则开始进行解析注解操作。这部分 MapperAnnotationBuilder 类的功能在前面已经讲解,到这里就把整个流程串联起来了。
测试
建一个数据库名称为 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', '小傅哥');
配置数据源
<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 进行测试验证。
配置Mapper加载方式
<mappers>
<!-- XML 配置
<mapper resource="mapper/User_Mapper.xml"/>
-->
<!--注解配置-->
<mapper class="cn.bugstack.mybatis.test.dao.IUserDao"/>
</mappers>
在前面章节都是通过 resource 方式配置 Mapper XML,本章节这里可以把这部分注释掉。通过 class 配置出 DAO 接口即可。
配置注解
public interface IUserDao {
@Select("SELECT id, userId, userName, userHead\n" +
"FROM user\n" +
"where id = #{id}")
User queryUserInfoById(Long id);
@Select("SELECT id, userId, userName, userHead\n" +
" FROM user\n" +
" where id = #{id}")
User queryUserInfo(User req);
@Select("SELECT id, userId, userName, userHead\n" +
"FROM user")
List<User> queryUserInfoList();
@Update("UPDATE user\n" +
"SET userName = #{userName}\n" +
"WHERE id = #{id}")
int updateUserInfo(User req);
@Insert("INSERT INTO user\n" +
"(userId, userName, userHead, createTime, updateTime)\n" +
"VALUES (#{userId}, #{userName}, #{userHead}, now(), now())")
void insertUserInfo(User req);
@Insert("DELETE FROM user WHERE userId = #{userId}")
int deleteUserInfoByUserId(String userId);
}
单元测试
@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();
}
测试结果
查询测试(多条数据)
@Test
public void test_queryUserInfoList() {
// 1. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 测试验证:对象参数
List<User> users = userDao.queryUserInfoList();
logger.info("测试结果:{}", JSON.toJSONString(users));
}
修改测试
@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();
}
删除测试
@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();
}
总结
本章节在原有解析 Mapper XML 的基础上扩展了使用注解方式的解析和处理,让整个框架功能更加完善。同时在扩展注解功能的结构时候,可以看到整个整合过程并不复杂,更多是类似模块式的拼装,通过开发出一个注解解析构建器,并在 Mapper 注册过程中完成调用和解析操作。所以如果一个框架的整体设计是完善的,那么在功能的迭代和添加过程中也会是非常清晰容易的。
好了到这里就结束了手写mybatis之通过注解配置执行SQL语句的学习,大家一定要跟着动手操作起来。需要源码的 可si我获取;