【微服务】 如何简洁优雅的开发DAO层

39e6aef0d951457ba627d5d64c6142b5_th.png

DAO层, 是每一个web后端程序员都绕不过去的一个话题java

DAO层负责的内容很重要也很单一, 从数据库中读取数据而后放到Model里, 仅此而已git

说他难吧, 其实就是体力活, 何况在微服务架构下, 历来都是单表查询, 复杂SQL也不知道各位多久没用过了.程序员

说他简单吧, 写SQL而后一个一个调用set方法, 也是个挺麻烦的事儿.github

因此市面上琳琅满目的出现了一堆DAO层框架, 如今比较主流的有Mybatis, Spring Data Jpa, JdbcTemplate, 这些框架极大的简化了咱们访问数据库的过程web

目前各个框架的不足

首先先明确一下DAO层具体负责哪些事情呢, 一共三点数据库

  1. 特定查询转换为SQL
  2. SQL参数抽取
  3. 链接并管理链接(其实这个属于更底层的链接池的责任)
  4. 数据库查询结果转成Java Model 从这几点出发, 各个框架都有哪些不足呢

Mybatis

Mybatis虽然目前已经霸占了大部分互联网公司的ORM层的位置, 提供了对动态SQL良好的支持后端

可是用Mybatis的同窗有没有以为每一个语句都要写个XML很是麻烦呢, 而且查找问题的时候从接口找对应的XML语句不太方便呢?架构

有同窗会说了, 咱们有Mybatis Generator, 能够自动生成XML文件, Mapper接口, 超级方便!!!app

嗯, Mybatis Generator 是个很是好的用于开发的框架, 能够极大的简化开发量, 我也用过一段时间, 可是因为他是代码生成框架的缘由, 项目里会多很是多难以维护而且很是大的类, 好比Example , XML 什么的, 有没有有代码洁癖的同窗看着很是不爽呢~框架

解决办法有吗? 有! 有同感的同窗能够往下看

Spring Data Jpa

Jpa家族的老大哥(嗯...可能hibernate才算?)

第一次看到这个框架的时候眼前一亮, 卧槽, 太tm方便了啊. 基本的CRDU都给提供好了, 一行代码不用写

可是当Jpa遇到动态SQL的状况, 就很坑爹了

一句话形容: 初期Jpa一时爽, 动态SQL火葬场

那么有没有方法在保持Jpa的简洁性的基础上能够很爽的写动态SQL呢, 能! 有兴趣的同窗能够往下看

JdbcTemplate

Spring原生自带的Jdbc包装层, 其实算不上ORM框架, 可是仍是有一些公司在使用

优势呢, 固然是Spring全家桶自带, SpringBoot的状况下基本免配置, 接入起来很是方便, 从代码量来讲比Mybatis要少很多, 而且性能拔群, 比Mybatis快很多

缺点也很是的明显, 基本是Jdbc的进阶版, 其他功能少的可怜, 基本是直接操做SQL, 查询结果转Java Model也要手动去写代码

那么可不可让JdbcTemplate用起来更方便一点, 减小一些手动编码呢, 能够!

解决方案

其实咱们的需求很是简单, 一共就四点

  1. 用尽可能少的开发量实现DAO层(像Jpa同样基本的CRUD方法都由框架提供), 使得项目自己更加简洁
  2. 有良好的API支持动态SQL(这是Mybatis的优势)
  3. 能够自动将查询结果转化为Java Model
  4. 性能尽量的好一些

在这里推荐一下个人ORM层开源项目fastdao-JdbcTemplatePlus

经过这个咱们能够得到哪些好处呢

第一点 - 基本查询由框架提供

基本的CRUD功能由框架提供 首先基本的经过主键的CRUD由框架提供,不须要写一行代码, 同时包含了Mybatis Generator中实用的insertSelective, updateSelective方法,提供的方法以下

/** * insert DATA to db, all field will be set include null * if you want to update only when none of field is null you can use this method */
    int insert(DATA model);

    /** * insert DATA to db, only non-null field will be insert * if you want null field to be default value in db ,you can use this method */
    int insertSelective(DATA model);

    /** * update DATA by primaryKey, all field will be set include null * make sure primaryKey in DATA is set */
    int updateByPrimaryKey(DATA model);

    /** * update DATA by primaryKey, only non-null field will be updated */
    int updateByPrimaryKeySelective(DATA model);

    /** * delete by primaryKey */
    int deleteByPrimaryKey(PRIM_KEY primaryKey);

    /** * select by primaryKey */
    DATA selectByPrimaryKey(PRIM_KEY primaryKey);

    /** * multiple selection */
    List<DATA> selectByPrimaryKeys(Collection<PRIM_KEY> primaryKeys);

    /** * multiple selection */
    List<DATA> selectByPrimaryKeys(PRIM_KEY... primaryKeys);

复制代码

那么有些小伙伴要问了, 若是我须要自定义查询怎么办, 好比按照用户名搜索用户?, 这里好像不支持啊

不支持是不可能的, 可是这里须要一些额外的开发量, 也是这个框架惟一须要引入额外类的地方

这个框架经过如下方法支持自定义操做

/** * update by UpdateRequest * if you want to update only a few field, or want to update by condition, you can use this method */
    int update(UpdateRequest updateRequest);
    /** * delete by condition */
    int delete(DeleteRequest deleteRequest);
    /** * count by condition */
    int count(CountRequest countRequest);
    /** * select by QueryRequest * this method doesn't support extra function */
    List<DATA> select(QueryRequest queryRequest);
}
复制代码

举个例子, 好比咱们的User Model类定义以下

@Data
@ToString
public class User {
    private Long id;
    private String name;
    private Date updated;
    private Date created;
    private Boolean deleted;
}
复制代码

这里须要小伙伴们额外定义一个操做类, 其实就是定义一下Model类的每一个字段和数据库字段之间的定义关系,是否是很简单

public class Columns {
    public static final Column ID = new Column("id");
    public static final Column NAME = new Column("name");
    public static final Column UPDATED = new Column("updated");
    public static final Column CREATED = new Column("created");
    public static final Column DELETED = new Column("deleted");
}
复制代码

那么怎么写自定义查询呢, 好比说须要按照name查找User, 只要一行代码

public List<User> selectByName(String name){
        return dao.select(QueryRequest.newInstance().setCondition(NAME.eq(name)));
    }
复制代码

是否是很简单呢, 这里QueryRequest等同于一个SQL查询的实体, setCondition 操做添加了这个查询的条件, NAME就是咱们刚才定义的哪一个操做类的字段了. 那么其余操做呢? 好比只更新指定字段的操做能完成吗 ? 固然! 好比说若是只须要对name字段作更新的话 能够这么写, 更新指定id的name字段

public void updateName(Long id,String name){
        UpdateRequest request=UpdateRequest.newInstance().addUpdateField(NAME,name).setCondition(ID.eq(id));
        dao.update(request);
    }
复制代码

第二点 - 动态SQL支持

那么动态SQL语句应该怎么写呢, 将刚才的例子扩展一下, 若是须要按照 namecreated 的范围来查询 User(两个都是可选条件), 而且须要按照建立时间排序和分页.

public List<User> selectByNameAndCreated(String name,Date createdStart,Date createdEnd){
        Condition condition = Condition.and()
                .andOptional(NAME.eq(name))
                .andOptional(CREATED.gt(createdStart).lt(createdEnd))
                .allowEmpty();
        dao.select(QueryRequest.newInstance().setCondition(condition).addSort(CREATED,OrderEnum.DESC).offset(0).limit(20));
    }
复制代码

解释一下, Condition 是一个查询条件类,Condition.and()建立了一个多条件查询条件类, andOptional为这个类添加可选条件, 什么是可选条件呢,好比说NAME.eq(name)这个条件,当namenull的时候, 这个条件将被排除掉,created的条件同理, allowEmpty()表示该条件容许为空, 也就是说是否容许SELECT * FROM table这样的语句, 第二行的设置排序规则, offset,limit聪明的小伙伴应该一眼就能看懂, 应该不用我多解释了~(or 语句也是支持的)

是否是感受可读性还能够, 而且用起来很方便呢

第三点 - 自动将查询结果转换为Java Model

相信小伙伴们也能看出来, 这个是框架天然支持的, 同时, 框架支持一些额外的操做,好比说单个Model查询, 单字段查询, 一键分页查询, ON DUPLICATE KEY ,聚合函数等

int insert(InsertRequest insertRequest, Consumer<Number> doWithGeneratedKeyOnSuccess);
    /** * select by QueryRequest * if has multiple result, only return first row */
    DATA selectOne(QueryRequest queryRequest);
    /** * select by Single Field */
    <T> List<T> selectSingleField(QueryRequest queryRequest, Class<T> clazz);
    /** * support SqlFunction as request field */
    List<QueryResult<DATA>> selectAdvance(QueryRequest queryRequest);
    /** * select by a page, and count result will set to page */
    List<DATA> selectPage(QueryRequest request, Page page);
复制代码

insert 接口目前能够支持 ON DUPLICATE KEY 操做 selectOne 接口主要用于一些惟一索引查询, 最终只会返回一个Model selectSingleField 接口用于尽须要某个字段的查询(能够是聚合函数)(须要多个字段的状况下能够用通用的select(QueryRequest request) 方法手动指定须要查询的字段, 默认为全字段查询) selectAdvance 用于支持GROUP BY HAVING等须要聚合函数做为字段的状况 selectPage 能够自动执行count语句, 并将总的条目数 set 到Page

第四点 - 性能尽量的好

通过测试, 在不使用插件的状况下, 查询的执行性能是Mybatis 的 三倍左右, 可是因为是基于jdbcTemplate的框架, 比jdbcTemplate 慢 50%左右(不要介意插件是什么~详细解释能够解释去github上看哈)

项目地址

项目目前已经开源, 欢迎你们尝试使用, 喜欢的话能够点个STAR支持一下,也但愿多多提意见,ISSUES~ 详细的使用和接入方法请参考github的README-CN

项目地址 : jdbcTemplatePlus项目地址

目前的不足

因为我一直在互联网公司任职, 而且目前互联网公司都采用单表SQL查询, 多表联合的状况每每经过程序编码进行联合查询, 因此对多表联合的场景并无进行良好的支持(其实连支持都没有), 若是由这类需求的小伙伴能够在github上提ISSUE,若是需求比较普遍的话, 会按照Jpa注解风格提供对联合查询的支持的~

感谢阅读~

相关文章
相关标签/搜索