阅读 266

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

39e6aef0d951457ba627d5d64c6142b5_th.png

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

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

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

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

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

目前各个框架的不足

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

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

Mybatis

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

但是用Mybatis的同学有没有觉得每个语句都要写个XML非常麻烦呢, 并且查找问题的时候从接口找对应的XML语句不太方便呢?

有同学会说了, 我们有Mybatis Generator, 可以自动生成XML文件, Mapper接口, 超级方便!!!

嗯, 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注解风格提供对联合查询的支持的~

感谢阅读~