初探 FluentMybatis
一个很偶然的机会,在掘金看到了Fluent MyBatis使用入门 。
自己其实一直在找一款可以完全不用写SQL的ORM框架,现在工作中使用的是Mybatis-Plus,可是Mybatis-Plus不支持多表连接查询,多表只能写SQL。
当然也不是讨厌写SQL,只是Idea对XML的检查没有对Java代码检查那样严谨。所以很容易犯一些小错误,有时候发布之后才发现,然后再修改发布,浪费时间。
然后就发现了FluentMybatis,一开始看到的时候还是挺惊喜的。
然后花了一天时间看了一下。
大概说一下吧。
依赖
<dependency>
<groupId>com.github.atool</groupId>
<artifactId>fluent-mybatis</artifactId>
<version>1.4.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.atool</groupId>
<artifactId>fluent-mybatis-processor</artifactId>
<version>1.4.1</version>
<scope>provided</scope>
</dependency>
<!--代码生成器-->
<dependency>
<groupId>com.github.atool</groupId>
<artifactId>generator</artifactId>
<version>1.0.2</version>
</dependency>
<!--连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.5.0</version>
</dependency>
如果用了代码生成器,要加一下代码生成器的依赖。我一开始没加,报错找不到FileGenerator这个类。还有示例里用的连接池是dbcp2。
代码生成器
FluentMybatis也有代码生成器,但是和Mybatis-Plus不一样。Mybatis-Plus需要使用模板,例如Freemaker。生成的代码也比较全,从Domain到Controller都有,而且配置更加灵活;后缀,包名,路径,作者还有AR模式都可以配置,也更符合三层架构的理念。
FluentMybatis配置没有那么灵活,可以说基本没有什么配置,指定好数据库,路径,表名就完了。注解模式如下:
public class AppEntityGenerator {
static final String url = "jdbc:mysql://localhost:3306/fluent_mybatis_demo?useSSL=false&useUnicode=true&characterEncoding=utf-8";
public static void main(String[] args) {
FileGenerator.build(Abc.class);
}
@Tables(
/** 数据库连接信息 **/
url = url, username = "root", password = "password",
/** Entity类parent package路径 **/
basePack = "com.ler.demo",
/** Entity代码源目录 **/
srcDir = "/src/main/java",
/** Dao代码源目录 **/
daoDir = "/src/main/java",
/** 如果表定义记录创建,记录修改,逻辑删除字段 **/
gmtCreated = "gmt_create", gmtModified = "gmt_modified", logicDeleted = "is_deleted",
/** 需要生成文件的表 **/
tables = @Table(value = {"student_score","student","county_division"})
)
static class Abc {
}
}
或者用实体类模式
public class EntityGeneratorDemo {
static final String url = "jdbc:mysql://localhost:3306/fluent_mybatis_demo?useSSL=false&useUnicode=true&characterEncoding=utf-8";
public static void main(String[] args) throws Exception {
FileGenerator.build(true, false)
.globalConfig(g -> g
/** 设置src/java 所在的路径 **/
.setOutputDir("src/main/java/")
/** 设置package **/
.setBasePackage("com.ler.demo")
//.setDaoPackage("com.ler.demo.service")
/** 设置数据库连接 **/
.setDataSource(url, "root", "password")
)
.tables(t -> t
/** 设置需要生成Entity类的表 **/
.table("your_table1")
//.table("其它表")
//.table("其它表")
)
.execute();
}
}
虽然简单了一点,但是灵活性就没有了。不过有一个亮点就是,FluentMybatis没有使用模板,她使用的是JavaPoet 。
JavaPoet
JavaPoet是用于代码生成的开源编程框架,利用JavaPoet可以方便生成.java文件。感兴趣的可以去研究一下,还是挺有意思的。FluentMybatis使用的就是javapoet 。
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
JavaPoet就不说了,网上也有挺多博客讲这个的。今天只是初探一下FluentMybatis。
再说代码生成器,她生成三个文件,Domain实体类,Dao接口和DaoImpl实现类。个人感觉其实这个Dao就是Service,而DaoImpl就是ServiceImpl。
可是不能修改,如果包名、后缀还有作者可以自定义就好了。看久了还是Service顺眼一点。
注解处理器(Annotation Processor)
FluentMybatis另一个亮眼的地方就是Mapper文件的管理。使用过Mybatis-Plus的人都知道,Mybatis-Plus会生成Mapper
接口文件,还可以生成XML文件,因为她不支持多表查询,所以有时候复杂的查询需要我们在XML中写一些SQL(在接口中使用注解也可以写SQL)。
可是FluentMybatis不管多复杂的SQL都支持,所以她的Mapper文件是没有暴露出来的。
她通过注解处理器(Annotation Processor),在编译时生成数据库操作所需要依赖的文件,包括BaseDao,Mapper,Helper,Warpper,Entity等等,如果表新增了字段,只需要重新编译就好,完全屏蔽了用户与Dao之间的关系,简化了持久层。
Java注解处理器,这篇文章不错,想了解的可以看一下。
所以程序员不用再操心持久层如何维护,只需要关心Service和Controller就好了。项目结构一下子简化了,变得更加清晰。
SQL的拼接
虽然是初探,还是简单说一下是如何使用代码拼接SQL的吧。
FluentMybatis的目标就是支持各种SQL,无论多复杂,只要能写出来,就能拼出来。看文档的例子就知道,所以比Mybatis-Plus复杂一点。
简单的写几个吧,都是从文档复制过来的,具体的可以去看文档 Gitee地址 文档。
嵌套查询:
SELECT * FROM student
WHERE is_deleted = ?
AND grade = ?
AND home_county_id IN
(SELECT id FROM county_division WHERE is_deleted = ? AND province = ? AND city = ?)
对应的java代码:
StudentQuery query = new StudentQuery()
.where.isDeleted().isFalse()
.and.grade().eq(4)
.and.homeCountyId().in(CountyDivisionQuery.class, q -> q
.selectId()
.where.isDeleted().isFalse()
.and.province().eq("浙江省")
.and.city().eq("杭州市")
.end()
).end();
List<StudentEntity> students = studentMapper.listEntity(query);
EXISTS函数:
SELECT * FROM student
WHERE is_deleted = ?
AND EXISTS (SELECT id FROM student_score
WHERE is_deleted = ?
AND school_term = ?
AND score < ?
AND subject IN (?, ?)
AND student_id =student.id)
对应的java代码:
StudentQuery query = new StudentQuery()
.where.isDeleted().isFalse()
.and.exists(StudentScoreQuery.class, q -> q
.selectId()
.where.isDeleted().isFalse()
.and.schoolTerm().eq(2019)
.and.score().lt(60)
.and.subject().in(new String[]{"语文", "数学"})
.and.studentId().apply("= student.id")
.end()
).end();
List<StudentEntity> students = studentMapper.listEntity(query);
JOIN操作:
SELECT t1.user_name, t3.subject, t3.score
FROM student t1
JOIN county_division t2 ON t1.home_county_id = t2.id
JOIN student_score t3 ON t1.id = t3.student_id
WHERE t1.is_deleted = ?
AND t2.is_deleted = ? AND t2.province = ? AND t2.city = ?
AND t3.is_deleted = ? AND t3.school_term = ? AND t3.subject IN (?, ?) AND t3.score >= ?
对应的java代码:
Parameters parameters = new Parameters();
IQuery query = JoinBuilder
.<StudentQuery>from(new StudentQuery("t1", parameters)
.select.userName().end()
.where.isDeleted().isFalse().end())
.join(new CountyDivisionQuery("t2", parameters)
.where.isDeleted().isFalse()
.and.province().eq("浙江省")
.and.city().eq("杭州市").end())
.on(l -> l.where.homeCountyId(), r -> r.where.id()).endJoin()
.join(new StudentScoreQuery("t3", parameters)
.select.subject().score().end()
.where.isDeleted().isFalse()
.and.schoolTerm().eq(2019)
.and.subject().in(new String[]{"语文", "数学"})
.and.score().ge(90).end())
.on(l -> l.where.id(), r -> r.where.studentId()).endJoin()
.build();
studentMapper.listMaps(query);
JOIN 和 OR操作:
SELECT t1.user_name, t3.subject, t3.score
FROM student t1
JOIN county_division t2 ON t1.home_county_id = t2.id
JOIN student_score t3 ON t1.id = t3.student_id
WHERE t1.is_deleted = ?
AND t2.is_deleted = ? AND t2.province = ? AND t2.city = ?
AND t3.is_deleted = ? AND t3.school_term = ?
AND ( subject = ? OR subject = ? )
AND t3.score >= ?
对应的java代码:
Parameters parameters = new Parameters();
IQuery query = JoinBuilder
.<StudentQuery>from(new StudentQuery("t1", parameters)
.select.userName().end()
.where.isDeleted().isFalse().end())
.join(new CountyDivisionQuery("t2", parameters)
.where.isDeleted().isFalse()
.and.province().eq("浙江省")
.and.city().eq("杭州市").end())
.on(l -> l.where.homeCountyId(), r -> r.where.id()).endJoin()
.join(new StudentScoreQuery("t3", parameters)
.select.subject().score().end()
.where.isDeleted().isFalse()
.and.schoolTerm().eq(2019)
.and(iq -> iq
.where.subject().eq("语文")
.or.subject().eq("数学").end())
.and.score().ge(90).end())
.on(l -> l.where.id(), r -> r.where.studentId()).endJoin()
.build();
studentMapper.listMaps(query);
这里主要是留个例子,方便以后忘记怎么拼接的时候,可以按照这个写一下。
总结
技术在发展,FluentMybatis使用的东西有的我也是第一次听说。
虽然有些地方很先进,可以完全不用写SQL,可是看到上面的拼接SQL的方式,我觉得使用起来也没那么简单。
上手虽然难了一点,不过熟练了肯定比Mybatis-Plus好用。
萝卜白菜各有所爱,最适合的才是最好的,而且还要看公司的技术路线。
最后
很久没有写了,一是没有什么好的材料,二是自己也比较懒,最新又喜欢玩游戏,这一拖就是一个多月,罪过罪过啊!以后还是要好好学习才行。
最后欢迎大家关注我的公众号,共同学习,一起进步。加油🤣