异构数据库相关方案实现
内置函数适配方案
方案概述
对各类数据库的内置函数声明统一的宏变量,并统一函数入参,开发人员在编写具体SQL语句时必须使用约定的宏变量替代具体函数。平台提供统一的数据库函数适配器,在系统运行期,动态拦截所执行SQL,把SQL中的宏变量替换成对应数据库的内置函数。
函数适配器设计
-
设计思路:声明统一的函数名称,DB_XXX, 在执行的过程中替换为实际的数据库函数。如果函数能直接替换,则直接替换,如获取字符串长度,DB_LENGTH替换为LEN;如果不能简单的进行函数名的替换,涉及到参数不一致,或者结构不一致的情况,通过正则匹配进行sql片段替换,如DB_CONCAT(str1,str2,str3),ORCACLE数据库替换成 str1||str2||str3。
-
编写sql脚本时使用统一的函数名称DB_XXX。
<select id="select">
select NAME_, VALUE_, DB_LENGTH(VALUE_) REV_ from ACT_GE_PROPERTY
</select>
-
KOCA应用在启动时,对于直接替换的函数映射关系加载进内存中。
-
在调用数据库执行语句时动态拦截将要执行的SQL,检查SQL中是否包含数据库函数,有则根据数据库类型匹配出对应的函数,并替换到SQL中;
-
执行已转换好的SQL。
-
扩展:函数类型扩展,命名必须以DB_开头,获取字符串长度如DB_LENGTH,其它各种命名均不符合规范,如:DBLENGTH,LENGTH,KOCA_LENGTH。
(详情见文档:后端框架–数据库操作–异构数据库兼容)
序列器适配方案
方案描述
对各类数据库的序列器功能,通过序列器表,将序列生成及设置的过程提前到代码中完成,可屏蔽底层数据库的差异,且具有较好的性能。
序列器管理设计
序列器管理类
public interface IdGenerator {
/**
\* 根据Id名称获取一个id值
\* @param idName Id名称
\* @return 下一个id值
\* */
String getId(String idName);
/**
\* 批量获取id,兼容复核模块id获取方式,提供批量获取
\* @param idName id名称
\* @param count 数量
\* @return id集合
\* */
List<String> getIdList(String idName, int count);
/**
\* 根据Id名称获取一个id值
\* @param idName Id名称
\* @return 下一个id值
\* */
default long getNumId(String idName) {
return 0;
}
/**
\* 批量获取id,兼容复核模块id获取方式,提供批量获取
\* @param idName id名称
\* @param count 数量
\* @return id集合
\* */
default List<Long> getNumIdList(String idName, int count) {
return null;
}
}
处理逻辑
代码示例:
String id = idGenerator.getId("ADMIN_JOBSCHEDULE_FLOW_SEQ");//使用序列生成器获取一个序列值
jobScheduleFlow.setScheduleFlowId(id);//在代码中设置主键
jobScheduleFlowDao.insert(jobScheduleFlow);//插入数据
(详情见文档:后端框架–数据库操作–主键生成器)
查询类适配方案
分页查询
方案概述
基于mybatis拦截器进行扩展,通过数据库类型自动匹配进行物理分页,开发者只需编写查询sql,即可实现查询总数和分页结果的效果,无需关注使用的是何种数据库。
实现概要
通过拦截器PageInterceptor, 在满足返回是list, 且sql为 select时, 在实际执行sql查询前进行sql扩展处理。
一般情况下,处理逻辑分为两步:a.查询总数 b.查询分页数据。
a.将业务编写的sql, 拼接查询总数的sql, 并优化order by , group by 等不影响查询总数的因子,查询count 总数。
b.先对count 和pageSzie、pageNum 进行比较,如果分页查询超出数据总数,则直接返回空列表,反之进行 分页sql拼装执行,并返回list。
开发流程
请求参数类是PageParam的子类或Map类型中携带pageSize 和pageNum 两个分页参数,业务sql作为普通的查询,如下:
<!--查询角色信息-->
<select id="findList" parameterType="com.szkingdom.koca.admin.user.vo.request.RoleParamVo" resultMap="roleInfo">
select *
from role_info a
where 1=1
<if test="roleNo != null and roleNo != '' ">
and a.role_no = #{roleNo, jdbcType=VARCHAR}
</if>
</select>
注:可以通过pageSize=0 不进行分页查询所有结果,通过pageSize=-1 只查询总数, 不需要额外再开发业务接口
(详情见文档:后端框架–数据库操作–Mybatis-分页查询)
递归查询
调用koca-jdbc的递归查询方法
级联查询
方案概述
级联查询关键字,如 join、left join 等,为标准sql关键字,各数据库均有支持,且功能一致,无需进行适配处理。
注:在使用数据库分片时,级联查询会对性能等方面造成影响,尽量使级联查询数据和表在同一分区内。
操作类适配方案
批量插入
使用mybatis 进行批量插入数据时,存在两个明显缺陷:
-
针对不同数据库,mybatis 批量插入的sql并不能完全一致,譬如oracle 和mysql的批量插入实现不一样。
-
mybatis 批量插入的性能不高。
方案概述
在实际场景中,使用JdbcTemplate进行批量操作比mybatis性能更高。为了方便管理,需要将SQL统一配置在mapper xml中,因此koca扩展了batch标签,其底层实现是JdbcTemplate,而对于开发者,其业务开发方式和mybatis 一致。
实现概要
扩展mybatis 源码中xml解析规则,添加koca自定义标签<batch>
解析实现。进行batch标签sql执行时,使用jdbcTemplate进行处理.
开发流程
- dao接口
public interface OrderDao {
// 入参必定为一个List
int baseBatch(List<Order> orders);
}
- mybatis xml 实现
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//koca-jdbc-mybatis//DTD Mapper" "koca-jdbc-mybatis-mapper.dtd" >
<mapper namespace="com.szkingdom.koca.support.mybatis.dao.OrderDao">
<sql id="jdbcTestOrder">
t_order
</sql>
<batch id="baseBatchParent" fetchSize="1" >
update
<include refid="jdbcTestOrder"/>
set order_price = #{orderPrice}
<where>
<if test="orderType!=null">
and order_type = #{orderType}
</if>
<if test="orderId!=null">
and order_id = #{orderId}
</if>
<if test="oper!=null">
and oper = #{oper}
</if>
</where>
</batch>
</mapper>
(详情见文档:后端框架–数据库操作–Mybatis–koca自定义的批量操作标签-batch)
数据库规范:README | 金证开发者社区