DAS是信也科技自研的数据库访问中间件。是集数据库管理,ORM,动态SQL构建和分库分表支持的一体化关系型数据库访问解决方案。java
看到这里,你必定会说少年啊!已经有了那么多ORM框架和分库分表组件,像Hibernate,Mybatis,mycat,sharding jdbc,还有咱们最爱的携程DAL可供选择,干吗还要重复造轮子?git
答案很简单,这些工具都很差用!而DAS是咱们最新发明的高科技轮子。程序员
警告!前方高能!非资深开发人员尽快撤离。请握紧鼠标,抓牢键盘!github
你必定奇怪,既然要作数据库中间件,为何不像其余产品那样,从JDBC或者数据库协议层入手,在传统数据库上面作分库分表或从新开发数据库引擎?那样多牛逼啊!现有程序不用改就能够无缝移植。为何DAS还要提供ORM功能呢?sql
回答这个问题以前,让咱们先简单回顾一下流行的数据库编程过程。这有助于理解DAS的产品定位。数据库
mybatis,Hibernate发明的年代尚未什么数据库垂直与水平扩容,分库分表之类的概念,天然也不会从设计上加以考虑。而如今随便一个互联网公司,天天产生的数据都是天量。所以一个正经的数据库项目每每会同时用到ORM工具和分库分表组件。不管是ORM仍是分库分表组件,通常都须要繁琐的配置。区别只在于难度级别是受得了仍是劝退。编程
以最流行的mybatis+任意分库分表组件为例,若是你是一个资深的CRUD boy,确定很是熟悉下面的套路,在开始写下图中间最终实际的DAO代码以前,你须要先搞定另外四件事情:安全
当你手忙脚乱搞好这些配置,第一次测试时候,十有八九不会成功,这个时候千万不要气馁,由于更惨的还在后面,当在项目中使用独立的ORM和分库分表组件时,你会难过到流泪:session
而随着库,表的增加,作这些事情的痛苦指数从痛苦向无限痛苦飞速发展。若是你以为这没什么,那你必定是能享受福报的那一批人mybatis
当最终克服万难代码能工做时,你会发现跟整体的配置和代码量相比,最终的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
DAS是Database Access Service的缩写。DAS的目标就是给研发人员提供一个一站式的数据库访问框架,让研发人员用最简单直接的方式开发数据库访问代码,实现上面全部非分的想法
为实现这个目标,DAS提供:
但DAS的真正的核心优点不是这些组件,咱们build了一个专业的团队,7*24小时主动为程序员服务,帮你们搞定从原子到宇宙尺度的任何数据库问题
在信也科技,研发人员发邮件告诉DAS团队各个环境的数据库配置和逻辑数据库信息,DAS团队经过DAS Console配置好并自动同步到公司的配置中心后,用户只要在本身的项目里面引入DAS Client的依赖就能够开始直接写代码。对,你没有看错,直接开始写代码,无需任何的本地配置工做。咱们把中间件产品的研发从交付组件提高到交付服务的层面
这,才是咱们成功的秘密![撒花]
你心中必定冷笑,吹吧你!那让咱们从技术角度看看 DAS的核心DAS Client 到底长什么样
DAS Client的设计听从分层抽象原则,从上到下分为:
DAO层是程序员使用最频繁的部分,今天会重点介绍这一部分,其余部分会在将来会逐一提供,请关注咱们的拍码场公众号。
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<Person> plist = dao.queryBySample(pk);
客户端建立到使用,两行代码完事,是否是很简单粗暴?像我说的同样,若是你要完成一个查询,你须要提供就只是数据库名和SQL,这里SQL用sample data表示。除此之外,没有多余动做。没有session,没有事务,也没有connection。只要写的代码足够少,BUG就不会追上我。这就是传说中的极简编程风
经过这种预约义API的方式能节省多少代码呢?再以一个实际例子对比一下完成一样功能mybatis和DAS之间代码量:
Mybatis mapping:
<select id="selectByExample" parameterType="com.ppdai.xxxxxxxxxxxxxxxxxxx.StrategyAccountDetailExample" resultMap="BaseResultMap"> select <if test="distinct"> distinct </if> 'false' as QUERYID, <include refid="Base_Column_List" /> from strategyaccountdetail${tableSuffix} <if test="_parameter != null"> <include refid="Example_Where_Clause" /> </if> <if test="orderByClause != null"> order by ${orderByClause} </if> </select>
DAS对应代码:
public List<Strategyaccountdetail> selectByExample(Strategyaccountdetail detail) throws SQLException { return client.queryBySample(detail); }
看到区别了吗?在不须要写一行XML的状况下,DAS用一行代码就能够搞定 mybatis须要十几行,甚至几十行配置才能完成的功能。其实上面显示的还只是完成这个功能完整mybatis配置的一小部分配置,不过已经足够说明我并无吹牛
你必定会想,按样例查询这个例子仍是很是容易提供通用实现的,若是要根据各类条件生成复杂,动态的SQL怎么办?是否是要写不少if-else语句本身拼?图样!这时候就要SqlBuilder出马了。仍是让咱们看看实际的代码对比:
Mybatis mapping:
<select id="selectListByUserIdExample" parameterType="java.util.Map" resultMap="BaseResultMap"> select * from (select ROW_NUMBER() OVER ( ORDER BY inserttime DESC ) rownum, <include refid="Base_Column_List" /> from strategyaccountdetail${tableSuffix} WITH(NOLOCK) where userid = #{userid,jdbcType=INTEGER} <if test="strategyid != null and strategyid != ''"> and strategyid = #{strategyid,jdbcType=VARCHAR} </if> <if test="typeid != null"> and typeid = #{typeid,jdbcType=INTEGER} </if> <if test="beginInserttime != null"> and inserttime <![CDATA[>= ]]> #{beginInserttime,jdbcType=TIMESTAMP} </if> <if test="endInserttime != null"> and inserttime <![CDATA[<= ]]> #{endInserttime,jdbcType=TIMESTAMP} </if> AND isactive=1) tpage WHERE tpage.rownum BETWEEN ${startPage} AND ${pageSize} </select>
DAS对应代码:
public List<Strategyaccountdetail> 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(p) where(). allOf( p.d.like(id).nullable(), p.firstName.like(firstNane).nullable(), p. lastNare .1ike(iastName).nullab1e() ).orderBy(p.lastName); }
明显仍是DAS的SqlBuilder设计更出色!
一步到位的提供API会存在一个设计风险,那就是任何操做都会存在特殊状况。好比一个简单的插入操做,就存在不少变体:
普通的作法是为每种特殊作法提供overload的方法,有几种特殊状况就提供几个方法。按照这种思路发展下去,方法的数量很快就会多到失控。如何才能确保在一个精简的API集合上提供尽量多的特殊操做呢?这就轮到Hints登场了。
你可能注意到DasClient的方法除了必要参数外,每每还会带一个Hints。这个Hints要么是以可变参数存在,要么是做为必要参数的一个属性。DAS利用Hints传递特殊指令,帮助用户处理灵活多变的场景。以插入单条记录为例,API长这样:
public <T> int insert(T entity, Hints...hints) throws SQLException
调用的时候既能够只传entity:
dao.insert(p);
也能够传最多一个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(p). setHints(Hints.hints().shardValue(1).tableShardValue(2)));
在推广过程当中咱们还发现一个有趣的事情。就是咱们觉得用户喜欢透明的分库分表,但事实上,出于各类缘由,用户用的最多的反而是直接指定分库分表。固然利用Hints能够很简单的作到:
List<Person> plist = dao.query(selectAllFrom(p).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
除了开源文档,咱们还提供在线技术支持,有兴趣的朋友能够入群得到帮助或者更多活动信息
最后说一句,不要重复造轮子是最广为人知的谬误。你不造,只是把机会让给别人。
做者介绍
Hejiehui,信也科技基础组件部门主管、信也DAS产品负责人、布道师。图形化构建工具集x-series的做者。曾主持开发携程开源数据库访问框架DAL。对应用开发效率提高和分布式数据库访问机制有多年的研究积累