初探 FluentMybatis

1,780 阅读5分钟

初探 FluentMybatis

一个很偶然的机会,在掘金看到了Fluent MyBatis使用入门

自己其实一直在找一款可以完全不用写SQLORM框架,现在工作中使用的是Mybatis-Plus,可是Mybatis-Plus不支持多表连接查询,多表只能写SQL

当然也不是讨厌写SQL,只是IdeaXML的检查没有对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。生成的代码也比较全,从DomainController都有,而且配置更加灵活;后缀,包名,路径,作者还有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

Github地址 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),在编译时生成数据库操作所需要依赖的文件,包括BaseDaoMapperHelperWarpperEntity等等,如果表新增了字段,只需要重新编译就好,完全屏蔽了用户与Dao之间的关系,简化了持久层。

Java注解处理器,这篇文章不错,想了解的可以看一下。

所以程序员不用再操心持久层如何维护,只需要关心ServiceController就好了。项目结构一下子简化了,变得更加清晰。

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好用。

萝卜白菜各有所爱,最适合的才是最好的,而且还要看公司的技术路线。

最后

很久没有写了,一是没有什么好的材料,二是自己也比较懒,最新又喜欢玩游戏,这一拖就是一个多月,罪过罪过啊!以后还是要好好学习才行。

最后欢迎大家关注我的公众号,共同学习,一起进步。加油🤣