作者:小傅哥


博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!

一、前言

你这代码写的,咋这么轴呢!

说到轴,让我想起初中上学时老师说的话:“你那脑瓜子,咋跟手焖子似的!” 东北话手焖子就是那种冬天戴的大棉手套,棉手套里的棉花都被压的又沉又硬的了,所以来比喻脑瓜子笨。

而写轴代码的大部分都是刚毕业没多久,或者刚开始工作的码农,毕竟经验不足经历不多,写出一些不太好维护的代码也情有可原。而那些绝对多数锻炼出来的老码农,其实代码的稳定程度、设计经验、缜密逻辑,都是相对来说要好很多的。当然一部分老码农,只是老了而已,代码还是那个代码!

所以企业招聘些年轻人,需要年轻的思想。但没必要嚯嚯只是头发没多少的老码农,否则谁来给你平稳落地你那些天马行空的想法呢!难道体验、稳定、流畅,不应该是更值得追求的,非得喜欢全是愣头青似的代码,写出几百个bug,造成大量资损和客诉,让老板觉得很爽?

二、目标

上一章节,小傅哥带着大家细化的 XML 语句构建器,解耦在解析 XML 中的所需要处理的 Mapper 信息,包括;SQL、入参、出参、类型,并对这些信息进行记录到 ParameterMapping 参数映射处理类中。那么这个一章节我们将结合这部分参数的提取,对执行的 SQL 进行参数的自动化设置,而不是像我们之前那样把参数写成固定的,如图 10-1 所示

  • 在流程上,通过 DefaultSqlSession#selectOne 方法调用执行器,并通过预处理语句处理器 PreparedStatementHandler 执行参数设置和结果查询。
  • 那么这个流程中我们所处理的参数信息,也就是每个 SQL 执行时,那些?号 需要被替换的地方,目前是通过硬编码的方式进行处理的。而这就是本章节需要解决的问题,如果只是硬编码完成参数设置,那么对于所有那些不同类型的参数就没法进行操作了。
  • 所以本章节需要结合结合上一章节所完成的语句构建器对 SQL 参数信息的拆解,本章将会按照这些参数的解析,处理这里硬编码为自动化类型设置。针对不同类型的参数设置,这部分使用了什么设计模式呢?

三、设计

这里可以思考下,参数的处理也就是通常我们使用 JDBC 直接操作数据库时,所使用 ps.setXxx(i, parameter); 设置的各类参数。那么在自动化解析 XML 中 SQL 拆分出所有的参数类型后,则应该根据不同的参数进行不同的类型设置,也就;Long 调用 ps.setLongString 调用 ps.setString 所以这里需要使用策略模式,在解析 SQL 时按照不同的执行策略,封装进去类型处理器(也就是是实现 TypeHandler 接口的过程)。整体设计如图 10-2 所示

  • 其实关于参数的处理,因为有很多的类型(Long\String\Object\...),所以这里最重要的体现则是策略模式的使用。
  • 这里包括了构建参数时根据类型,选择对应的策略类型处理器,填充到参数映射集合中。另外一方面是参数的使用,也就是在执行 DefaultSqlSession#selectOne 的链路中,包括了参数的设置,按照参数的不同类型,获取出对应的处理器,以及入参值。注意:由于入参值可能是一个对象中的属性,所以这里我们用到了前面章节实现的反射类工具 MetaObject 进行值的获取,避免由于动态的对象,没法硬编码获取属性值。

四、实现

1. 工程结构

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

工程源码https://github.com/fuzhengwei/small-mybatis

使用策略模式,处理参数处理器核心类关系,如图 10-3 所示

核心处理主要分为三块;类型处理、参数设置、参数使用;

  • 以定义 TypeHandler 类型处理器策略接口,实现不同的处理策略,包括;Long、String、Integer 等。这里我们先只实现2种类型,读者在学习过程中,可以按照这个结构来添加其他类型。
  • 类型策略处理器实现完成后,需要注册到处理器注册机中,后续其他模块参数的设置还是使用都是从 Configuration 中获取到 TypeHandlerRegistry 进行使用。
  • 那么有了这样的策略处理器以后,在进行操作解析 SQL 的时候,就可以按照不同的类型把对应的策略处理器设置到 BoundSql#parameterMappings 参数里,后续使用也是从这里进行获取。

2. 入参数校准

这里我们要先解决一个小问题,不知道读者在我们所实现的源码中,是否注意到这样一个参数的传递,如图 10-4

  • 这里的参数传递后,需要获取第0个参数,而且是硬编码固定的。这是为什么呢?这个第0个参数是哪来的,我们接口里面调用的方法,参数不是一个吗?就像:User queryUserInfoById(Long id);
  • 其实这个参数来自于映射器代理类 MapperProxy#invoke 中,因为 invoke 反射调用的方法,入参中是 Object[] args,所以这个参数被传递到后续的参数设置中。而我们的 DAO 测试类是一个已知的固定参数,所以后面硬编码了获取了第0个参数。

    • 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 Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
// 1.先加一个#{0},#{1},#{2}...参数
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// ...
}
return param;
}
} }
}
  • 在映射器方法中 MapperMethod#execute 将原来的直接将参数 args 传递给 SqlSession#selectOne 方法,调整为转换后再传递对象。
  • 其实这里的转换操作就是来自于 Method#getParameterTypes 对参数的获取和处理,与 args 进行比对。如果是单个参数,则直接返回参数 Tree 树结构下的对应节点值。非单个类型,则需要进行循环处理,这样转换后的参数才能被直接使用。

3. 参数策略处理器

在 Mybatis 的源码包中,有一个 type 包,这个包下所提供的就是一套参数的处理策略集合。它通过定义类型处理器接口、由抽象模板实现并定义标准流程,定提取抽象方法交给子类实现,这些子类就是各个类型处理器的具体实现。

3.1 策略接口

源码详见cn.bugstack.mybatis.type.TypeHandler

public interface TypeHandler<T> {

    /**
* 设置参数
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; }
  • 首先定义一个类型处理器的接口,这和我们在日常的业务开发中是类似的,就像如果是发货商品,则定义一个统一标准接口,之后根据这个接口实现出不同的发货策略。
  • 这里设置参数也是一样,所有不同类型的参数,都可以被提取出来这些标准的参数字段和异常,后续的子类按照这个标准实现即可。Mybatis 源码中有30+个类型处理

3.2 模板模式

源码详见cn.bugstack.mybatis.type.BaseTypeHandler

public abstract class BaseTypeHandler<T> implements TypeHandler<T> {

    @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; }
  • 通过抽象基类的流程模板定义,便于一些参数的判断和处理。不过目前我们还不需要那么多的流程校验,所以这里只是定义和调用了一个最基本的抽象方法 setNonNullParameter。
  • 不过有一个这样的结构,可以让大家更加清楚整个 Mybatis 源码的框架,便于后续阅读或者扩展此部分源码的时候,有一个框架结构的认知。

3.3 子类实现

源码详见cn.bugstack.mybatis.type.*

/**
* @description Long类型处理器
*/
public class LongTypeHandler extends BaseTypeHandler<Long> { @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<String>{ @Override
protected void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
} }
  • 这里的接口实现举了个例子,分别是;LongTypeHandler、StringTypeHandler,在 Mybatis 源码中还有很多其他类型,这里我们暂时不需要实现那么多,只要清楚这个处理过程和编码方式即可。大家在学习中,可以尝试再添加几个其他类型,用于学习验证

3.4 类型注册机

类型处理器注册机 TypeHandlerRegistry 是我们前面章节实现的,这里只需要在这个类结构下,注册新的类型就可以了。

源码详见cn.bugstack.mybatis.type.TypeHandlerRegistry

public final class TypeHandlerRegistry {

    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<>();
private final Map<Class<?>, 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());
} //...
}
  • 这里在构造函数中,新增加了 LongTypeHandler、StringTypeHandler 两种类型的注册器。
  • 同时可以注意到,无论是对象类型,还是基本类型,都是一个类型处理器。只不过在注册的时候多注册了一个。这种操作方式和我们平常的业务开发中,也是一样的。一种是多注册,另外一种是判断处理。

4. 参数构建

相对于前面章节所完成的内容,这个章节需要对 SqlSourceBuilder 源码构建器中,创建参数映射 ParameterMapping 需要添加参数处理器的内容。因为只有这样才能方便的从参数映射中获取到对应类型的处理器进行使用。

那么就需要完善 ParameterMapping 添加 TypeHandler 属性信息,以及在 ParameterMappingTokenHandler#buildParameterMapping 处理参数映射时,构建出参数的映射。这一部分是在上一章节的实现过程中,细化的完善部分,如图 10-6

那么结合上一章节,这里我们开始扩展出类型的设置。同时注意 MetaClass 反射工具类的使用

源码详见cn.bugstack.mybatis.builder.SqlSourceBuilder

// 构建参数映射
private ParameterMapping buildParameterMapping(String content) {
// 先解析参数映射,就是转化成一个 HashMap | #{favouriteSection,jdbcType=VARCHAR}
Map<String, String> propertiesMap = 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();
}
  • 这一部分就是对参数的细化处理,构建出参数的映射关系,首先是 if 判断对应的参数类型是否在 TypeHandlerRegistry 注册器中,如果不在则拆解对象,按属性进行获取 propertyType 的操作。
  • 这一块也用到了 MetaClass 反射工具类的使用,它的存在可以让我们更加方便的处理,否则还需要要再写反射类进行获取对象属性操作。

5. 参数使用

参数构建完成后,就可以在 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 {
List<ParameterMapping> parameterMappings = 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);
}
}
} }
  • 每一个循环的参数设置,都是从 BoundSql 中获取 ParameterMapping 集合进行循环操作,而这个集合参数就是我们前面 ParameterMappingTokenHandler#buildParameterMapping 构建参数映射时处理的。
  • 设置参数时根据参数的 parameterObject 入参的信息,判断是否基本类型,如果不是则从对象中进行拆解获取(也就是一个对象A中包括属性b),处理完成后就可以准确拿到对应的入参值了。因为在映射器方法 MapperMethod 中已经处理了一遍方法签名,所以这里的入参就更方便使用了
  • 基本信息获取完成后,则根据参数类型获取到对应的 TypeHandler 类型处理器,也就是找到 LongTypeHandler、StringTypeHandler 等,确定找到以后,则可以进行对应的参数设置了 typeHandler.setParameter(ps, i + 1, value, jdbcType) 通过这样的方式把我们之前硬编码的操作进行解耦。

五、测试

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"/>
<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>
  • 这部分暂时不需要调整,目前还只是一个入参的类型的参数,后续我们全部完善这部分内容以后,则再提供更多的其他参数进行验证。

2. 单元测试

源码详见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();
}
  • 因为接下来我们需要验证两种不同入参的单元测试,分别来测试基本类型参数和对象类型参数。

2.1 基本类型参数

@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 [main] INFO cn.bugstack.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
  • 测试过程中可以在 DefaultParameterHandler#setParameters 中打断点,验证方法参数以及获得到的类型处理器,这里测试验证通过,可以满足基本类型对象的入参信息。

2.2 对象类型参数

@Test
public void test_queryUserInfo() {
// 1. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2. 测试验证:对象参数
User user = userDao.queryUserInfo(new User(1L, "10001"));
logger.info("测试结果:{}", JSON.toJSONString(user));
}

07:41:11.025 [main] INFO  c.b.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:userId propertyType:class java.lang.String
07:41:11.232 [main] INFO c.b.m.s.defaults.DefaultSqlSession - 执行查询 statement:cn.bugstack.mybatis.test.dao.IUserDao.queryUserInfo parameter:{"id":1,"userId":"10001"}
07:41:11.638 [main] INFO c.b.m.d.pooled.PooledDataSource - Created connection 402405659.
07:41:11.661 [main] INFO c.b.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:1
07:43:28.516 [main] INFO c.b.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"10001"
07:43:30.820 [main] INFO cn.bugstack.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
  • 此案例主要验证对象参数 User 中包含两个属性时,检查我们的代码处理过程,验证是否可以正确获取到两个类型处理器,分别设置参数的过程。
  • 从测试结果中,可以看到测试通过,并打印了相关参数的构建和使用。

六、总结

  • 到本章节,我们算是把一个 ORM 框架的基本流程串联起来了,不要硬编码也能完成简单 SQL 的处理。读者伙伴可以仔细阅读下当前框架中,所包含的分包结构。比如:构建、绑定、映射、反射、执行、类型、事务、数据源等等,尝试画一画他们的链接关系,这样会让你更加清晰现在的代码解耦结构。
  • 此章节中比较重要的体现是关于参数类型的策略化设计,通过策略解耦,模板定义流程,让我们整个参数设置变得更加清晰,也就不需要硬编码了。
  • 除此之外也有一些细节的功能点,如;MapperMethod 中添加方法签名、类型处理器创建和使用时候,都使用了 MetaObject 这样的反射器工具类进行处理。这些细节的功能点,读者需要在学习的过程中,进行调试验证才能更好的吸收此类编码设计的技巧和经验。

《Mybatis 手撸专栏》第10章:使用策略模式,调用参数处理器的更多相关文章

  1. 《Mybatis 手撸专栏》第7章:SQL执行器的定义和实现

    作者:小傅哥 博客:https://bugstack.cn - <手写Mybatis系列> 一.前言 为什么,要读框架源码? 因为手里的业务工程代码太拉胯了!通常作为业务研发,所开发出来的 ...

  2. 《Mybatis 手撸专栏》第1章:开篇介绍,我要带你撸 Mybatis 啦!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 1. 为甚,撸Mybatis 我就知道,你会忍不住对它下手! 21年带着粉丝伙伴撸了一遍 Sp ...

  3. 《Mybatis 手撸专栏》第6章:数据源池化技术实现

    作者:小傅哥 博客:https://bugstack.cn - 手写Mybatis系列文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 码农,只会做不会说? 你有发现吗,其实很大一部分码农 ...

  4. 《Mybatis 手撸专栏》第8章:把反射用到出神入化

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 为什么,读不懂框架源码? 我们都知道作为一个程序员,如果想学习到更深层次的技术,就需 ...

  5. 《Mybatis 手撸专栏》第9章:细化XML语句构建器,完善静态SQL解析

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你只是在解释过程,而他是在阐述高度! 如果不是长时间的沉淀.积累和储备,我一定也没有 ...

  6. 《Spring 手撸专栏》第 3 章:初显身手,运用设计模式,实现 Bean 的定义、注册、获取

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你是否能预见复杂内容的设计问题? 讲道理,无论产品功能是否复杂,都有很大一部分程序员 ...

  7. 手撸基于swoole 的分布式框架 实现分布式调用(20)讲

    最近看的一个swoole的课程,前段时间被邀请的参与的这个课程 比较有特点跟一定的深度,swoole的实战教程一直也不多,结合swoole构建一个新型框架,最后讲解如何实现分布式RPC的调用. 内容听 ...

  8. 《Spring 手撸专栏》第 2 章:小试牛刀(让新手能懂),实现一个简单的Bean容器

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 上学时,老师总说:不会你就问,但多数时候都不知道要问什么! 你总会在小傅哥的文章前言 ...

  9. 10、Strategy 策略模式 整体地替换算法 行为型模式

    1.模式说明 策略模式比较好理解,就是将程序中用到的算法整体的拿出来,并有多个不同版本的算法实现,在程序运行阶段,动态的决定使用哪个算法来解决问题. 2.举例 排序算法的问题,假如我们的程序中需要对数 ...

随机推荐

  1. [翻译]Service workers:PWA背后的英雄

    原文地址:https://medium.freecodecamp.org/service-workers-the-little-heroes-behind-progressive-web-apps-4 ...

  2. hdfs对文件的增删改查

    源代码: pom.xml: <?xml version="1.0" encoding="UTF-8"?> <project xmlns=&qu ...

  3. 【小程序开发】文本text-overflow属性的使用

    text-overflow原本是CSS3的一个属性,在微信小程序中也支持. text-overflow文本溢出显示省略号~ 注:使用text-overflow时,需要设置固定的宽度才起作用,所以该元素 ...

  4. 【Android开发】View 转 Bitmap

    public static Bitmap loadBitmapFromView(View v) { int w = v.getWidth(); int h = v.getHeight(); Bitma ...

  5. 在node中通过cors跨域。

    cors : 全称 cross origin resource share  跨资源共享 在nodejs 中可以通过在服务器端设置代码如下实现cors跨域 res.setHeader('Access- ...

  6. 设计模式学习笔记(十四)责任链模式实现以及在Filter中的应用

    责任链模式(Chain Of Responsibility Design Pattern),也叫做职责链,是将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求.当有请求发生时,可将请求沿着这条 ...

  7. 《码处高效:Java开发手册》之代码风格

    流水淡,碧天长,鸿雁成行.编码风格,简捷清爽,反引无限风光. 在美剧<硅谷>中有这样一个经典镜头,主人公 Richard 与同为开发工程师的女友闹分手,理由是两人对缩进方式有着截然不同的编 ...

  8. 手把手教会 VS2022 设计 Winform 高DPI兼容程序 (net461 net6.0 双出)

    本文主要解决两个问题 C# Winform高DPI字体模糊. 高DPI下(缩放>100%), UI设计器一直提示缩放到100%, 如果不重启到100%,设计的控件会乱飞. 建立测试程序 新建.N ...

  9. Java语言学习day21--7月27日

    ###01接口的概念 * A:接口的概念 接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的"类". 接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实 ...

  10. oracle提交后再回滚解决办法

    BEGIN; 刚才改错数据,直接commit了,rollback了下,没效果,经过google,oracle有个 闪回 功能,经测试,可用. -- 查询闪回id 如:06001B00054E0000 ...