写点什么

DAS 与 DAL 对比

2020 年 6 月 29 日

DAS与DAL对比

概述

拍拍贷 DAS 是拍拍贷自研的数据库访问框架,支持数据库管理,ORM,并内置了分库分表引擎。为了快速交付能力,DAS 在研发初期并没有从头开发新产品,而是以携程 DAL 框架为基础做深入的定制化改造。DAS 在不断的演化升级中逐渐重构和替换掉携程 DAL 原有代码,目前除了客户端最底层的部分代码外,DAS 已经是一个全新的产品。


DAS 与 DAL 的定位基本相同,站在使用者的角度看,DAS 对 DAL 的改进主要体现在以下几个方面:


  • 增强的分库分表策略

  • 简洁高效的 DAO 设计

  • 具备元数据的 Entity

  • 灵活方便的 SqlBuilder


由于 DAL 也是我参与开发的项目,因此这个对比也是一篇自我回顾,自我总结的文章。颇有些我“杀”了我的感觉。



分库分表策略改进

分库分表策略定义

分库分表策略是支持数据库分片的数据库访问框架的核心。其作用是判断用户给出的 SQL 语句要在那些数据库或表分片上执行。判断 SQL 对应的分片范围很有技术挑战。完美的解决方案应该是:


1. 解析 SQL,确定所有的表达式,表达式包括但不限于以下>, >=, <, <=, <>, between,not between, in, not in (…), like, not like, is null, is not null,等等。


2. 计算每个表达式对应的分片范围。


3. 根据一定的规则合并各自的分片范围来生成最终的集合。


分库分表策略定义是否全面合理,直接决定了数据库访问框架的能力上限。接下来我们来对比 DAL 和 DAS 各自的策略定义。


携程 DAL 的策略接口核心定义如下:


public interface DalShardingStrategy {    String locateDbShard(DalConfigure configure, String logicDbName, DalHints hints);    String locateTableShard(DalConfigure configure, String logicDbName, String tabelName, DalHints hints);    }
复制代码


其中 hints 参数会传递表达式中参数的集合,但不会传递参数对应的表达式的操作符(=,>,<之类)具体是什么;同时接口的返回值定义为 String,因此而返回值仅能指定最多一个分片。


这种策略定义导致只有包含相等表达式或者赋值类操作的 SQL 才能准确的判断分片范围。


该策略可以支持的语句如下:


SELECTE * FROM PERSON WHERE AGE = 18


当然由于 IN 可以看做是一系列相等操作,因此经过变通也可以支持 IN,所以下面的语句也支持:


SELECTE * FROM PERSON WHERE AGE IN (18,19,20)


但是用户的 SQL 语句不仅仅只是相等或者 IN 判断,所以这种策略定义在实际使用中有较大限制。


接下来我们看一下 DAS 策略接口的核心定义:


public interface ShardingStrategy {    Set<String> locateDbShards(ShardingContext ctx);    Set<String> locateTableShards(TableShardingContext ctx);    }
复制代码


其中 ShardingContext 参数中包含了 ConditionList 属性。该属性定义了表达式集合,以及表达式之间的关系(AND,OR,NOT)。同时策略的返回值允许是分片集合,而不是某个特定分片。


这种策略定义可以支持几乎所有的表达式。可以处理表达式间的与或非关系,以及括号和嵌套括号。例如:


SELECTE * FROM PERSON WHERE (AGE > 18 OR AGE <20) AND (AGE IN (18,19,20) OR AGE BETWEEN [0,100])


通过对比我们可以了解 DAS 的策略适用于更普遍的场景,对用户的限制更少,用法更灵活,更符合用户习惯。具体设计可以参考:


https://github.com/ppdaicorp/das/wiki/DasClient-%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E7%AD%96%E7%95%A5%E8%AE%BE%E8%AE%A1


DAO 改进

DAO 是用户使用数据库访问框架的主要途径,通过 DAO,用户得以完成对数据库的增删改查操作。DAO 设计的好坏直接影响了用户的使用体验。


DAL 有着较复杂的 DAO 类层次结构。要使用 DAO,用户需要先通过 DAL console 生成标准,构建和自定义 DAO 的代码:


1. 标准 DAO 包含了最常用的单表操作,与特定表相关联。


2. 构建 DAO 包含针对单表的自定义的操作,生成的时候会跟同一表名的标准 DAO 的代码合并


3. 自定义 DAO 包装用户提供的自定义 SQL,用于跨表查询或者语法特殊的 SQL


标准 DAO 和构建 DAO 基于基础 DAO 类 DalTableDao。自定义 DAO 基于基础 DAO 类 DalQueryDao。如果涉及到事务操作,需要调用底层接口 DalClient。关系如下所示:



即使要完成最简单的数据库操作,用户也需要先生成 DAO。同时在某些特殊场景下还需要调用预定义的 DAO,步骤繁琐,学习成本高。我印象中,用户多有吐槽。


DAS 对 DAO 做了大幅优化。将 DalTableDao, DalQueryDao,DalClient 的功能合并在 DasClient 一个类并暴露给用户直接使用。要做数据库操作时,用户可以直接使用 DasClient,再也无需先生成任何 DAO 代码:



除了简化 DAO 类设计,DAS 还做了以下优化:


1. 简化 API 设计,降低学习成本。例如 DAL 中的 DalTableDao 和 DalQueryDao 一共有 34 个 query 方法,DasClient 里完成全部功能只用了 7 个。


2. 简化 Hints 的用法,去掉了 DAL 中不常用的 hints,例如 continueOnError、asyncExecution 等等,以在功能的灵活性,可理解性和系统复杂度方面取得平衡。


3. 增强 DAS 功能。例如重新设计了 SqlBuilder 类和表实体,可以让用户类似写原生 SQL 的方式自定义 SQL 语句。下面的章节里会专门介绍


DAS 在 DAO 设计上相比 DAL 有很显著的改进。与 DAL 相比,DAS 的类层次更简洁,API 设计更合理,显著降低了用户上手门槛,用起来很顺手。


在 DAL 的落地中我们原来收到的反馈是用户强烈希望 DAO 不要绑死在某张表上面,因此我们将 DAL 的 DAO 简化为 DAS 的形式。但在 DAS 落地过程中,却有用户反馈希望提供针对单表的 DAO 以方便继承,还提出希望为记录逻辑删除操作提供便利。于是我们又增加了 TableDao 对 DasClient 做了简单的封装,参数化了实体类型来满足用户自定义需求。并基于 TableDao 提供了 LogicDeletionDao 来支持逻辑删除操作。一顿操作猛如虎之后发现貌似又回到了开头,真是万万没想到啊。



Entity 改进

Entity 是数据库中的表或数据库查询结果的 Java 对应物,一般称为实体。其中表实体可以直接用于数据库的 CRUD 操作,查询实体仅用于表示查询结果。这两种实体一般通过 console 生成。实体的主要结构是字段属性,表类型的实体还会包含表名信息。


DAL 表实体

DAL 的 entity 里仅包含可赋值的了表字段,通过注解标明了对应的表字段结构。


@Entity(name="dal_client_test")public class ClientTestModelJpa {    @Id    @Column(name="id")    @GeneratedValue(strategy = GenerationType.AUTO)    @Type(value=Types.INTEGER)    private Integer id;        @Column(name="quantity")    @Type(value=Types.INTEGER)    private Integer quan;。。。    public Integer getId() {        return id;    }     public void setId(Integer id) {        this.id = id;    }     public Integer getQuantity() {        return quan;    }     public void setQuantity(Integer quantity) {        this.quan = quantity;    } 
复制代码


DAS 表实体

DAS 扩充了 DAL 表实体的定义。在普通的属性字段定义外,还新增了表结构元数据定义。下面的例子中,Person 实体代码里面的 PersonDefinition 定义表结构的元数据,其包含的 PeopleID 等定义表字段元数据。


@Tablepublic class Person {    public static final PersonDefinition PERSON = new PersonDefinition();        public static class PersonDefinition extends TableDefinition {        public final ColumnDefinition PeopleID;        public final ColumnDefinition Name;。。。        public PersonDefinition as(String alias) {return _as(alias);}        public PersonDefinition inShard(String shardId) {return _inShard(shardId);}        public PersonDefinition shardBy(String shardValue) {return _shardBy(shardValue);}         public PersonDefinition() {            super("person");            setColumnDefinitions(                    PeopleID = column("PeopleID", JDBCType.INTEGER),                    Name = column("Name", JDBCType.VARCHAR),。。。                    );        }            }    @Id    @Column(name="PeopleID")    @GeneratedValue(strategy = GenerationType.AUTO)    private Integer peopleID;        @Column(name="Name")    private String name;....    public Integer getPeopleID() {        return peopleID;    }    public void setPeopleID(Integer peopleID) {        this.peopleID = peopleID;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }
复制代码


这些元数据提供了可以生成非常丰富和全面的表达式的 API。基于这些 API,用户可以按照符合 SQL 语法的方式通过 Sqlbuilder 构建动态 SQL。比 DAL 构建 SQL 的方式要自然和简洁得多。


例如:


PersonDefinition p = Person.PERSON;p = p.inShard("0");builder = SqlBuilder.selectAllFrom(p).where(p.Name.eq(name)).into(Person.class);Person pk = dao.queryObject(builder);
复制代码


可以看到使用 DAS 的 entity 可以方便的获取表名,列名,创建表达式,指定表分片。


表达式方法除了全称,还有简写。例如 eq 和 equal 是等价的方法。下面是一个包含所有表达式全称与简写的例子:


query(selectAllFrom(p).where(p.PeopleID.eq(1)), i, 1);query(selectAllFrom(p).where(p.PeopleID.equal(1)), i, 1);query(selectAllFrom(p).where(p.PeopleID.neq(1)), i, 3);query(selectAllFrom(p).where(p.PeopleID.notEqual(1)), i, 3);query(selectAllFrom(p).where(p.PeopleID.greaterThan(1)), i, 3);query(selectAllFrom(p).where(p.PeopleID.gteq(1)), i, 4);query(selectAllFrom(p).where(p.PeopleID.greaterThanOrEqual(1)), i, 4);query(selectAllFrom(p).where(p.PeopleID.lessThan(3)), i, 2);query(selectAllFrom(p).where(p.PeopleID.lt(3)), i, 2);query(selectAllFrom(p).where(p.PeopleID.lessThanOrEqual(3)), i, 3);query(selectAllFrom(p).where(p.PeopleID.lteq(3)), i, 3);query(selectAllFrom(p).where(p.PeopleID.between(1, 3)), i, 3);query(selectAllFrom(p).where(p.PeopleID.notBetween(2, 3)), i, 2);query(selectAllFrom(p).where(p.PeopleID.notBetween(2, 4)), i, 1);query(selectAllFrom(p).where(p.PeopleID.in(pks)), i, 3);query(selectAllFrom(p).where(p.PeopleID.notIn(pks)), i, 1);query(selectAllFrom(p).where(p.Name.like("Te%")), i, 4);query(selectAllFrom(p).where(p.Name.notLike("%s")), i, 4);query(selectAllFrom(p).where(p.Name.isNull()), i, 0);query(selectAllFrom(p).where(p.Name.isNotNull()), i, 4);
复制代码


SqlBuilder 改进

DAL 的 SqlBuilder 比较复杂,分为单表,多表和批处理三大类,共 7 种:



可以 DAL 里面 Builder 类划分过细。


在 DAS 中,上面所有的 builder 除了 MultipleSqlBuilder 外,在 DAS 里都用一个 SqlBuilder 取代了。


同时为了简化和规范操作,DAS 增加了专门用于批量查询,更新的 BatchQueryBuilder,BatchUpdateBuilder 以及专门用于存储过程调用的 CallBuilder 和 BatchCallBuilder。如下所示:



此外,SqlBuilder 增加了生成语句的静态方法,可以让用户以符合 SQL 语法的方式写创建动态 SQL。


示例如下:


import static com.ppdai.das.client.SqlBuilder.*; // 查询SqlBuilder builder = selectAllFrom(p).where(p.PeopleID.eq(j+1)).into(Person.class);Person pk = dao.queryObject(builder); builder = selectAllFrom(p).where(p.PeopleID.eq(j+1)).into(Person.class).withLock();Person pk = dao.queryObject(builder); SqlBuilder builder = select(p.Name).from(p).where().allOf(p.PeopleID.eq(k+1), p.Name.eq("test")).into(String.class);String name = dao.queryObject(builder); SqlBuilder builder = select(p.PeopleID, p.CountryID, p.CityID).from(p).where(p.PeopleID.eq(k+1)).into(Person.class);Person pk = dao.queryObject(builder); // 插入SqlBuilder builder = insertInto(p, p.Name, p.CountryID, p.CityID).values(p.Name.of("Jerry" + k), p.CountryID.of(k+100), p.CityID.of(k+200));assertEquals(1, dao.update(builder)); // 更新SqlBuilder builder = update(Person.PERSON).set(p.Name.eq("Tom"), p.CountryID.eq(100), p.CityID.eq(200)).where(p.PeopleID.eq(k+1));assertEquals(1, dao.update(builder)); // 删除SqlBuilder builder = deleteFrom(p).where(p.PeopleID.eq(k+1));assertEquals(1, dao.update(builder));
复制代码


总结

本文主要介绍策略,DAO,entity 和 SqlBuilder 的对比。


今天写这个 DAS 和 DAL 的对比让我非常感慨。我在 2013 年到 2018 年作为产品负责人和 Java 客户端主力开发,与团队一起打造了携程 DAL。 DAL 目前还在继续完善并作为主力框架产品支撑着携程每天亿万的数据库请求。我为我的团队和产品感到万分自豪。


但在 DAL 的研发中,也有很多遗憾。因为是第一次开发如此复杂,使用量如此大的产品,同时由于经验不足,我们有些使用场景假设是错误的,一些设计也存在考虑不周的情况。在使用中,我们不断收到用户的反馈。虽然尽心尽力的改进着,但由于框架产品的特殊性,一旦发布就会被所有上游代码所依赖,我们很难调整 API 来实现所有改进,有时候权衡再三,最终还是不得不放弃了一些想法。


这些遗憾在打造信业框架 DAS 框架的时候得到了弥补。几乎是奇迹般的,我有一个全新的能将所有好的想法,用户的反馈和积累的全部经验付诸实施的机会。为了做出完美的设计,易用的功能,节省用户每一步操作,我们开发团队付出了巨大的努力。DAS 凝结了我们所有的心血,在公司内部获得普遍认可和好评。现在公司将其贡献给开源社区回报社会。愿大家用起来顺手之余,心满意足的 star 我们的产品:


DAS 除了客户端外,还包括 DAS Console 和 DAS Proxy Server。其中 DAS Console 的功能是管理数据库配置和生成 Entity 类。DAS Proxy Server 可以和 DAS Client 配合使用,透明的支持本地直连和基于代理的数据库连接模式,允许用户在数据库不断增长的情况下平滑升级整体架构。关于这些的介绍请持续关注信也科技的拍码场技术公众号。


作者介绍

赫杰辉,信也科技基础组件部门主管、信也 DAS 产品负责人、布道师。图形化构建工具集 x-series 的作者。曾主持开发携程开源数据库访问框架 DAL。对应用开发效率提升和分布式数据库访问机制拥有有多年研究积累。


2020 年 6 月 29 日 07:011144
用户头像
刘燕 InfoQ记者

发布了 568 篇内容, 共 179.6 次阅读, 收获喜欢 1088 次。

关注

评论

发布
暂无评论
发现更多内容

darknet A版安装

Dreamer

区块链数字货币钱包开发模式,深圳数字钱包开发

135深圳3055源中瑞8032

USDT支付系统开发方案,区块链币支付系统搭建

135深圳3055源中瑞8032

Week 5命题作业

balsamspear

极客大学架构师训练营

架构师训练营第一期 - week7

习习

还在为算法烦恼?那你应该还没看过这份Git上70k标星的笔记

Java架构师迁哥

天源迪科受邀出席“第四届央企电商化采购发展高峰论坛"

DT极客

【Knative系列】一文读懂 Knative Serving扩缩容的原理

Chumper

Serverless knative autoscaler kantive

交易所开发技术,数字货币交易系统源码搭建

135深圳3055源中瑞8032

DDIA 读书笔记(5)数据分区方案

莫黎

读书笔记

直播预告 | 应用加固防破解,4.1折就够了

蚂蚁集团移动开发平台 mPaaS

安全攻防 App风险 mPaaS

银行数字化转型:需建立起以体验为核心、数据为基础、技术为驱动的架构体系

CECBC区块链专委会

银行 数字经济

架构师第一期作业(第 7 周)

Cheer

课程作业

广东云算力矿机系统开发,挖矿平台搭建

135深圳3055源中瑞8032

甲方日常 45

句子

工作 随笔杂谈 日常

Dubbo-go Server端开启服务过程

apache/dubbo-go

dubbo dubbo-go dubbogo

配置企业管理系统,什么样的工作流才有用

雯雯写代码

工作流 企业管理系统

英特尔顶级技术专家合力缔造精品:Linux开源网络全栈详解

周老师

Java 编程 程序员 架构 面试

Week 5学习总结

balsamspear

极客大学架构师训练营

Android 扫码 生成二维码

Java android kotlin zxing camera

深入理解Java虚拟机第三版,通俗易懂,大牛带你轻松搞懂JVM性能调优

Java架构之路

Java 程序员 架构 面试 编程语言

区块链+能源 大放异彩

CECBC区块链专委会

区块链 能源

Week 7 命题作业

阿泰

阿里云视频云技术专家 LVS 演讲全文:《“云端一体”的智能媒体生产制作演进之路》

阿里云视频云

媒体 音视频

high-performance-tidb-challenge 记录

程序员老王

「混合云」会是云计算的下一个战场吗?

ToB行业头条

阿里云

在深夜加油站遇见哈利波特

脑极体

书写高质量SQL的30条建议

诸葛小猿

MySQL SQL优化

【性能优化】纳尼?内存又溢出了?!是时候总结一波了!!

冰河

性能优化 内存泄露 高并发 高性能 内存溢出

面试大厂被算法难倒惨遭滑铁卢?这份字节内部大佬整理的《数据结构与算法》学习笔记你一定要看看!

Java架构之路

Java 程序员 架构 面试 编程语言

央行数字货币为人民币国际化之路提供推动力

CECBC区块链专委会

数字货币

DAS与DAL对比-InfoQ