DAS是信也科技自研的数据库访问中间件,是集数据库管理,ORM,动态SQL构建和分库分表支持的一体化关系型数据库访问解决方案。
看到这里,你一定会说少年啊!已经有了那么多ORM框架和分库分表组件,像Hibernate,Mybatis,mycat,sharding jdbc,还有我们最爱的携程DAL可供选择,干嘛还要重复造轮子?
答案很简单,这些工具都不好用!而DAS是我们最新发明的高科技轮子。
警告!前方高能!非资深开发人员尽快撤离。请握紧鼠标,抓牢键盘!
你一定奇怪,既然要做数据库中间件,为什么不像其他产品那样,从JDBC或者数据库协议层入手,在传统数据库上面做分库分表或重新开发数据库引擎?那样多牛啊!现有程序不用改就可以无缝移植。为什么DAS还要提供ORM功能呢?
回答这个问题之前,让我们先简单回顾一下流行的数据库编程过程。这有助于理解DAS的产品定位。
mybatis,Hibernate发明的年代还没有什么数据库垂直与水平扩容,分库分表之类的概念,自然也不会从设计上加以考虑。而现在随便一个互联网公司,每天产生的数据都是“天量”。因此一个正经的数据库项目往往会同时用到ORM工具和分库分表组件。无论是ORM还是分库分表组件,一般都需要繁琐的配置。区别只在于难度级别是受得了还是劝退。
以最流行的mybatis+任意分库分表组件为例,如果你是一个资深的CRUD boy,肯定非常熟悉下面的套路,在开始写下图中间最终实际的DAO代码之前,你需要先搞定另外四件事情:
当你手忙脚乱搞好这些配置,第一次测试时候,十有八九不会成功,这个时候千万不要气馁,因为更惨的还在后面,当在项目中使用独立的ORM和分库分表组件时,你会难过到流泪:
当最终克服万难代码能工作时,你会发现跟总体的配置和代码量相比,最终的DAO代码只占很少一部分。而就这一部分代码里面,真正有用的也只是极小一部分,不信你看:
public static void main(string[] args) throws IOException { Inputstrean resourceAsstream Resources ogetResourceasstrean(“cc/sq1MupConfig.xml”); sq1SessionFactory ssfenew sq1Session actoryBullder() .build(resourceAsstream); ///mapper就是UserMapper接口的实现类
UserMapper mapper = sqlSession. getMapper(UserMapper.class);
User u = mapper.finduserById(10);
system.out.print1n();
}
上面这段典型的mybatis代码。除了倒数第二行代码,其他的都是什么玩意?只是抢个两分钱的红包,干嘛要“磕这么多响头”?刨去注释和无关代码,这里面真正有用的代码只占1/5而已。你不觉得这个比例荒谬到可笑吗?为交付这一点点代码,付出的代价如此之
“大”,是不是觉得哪里不对?
其实要查询,真正关键的信息就是数据库名和查询语句而已。评价一个设计的的好坏只要看实现一个需求在多大程度上只需要提供必要信息。额外步骤越多,设计越失败!作为参考,请思考餐厅用餐和自己买菜做饭的区别。
作为一个老程序员,我已经厌倦了使用破烂工具。人嘛,要对自己好一点,诚实一点。一个人性化的数据库访问框架应该是这个样子:
于是2018年在时任CTO的规划下,我们信也科技基础组件团队决定自己动手搞一套符合自己心意的数据库访问中间件,这就是
DAS是Database Access Service的缩写。DAS的目标就是给研发人员提供一个一站式的数据库访问框架,让研发人员用最简单直接的方式开发数据库访问代码,实现上面所有“非分”的想法。
为实现这个目标,DAS提供:
但DAS的真正的核心优势不是这些组件,我们build了一个专业的团队,7*24小时主动为程序员服务,帮大家搞定从原子到宇宙尺度的任何数据库问题。
在信也科技,研发人员发邮件告诉DAS团队各个环境的数据库配置和逻辑数据库信息,DAS团队通过DAS Console配置好并自动同步到公司的配置中心后,用户只要在自己的项目里面引入DAS Client的依赖就可以开始直接写代码。对,你没有看错,直接开始写代码,无需任何的本地配置工作。我们把中间件产品的研发从交付组件提升到交付服务的层面。
这,才是我们成功的秘密![撒花]
你心中一定冷笑,吹吧你!那让我们从技术角度看看 DAS的核心DAS Client 到底长什么样。
DAS Client的设计遵从分层抽象原则,从上到下分为:
1. DAO层,一个完整的ORM框架。关于编程所有美好的想象都在这里。
2. 分库分表层。抽象数据库操作差异,以统一的方式处理数据的路由与合并。
3. 执行层。操作底层数据库完成实际工作,封装数据源,链接与事务。
DAO层是程序员使用最频繁的部分,今天会重点介绍这一部分,其他部分会在未来会逐一提供,请关注我们的“拍码场”公众号。
DAS ORM简介:
DAS ORM的主要由预定义DAO类DasClient,SQL创建工具类SqlBuilder和特殊操作指令类Hints组成。下面一一介绍。
DAS ORM的核心是DasClient类,来看看里面提供了啥方法:
DasClient提供了几乎所有常见的ORM操作,开箱即用,不需要用户生成任何DAO接口或实现。
别跟我“扯犊子”,上代码!
OK!猜猜看用DAS实现一个查询操作需要几行代码?
Person pk = new Person();
pk.setName(“test”);
DasClient dao = DasClientFactory.getClient(“logicDbName”);
List plist = dao.queryBySample(pk);
客户端创建到使用,两行代码完事,是不是很简单粗暴?像我说的一样,如果你要完成一个查询,你需要提供就只是数据库名和SQL,这里SQL用sample data表示。除此以外,没有多余动作。没有session,没有事务,也没有connection。只要写的代码足够少,BUG就不会追上我。这就是传说中的极简编程风
通过这种预定义API的方式能节省多少代码呢?再以一个实际例子对比一下完成同样功能mybatis和DAS之间代码量:
Mybatis mapping:
select distinct ‘false’ as QUERYID, from strategyaccountdetail${tableSuffix} order by ${orderByClause}
DAS对应代码:
public List selectByExample(Strategyaccountdetail detail) throws SQLException { return client.queryBySample(detail);
}
看到区别了吗?在不需要写一行XML的情况下,DAS用一行代码就可以搞定 mybatis需要十几行,甚至几十行配置才能完成的功能。其实上面显示的还只是完成这个功能完整mybatis配置的一小部分配置,不过已经足够说明我并没有吹牛
你一定会想,按样例查询这个例子还是非常容易提供通用实现的,如果要根据各种条件生成复杂,动态的SQL怎么办?是不是要写很多if-else语句自己拼?图样!这时候就要SqlBuilder出马了。还是让我们看看实际的代码对比: Mybatis mapping:
select * from (select ROW_NUMBER() OVER ( ORDER BY inserttime DESC ) rownum, from strategyaccountdetail${tableSuffix} WITH(NOLOCK) where userid = #{userid,jdbcType=INTEGER} and strategyid = #{strategyid,jdbcType=VARCHAR} and typeid = #{typeid,jdbcType=INTEGER} and inserttime = ]]> #{beginInserttime,jdbcType=TIMESTAMP} and inserttime #{endInserttime,jdbcType=TIMESTAMP} AND isactive=1) tpage WHERE tpage.rownum BETWEEN ${startPage} AND ${pageSize}
DAS对应代码:
public List selectListByUserIdExample(Long userId, String strategyid, Integer typeId,
Date beginInserttime, Date endInserttime, Integer pageNum, Integer pageSize) throws SQLException {
SqlBuilder builder = SqlBuilder.selectAllFrom(definition).where().allOf(definition.Userid.eq(userId),definition.Isactive.eq(1),
definition.Strategyid.eq(strategyid).nullable(),
definition.Typeid.eq(typeId).nullable(),definition.Inserttime.greaterThanOrEqual(beginInserttime).nullable(),
definition.Inserttime.lessThanOrEqual(endInserttime).nullable()).
orderBy(definition.Inserttime.desc()).into(Strategyaccountdetail.class).offset(pageNum, pageSize).withLock(); return client.query(builder);
}
使用SqlBuilder的DAS的code是不是还是一样紧致光滑?有人会说最新的mybatis也有SqlBuiler嘛。那我们就也比一比,不要说我骗人: Mybatis Sql builder:
public string selectPersonLike(final String id, final String firstName, final string lastlame) 《
return new SQL() {
{
SELECT(“P. ID, P.USERNAIE, P.PASSHORD, P.FIRST _NANE, P.LAST NAME”);
FROM(“PERSON P”) if (id != null) {
WHERE(“P.ID like#{id}”);
} if (firstlame != null) {
WHERE(“P.FIRST MAE like #{firstliase}”);
} if (lastlame != null) {
WHERE(“P.LAST NAMIE like #{lastName}”);
} ORDER BY(“P.LAST. NAME”);
}
}.toString();
}
DAS SqlBuilder:
public SqlBuilder seletPersonLike(final string id, final String firstlane, final string lastName) {
Person.PersonDefinition P = Person.PERSON; return sqlBuilder.selectAllFrom§ where().
allOf(
p.d.like(id).nullable(),
p.firstName.like(firstNane).nullable(),
p. lastNare .1ike(iastName).nullab1e()
).orderBy(p.lastName);
}
明显还是DAS的SqlBuilder设计更出色!
一步到位的提供API会存在一个设计风险,那就是任何操作都会存在特殊情况。比如一个简单的插入操作,就存在很多变体:
1. 在存在自增ID的情况下生成自增ID
2. 在存在自增ID的情况下使用自定义ID
3. 在存在自增ID的情况下生成自增ID并将生成的ID设置到输入实体
4. 等等
普通的做法是为每种特殊做法提供overload的方法,有几种特殊情况就提供几个方法。按照这种思路发展下去,方法的数量很快就会多到失控。如何才能确保在一个精简的API集合上提供尽可能多的特殊操作呢?这就轮到Hints登场了。
你可能注意到DasClient的方法除了必要参数外,往往还会带一个Hints。这个Hints要么是以可变参数存在,要么是作为必要参数的一个属性。DAS利用Hints传递特殊指令,帮助用户处理灵活多变的场景。以插入单条记录为例,API长这样:
public int insert(T entity, Hints…hints) throws SQLException
调用的时候既可以只传entity:
dao.insert§;
也可以传最多一个hints
dao.insert(p, hints.insertWithId());
无论哪种情况,方法只有一个。 虽然Hints也算不上脑洞特别大的发明,但与ORM结合得如此之紧密自然,别无分号。这种设计带来的便利是巨大的。不信可以参考一下如果用独立的分库分表组件会怎样实现:
// Sharding database and table with using hintManager ,
String sql = “SELECT * FROM t order”; try (HintManager hintManager = HintManager.getInstance(;
Connectlon conn = dataSource.getConnection();
PreparedStatement preparedstatement conn. prepareStatement(sq1)) {
hintManager.addDatabaseShardingValue(“t_order”, 1);
hintManager.addTableShardingValue(“t_order”, 2); try (ResultSet rs = preparedStatement.executeQuery()) { while (rs.next()) { //…
}
}
}
上面需要3行独立代码完成的Hints相关工作。倒不是说这个分库分表组件设计的不好。除了TiDB或Amazon Aurora这种真正的分布式数据库之外,绝大多数基于传统数据库之上的分库分表组件都难以做到完全的对应用代码透明。在特殊场景下都需要以某种方式传递特殊指令。如果依赖于现有ORM工具或基于JDBC,就会存在类似上面这种很不自然的代码。
而DAS通过将Hints与ORM接口结合的方式,完美的解决了特殊与一般的矛盾。同样的事情DAS只需要一行:
List<Person) plist = dao.query(selectAllFrom§. setHints(Hints.hints().shardValue(1).tableShardValue(2)));
在推广过程中我们还发现一个有趣的事情。就是我们以为用户喜欢透明的分库分表,但事实上,出于各种原因,用户用的最多的反而是直接指定分库分表。当然利用Hints可以很简单的做到:
List plist = dao.query(selectAllFrom§.setHints(Hints.hints(). inShard(1).inTableShard(2)));
自研ORM还有一个额外的好处。那就是虽然从成本还有技术的方面来看,分库分表技术目前还有市场,但长远来看,这大概率是一种过渡性的技术。即使哪天人们完全解决了分布式数据库的性能和一致性问题,也还是需要某种面向应用的ORM技术来实现灵活多变的需求。这样DAS就可以继续发挥作用。从今天的标准来看,DAS ORM的设计在易用性和灵活性上已经达到了能达到的极限。
DAS完美结合了ORM和分库分表功能,其产品定位是进可攻,退可守。根据公司内部实际使用效果来看,使用DAS能极大提高研发效率,减少代码量和出错概率,再也没有因配置导致的各种故障。
有一次偶尔路过听到一个总监和下面tech leader的对话,总监问如果技术输出,新的代码里面能否不用我们的DAS,leader微笑着但坚定的回答,不行,DAS很好用的,我要用。
对我们做框架的程序员来说,还有什么比一句好用更高的评价吗?
是好东西就要拿出来大家一起用,DAS已经开源,并提供了详尽的文档供大家参考,请大家尽情star~
GitHub地址:https://github.com/ppdaicorp/das
除了开源文档,我们还提供在线技术支持,有兴趣的朋友可以入群获得帮助或者更多活动信息。
最后说一句,不要重复造轮子是最广为人知的谬误。你不造,只是把机会让给别人。
赫杰辉,信也科技基础组件部门主管、信也DAS产品负责人、布道师。图形化构建工具集x-series的作者。曾主持开发携程开源数据库访问框架DAL。对应用开发效率提升和分布式数据库访问机制拥有有多年研究积累。
领取专属 10元无门槛券
私享最新 技术干货