你这代码写的,咋这么轴呢!
创新互联公司专注于钦州企业网站建设,响应式网站开发,商城网站制作。钦州网站建设公司,为钦州等地区提供建站服务。全流程按需网站建设,专业设计,全程项目跟踪,创新互联公司专业和态度为您提供的服务
说到轴,让我想起初中上学时老师说的话:“你那脑瓜子,咋跟手焖子似的!”
东北话手焖子就是那种冬天戴的大棉手套,棉手套里的棉花都被压的又沉又硬的了,所以来比喻脑瓜子笨。
而写轴代码的大部分都是刚毕业没多久,或者刚开始工作的码农,毕竟经验不足经历不多,写出一些不太好维护的代码也情有可原。而那些绝对多数锻炼出来的老码农,其实代码的稳定程度、设计经验、缜密逻辑,都是相对来说要好很多的。当然一部分老码农,只是老了而已,代码还是那个代码!
所以企业招聘些年轻人,需要年轻的思想。但没必要嚯嚯只是头发没多少的老码农,否则谁来给你平稳落地你那些天马行空的想法呢!难道体验、稳定、流畅,不应该是更值得追求的,非得喜欢全是愣头青似的代码,写出几百个bug,造成大量资损和客诉,让老板觉得很爽?
上一章节,小傅哥带着大家细化的 XML 语句构建器,解耦在解析 XML 中的所需要处理的 Mapper 信息,包括;SQL、入参、出参、类型,并对这些信息进行记录到 ParameterMapping 参数映射处理类中。那么这个一章节我们将结合这部分参数的提取,对执行的 SQL 进行参数的自动化设置,而不是像我们之前那样把参数写成固定的,如图 10-1 所示:
图 10-1 硬编码参数设置
在流程上,通过 DefaultSqlSession#selectOne 方法调用执行器,并通过预处理语句处理器 PreparedStatementHandler 执行参数设置和结果查询。
那么这个流程中我们所处理的参数信息,也就是每个 SQL 执行时,那些?号需要被替换的地方,目前是通过硬编码的方式进行处理的。而这就是本章节需要解决的问题,如果只是硬编码完成参数设置,那么对于所有那些不同类型的参数就没法进行操作了。
所以本章节需要结合结合上一章节所完成的语句构建器对 SQL 参数信息的拆解,本章将会按照这些参数的解析,处理这里硬编码为自动化类型设置。针对不同类型的参数设置,这部分使用了什么设计模式呢?
这里可以思考下,参数的处理也就是通常我们使用 JDBC 直接操作数据库时,所使用 ps.setXxx(i, parameter);
设置的各类参数。那么在自动化解析 XML 中 SQL 拆分出所有的参数类型后,则应该根据不同的参数进行不同的类型设置,也就;Long 调用 ps.setLong、String 调用 ps.setString 所以这里需要使用策略模式,在解析 SQL 时按照不同的执行策略,封装进去类型处理器(也就是是实现 TypeHandler接口的过程)。整体设计如图 10-2 所示:
图 10-2 策略模式处理参数处理器
mybatis-step-09
└── 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
│ │ ├── ParameterExpression.java
│ │ ├── SqlSourceBuilder.java
│ │ └── StaticSqlSource.java
│ ├── datasource
│ ├── executor
│ │ ├── resultset
│ │ │ └── ParameterHandler.java
│ │ ├── resultset
│ │ │ ├── DefaultResultSetHandler.java
│ │ │ └── ResultSetHandler.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
│ │ ├── SqlCommandType.java
│ │ └── SqlSource.java
│ ├── parsing
│ ├── reflection
│ ├── scripting
│ │ ├── defaults
│ │ │ └── DefaultParameterHandler.java
│ │ ├── xmltags
│ │ │ ├── DynamicContext.java
│ │ │ ├── MixedSqlNode.java
│ │ │ ├── SqlNode.java
│ │ │ ├── StaticTextSqlNode.java
│ │ │ ├── XMLLanguageDriver.java
│ │ │ └── XMLScriptBuilder.java
│ │ ├── LanguageDriver.java
│ │ └── LanguageDriverRegistry.java
│ ├── session
│ │ ├── defaults
│ │ │ ├── DefaultSqlSession.java
│ │ │ └── DefaultSqlSessionFactory.java
│ │ ├── Configuration.java
│ │ ├── ResultHandler.java
│ │ ├── SqlSession.java
│ │ ├── SqlSessionFactory.java
│ │ ├── SqlSessionFactoryBuilder.java
│ │ └── TransactionIsolationLevel.java
│ ├── transaction
│ └── type
│ ├── BaseTypeHandler.java
│ ├── JdbcType.java
│ ├── LongTypeHandler.java
│ ├── StringTypeHandler.java
│ ├── TypeAliasRegistry.java
│ ├── TypeHandler.java
│ └── TypeHandlerRegistry.java
└── test
├── java
│ └── cn.bugstack.mybatis.test.dao
│ ├── dao
│ │ └── IUserDao.java
│ ├── po
│ │ └── User.java
│ └── ApiTest.java
└── resources
├── mapper
│ └──User_Mapper.xml
└── mybatis-config-datasource.xml
使用策略模式,处理参数处理器核心类关系,如图 10-3 所示:
图 10-3 使用策略模式,处理参数处理器核心类关系
核心处理主要分为三块;类型处理、参数设置、参数使用;
这里我们要先解决一个小问题,不知道读者在我们所实现的源码中,是否注意到这样一个参数的传递,如图 10-4:
图 10-4 参数设置时入参获取
JDK 反射调用方法操作固定方法入参
源码详见:cn.bugstack.mybatis.binding.MapperMethod
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result = null;
switch (command.getType()) {
case SELECT:
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
break;
default:
throw new RuntimeException("Unknown execution method for: " + command.getName());
}
return result;
}
/**
* 方法签名
*/
public static class MethodSignature {
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
// 如果没参数
return null;
} else if (paramCount == 1) {
return args[params.keySet().iterator().next().intValue()];
} else {
// 否则,返回一个ParamMap,修改参数名,参数名就是其位置
final Mapparam = new ParamMap
在映射器方法中 MapperMethod#execute 将原来的直接将参数 args 传递给 SqlSession#selectOne 方法,调整为转换后再传递对象。
其实这里的转换操作就是来自于 Method#getParameterTypes 对参数的获取和处理,与 args 进行比对。如果是单个参数,则直接返回参数
Tree 树结构下的对应节点值。非单个类型,则需要进行循环处理,这样转换后的参数才能被直接使用。
在 Mybatis 的源码包中,有一个 type 包,这个包下所提供的就是一套参数的处理策略集合。它通过定义类型处理器接口、由抽象模板实现并定义标准流程,到提取抽象方法交给子类实现,这些子类就是各个类型处理器的具体实现。
源码详见:cn.bugstack.mybatis.type.TypeHandler
public interface TypeHandler{
/**
* 设置参数
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}
源码详见:cn.bugstack.mybatis.type.BaseTypeHandler
public abstract class BaseTypeHandlerimplements TypeHandler {
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
// 定义抽象方法,由子类实现不同类型的属性设置
setNonNullParameter(ps, i, parameter, jdbcType);
}
protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}
源码详见:cn.bugstack.mybatis.type.*
/**
* @description Long类型处理器
*/
public class LongTypeHandler extends BaseTypeHandler{
@Override
protected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {
ps.setLong(i, parameter);
}
}
/**
* @description String类型处理器
*/
public class StringTypeHandler extends BaseTypeHandler{
@Override
protected void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
}
这里的接口实现举了个例子,分别是;LongTypeHandler、StringTypeHandler,在 Mybatis 源码中还有很多其他类型,这里我们暂时不需要实现那么多,只要清楚这个处理过程和编码方式即可。大家在学习中,可以尝试再添加几个其他类型,用于学习验证。
类型处理器注册机 TypeHandlerRegistry 是我们前面章节实现的,这里只需要在这个类结构下,注册新的类型就可以了。
源码详见:cn.bugstack.mybatis.type.TypeHandlerRegistry
public final class TypeHandlerRegistry {
private final Map> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);
private final Map>> TYPE_HANDLER_MAP = new HashMap<>();
private final Map, TypeHandler>> ALL_TYPE_HANDLERS_MAP = new HashMap<>();
public TypeHandlerRegistry() {
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
}
//...
}
相对于前面章节所完成的内容,这个章节需要对 SqlSourceBuilder 源码构建器中,创建参数映射 ParameterMapping 需要添加参数处理器的内容。因为只有这样才能方便的从参数映射中获取到对应类型的处理器进行使用。
那么就需要完善 ParameterMapping 添加 TypeHandler 属性信息,以及在 ParameterMappingTokenHandler#buildParameterMapping
处理参数映射时,构建出参数的映射。这一部分是在上一章节的实现过程中,细化的完善部分,如图 10-6:
图 10-6 构建参数映射
那么结合上一章节,这里我们开始扩展出类型的设置。同时注意 MetaClass 反射工具类的使用。
源码详见:cn.bugstack.mybatis.builder.SqlSourceBuilder
// 构建参数映射
private ParameterMapping buildParameterMapping(String content) {
// 先解析参数映射,就是转化成一个 HashMap | #{favouriteSection,jdbcType=VARCHAR}
MappropertiesMap = new ParameterExpression(content);
String property = propertiesMap.get("property");
Class> propertyType;
if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
} else if (property != null) {
MetaClass metaClass = MetaClass.forClass(parameterType);
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
} else {
propertyType = Object.class;
}
logger.info("构建参数映射 property:{} propertyType:{}", property, propertyType);
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
return builder.build();
}
参数构建完成后,就可以在 DefaultSqlSession#selectOne 调用时设置参数使用了。那么这里的链路关系;Executor#query - > SimpleExecutor#doQuery -> StatementHandler#parameterize -> PreparedStatementHandler#parameterize -> ParameterHandler#setParameters 到了 ParameterHandler#setParameters 就可以看到了根据参数的不同处理器循环设置参数。
源码详见:cn.bugstack.mybatis.scripting.defaults.DefaultParameterHandler
public class DefaultParameterHandler implements ParameterHandler {
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
ListparameterMappings = boundSql.getParameterMappings();
if (null != parameterMappings) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
String propertyName = parameterMapping.getProperty();
Object value;
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
// 通过 MetaObject.getValue 反射取得值设进去
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
JdbcType jdbcType = parameterMapping.getJdbcType();
// 设置参数
logger.info("根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:{}", JSON.toJSONString(value));
TypeHandler typeHandler = parameterMapping.getTypeHandler();
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
创建一个数据库名称为 mybatis 并在库中创建表 user 以及添加测试数据,如下:
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', '小傅哥');
源码详见:cn.bugstack.mybatis.test.ApiTest
@Before
public void init() throws IOException {
// 1. 从SqlSessionFactory中获取SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
sqlSession = sqlSessionFactory.openSession();
}
因为接下来我们需要验证两种不同入参的单元测试,分别来测试基本类型参数和对象类型参数。
@Test
public void test_queryUserInfoById() {
// 1. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 测试验证:基本参数
User user = userDao.queryUserInfoById(1L);
logger.info("测试结果:{}", JSON.toJSONString(user));
}
07:40:08.531 [main] INFO c.b.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:id propertyType:class java.lang.Long
07:40:08.598 [main] INFO c.b.m.s.defaults.DefaultSqlSession - 执行查询 statement:cn.bugstack.mybatis.test.dao.IUserDao.queryUserInfoById parameter:1
07:40:08.875 [main] INFO c.b.m.d.pooled.PooledDataSource - Created connection 183284570.
07:40:08.894 [main] INFO c.b.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:1
07:40:08.961本文名称:使用策略模式,调用参数处理器
分享地址:http://www.shufengxianlan.com/qtweb/news4/82554.html网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联