学习记录:DAY23
项目开发与学习记录:字段注入优化
前言
我总有一种什么大的要来了的危机感。还是尽快把项目做起来吧,现在全在弄底层的框架。这是一个两天的blog,前一天bug没修好,气到连blog都没写。
日程
5月7日
- 晚上7点:本来想玩游戏的,但转念一想,还是学习吧。
- 晚上9点:做完blog后针对并发安全性进行了优化,看看网课,补补学科内容。
学习记录
计算机网络
- CIDR
- 路由聚合
操作系统
- 虚拟内存
- 请求分页管理:缺页中断结构
- 页面置换算法
学习内容
省流
- params字段注入
1. params字段注入
这部分要完成的任务看上去比较简单。这是原来的手动注入模式:
String sql = "UPDATE movies SET " +"title = COALESCE(?, title), " +"release_date = COALESCE(?, release_date), " +"poster_url = COALESCE(?, poster_url), " +"duration = COALESCE(?, duration), " +"genre = COALESCE(?, genre), " +"rating = COALESCE(?, rating), " +"status = COALESCE(?, status), " +"updated_at = NOW() " +"WHERE id = ?";
JdbcUtils.executeUpdate(sql,movie.getTitle(),movie.getReleaseDate(),movie.getPosterUrl(),movie.getDuration(),movie.getGenre(),movie.getRating(),movie.getStatus(),movie.getId());
目标是将SQL改造成这样,然后直接通过movie的字段进行注入:
String sql = "UPDATE movies SET " +"title = COALESCE(#{title}, title), " +"release_date = COALESCE(#{releaseDate}, release_date), " +"poster_url = COALESCE(#{posterUrl}, poster_url), " +"duration = COALESCE(#{duration}, duration), " +"genre = COALESCE(#{genre}, genre), " +"rating = COALESCE(#{rating}, rating), " +"status = COALESCE(#{status}, status), " +"updated_at = NOW() " +"WHERE id = #{id}";
一开始想实现像mybatis那样的注解扫描模式。但发现这是一个大工程,因为我原有的bean注册器没有针对代理类的处理,而且这需要额外构建JdbcUtils
的方法句柄,这动摇到了我一开始的分模块理念。工具类是不应该出现在核心模块当中的,一个代理类的方法,外面再套了一层AOP,我不知道这中间的通知链是否会出问题(猜测不会)。除此之外,一旦进行了代理,调试起来就会变得比较复杂,而我连最基本的注入处理都没有弄好。所以这个想法暂时放弃了(真不是我懒哈)。
现在的方案是基于实体类的字段反射注入。
1)向外提供SQL参数提取方法
public static Object[] extractParams(String sql, Object... params) {SqlTemplate template = Cache.SQL_TEMPLATE_CACHE.computeIfAbsent(sql, SqlTemplate::new);Object[] result = new Object[template.paramNames.size()];for (int i = 0; i < template.paramNames.size(); i++) {String paramName = template.paramNames.get(i);result[i] = findParamValue(paramName, params);}return result;
}
2)关键方法:从实体类中查找字段
for (Object param : params) {if (param == null || param instanceof Map || param instanceof String) {continue;}Class<?> clazz = param.getClass();Map<String, MethodHandle[]> accessors = Cache.ACCESSOR_CACHE.computeIfAbsent(clazz, KatSimpleMapper::createAccessors);MethodHandle getter;try {getter = accessors.get(paramName)[0]; // [0]是getter} catch (Exception e) {log.error("Failed to access field: {}", paramName);log.error("Check accessors: {}", accessors);throw new RuntimeException(e);}if (getter != null) {try {return getter.invoke(param); //调用实体类param的getter方法句柄获取值} catch (Throwable e) {throw new RuntimeException("Failed to access field: " + paramName, e);}}
}
增加对map和显式键值对的兼容:
// 1:查找显式键值对(如 "id", 123)
for (int i = 0; i < params.length; i++) {if (params[i] instanceof String key && i + 1 < params.length) {if (key.equals(paramName)) {return params[i + 1];}}
}// 2:查找 Map 中的参数
for (Object param : params) {if (param instanceof Map<?, ?> map) {if (map.containsKey(paramName)) {return map.get(paramName);}}
}
并确定了查找优先级:键值对 > Map > 实体类字段。
3)缓存优化
对sql模板进行了缓存(貌似没有优化多少):
//sql模板缓存
private static final Map<String, SqlTemplate> SQL_TEMPLATE_CACHE = new ConcurrentHashMap<>();
将原来的setter缓存改成了更通用的字段缓存,这样无论是进行结果集映射还是字段注入,同一个类只需要缓存一次:
//缓存访问器数组 [getter, setter]
static final Map<Class<?>, Map<String, MethodHandle[]>> ACCESSOR_CACHE = new ConcurrentHashMap<>();
4)正则替换,将当前的特殊sql转回防注入sql
/*** 将#{param}替换为?*/
public static String replaceParamPlaceholders(String sql) {return sql.replaceAll("#\\{\\w+}", "?");
}
结语
抓紧时间bro。