
DAO层, 是每个web后端程序员都绕不过去的一个话题
DAO层负责的内容很重要也很单一, 从数据库中读取数据然后放到Model里, 仅此而已
说他难吧, 其实就是体力活, 况且在微服务架构下, 从来都是单表查询, 复杂SQL也不知道各位多久没用过了.
说他简单吧, 写SQL然后一个一个调用set方法, 也是个挺麻烦的事儿.
所以市面上琳琅满目的出现了一堆DAO层框架, 现在比较主流的有Mybatis, Spring Data Jpa, JdbcTemplate, 这些框架极大的简化了我们访问数据库的过程
目前各个框架的不足
首先先明确一下DAO层具体负责哪些事情呢, 一共三点
- 特定查询转换为SQL
- SQL参数抽取
- 连接并管理连接(其实这个属于更底层的连接池的责任)
- 数据库查询结果转成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用起来更方便一点, 减少一些手动编码呢, 可以!
解决方案
其实我们的需求非常简单, 一共就四点
- 用尽量少的开发量实现DAO层(像Jpa一样基本的CRUD方法都由框架提供), 使得项目本身更加简洁
- 有良好的API支持动态SQL(这是Mybatis的优点)
- 可以自动将查询结果转化为Java Model
- 性能尽可能的好一些
在这里推荐一下我的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语句应该怎么写呢, 将刚才的例子扩展一下, 如果需要按照 name 和 created 的范围来查询 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)这个条件,当name为null的时候, 这个条件将被排除掉,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注解风格提供对联合查询的支持的~
感谢阅读~