异构数据库兼容实现

异构数据库相关方案实现

内置函数适配方案

方案概述

对各类数据库的内置函数声明统一的宏变量,并统一函数入参,开发人员在编写具体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 | 金证开发者社区