首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

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 locateDbShards(ShardingContext ctx);

Set 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等定义表字段元数据。

@Table

public 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的方式要自然和简洁得多。

例如:

import static com.ppdai.das.client.SqlBuilder.selectAllFrom;

private PersonDefinition p = Person.PERSON;

p = p.inShard(“0”);

builder = selectAllFrom(p).where(p.Name.eq(name)).into(Person.class);

Person pk = dao.queryObject(builder);

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

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

query(selectAllFrom§.where(p.PeopleID.eq(1)), i, 1);

query(selectAllFrom§.where(p.PeopleID.equal(1)), i, 1);

query(selectAllFrom§.where(p.PeopleID.neq(1)), i, 3);

query(selectAllFrom§.where(p.PeopleID.notEqual(1)), i, 3);

query(selectAllFrom§.where(p.PeopleID.greaterThan(1)), i, 3);

query(selectAllFrom§.where(p.PeopleID.gteq(1)), i, 4);

query(selectAllFrom§.where(p.PeopleID.greaterThanOrEqual(1)), i, 4);

query(selectAllFrom§.where(p.PeopleID.lessThan(3)), i, 2);

query(selectAllFrom§.where(p.PeopleID.lt(3)), i, 2);

query(selectAllFrom§.where(p.PeopleID.lessThanOrEqual(3)), i, 3);

query(selectAllFrom§.where(p.PeopleID.lteq(3)), i, 3);

query(selectAllFrom§.where(p.PeopleID.between(1, 3)), i, 3);

query(selectAllFrom§.where(p.PeopleID.notBetween(2, 3)), i, 2);

query(selectAllFrom§.where(p.PeopleID.notBetween(2, 4)), i, 1);

query(selectAllFrom§.where(p.PeopleID.in(pks)), i, 3);

query(selectAllFrom§.where(p.PeopleID.notIn(pks)), i, 1);

query(selectAllFrom§.where(p.Name.like(“Te%”)), i, 4);

query(selectAllFrom§.where(p.Name.notLike("%s")), i, 4);

query(selectAllFrom§.where(p.Name.isNull()), i, 0);

query(selectAllFrom§.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§.where(p.PeopleID.eq(j+1)).into(Person.class);

Person pk = dao.queryObject(builder);

builder = selectAllFrom§.where(p.PeopleID.eq(j+1)).into(Person.class).withLock();

Person pk = dao.queryObject(builder);

SqlBuilder builder = select(p.Name).from§.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§.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§.where(p.PeopleID.eq(k+1));

assertEquals(1, dao.update(builder));

可以看到DAS重新设计了SqlBuilder,相对DAL既有简化,又有增强。

总结

本文主要介绍策略,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。对应用开发效率提升和分布式数据库访问机制拥有有多年研究积累。

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/8gvEXh8ghDeukO1LkcBm
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券