Without XML Mapper, Fluent Mybatis code is SQL operation! Really fragrant?

Posted by AustinP on Mon, 28 Feb 2022 06:46:26 +0100

Hello, this is Guide. It's interesting to see an ORM framework Fluent Mybatis recently. The whole design concept is very in line with the thinking of engineers.

I have briefly sorted out some contents of the official documents. Through this article, I will take you to see this new ORM framework.

Official documents: https://gitee.com/fluent-mybatis/fluent-mybatis/wikis

Make a statement in advance: if you don't fully understand this kind of personal maintenance and development framework, you must not use it in formal projects! Otherwise, the follow-up problems will be very troublesome!!! At present, I am only interested in the framework of Fluent Mybatis. I want to learn its internal design.

Introduction to Fluent Mybatis

What is fluent Mybatis? Fluent Mybatis is a Mybatis syntax enhancement framework, which integrates the characteristics and advantages of Mybatis Plus, Dynamic SQL, JPA and other frameworks, and uses the annotation processor to generate code.

What are the highlights of Fluent Mybatis? Using Fluent Mybatis, you can construct complex business SQL statements through Java API without writing specific XML files, so as to achieve the integration of code logic and SQL logic. It is no longer necessary to assemble query or update operations in Dao, and then assemble parameters in XML or Mapper.

Project address: https://gitee.com/fluent-mybatis/fluent-mybatis

Series of articles: https://juejin.cn/column/7033388011911921678

Fluent Mybatis feature list

Fluent Mybatis properties

Fluent Mybatis principle

Fluent Mybatis principle

Fluent Mybatis vs Mybatis vs Mybatis Plus

Compared with native Mybatis, Mybatis Plus or other frameworks, what convenience does fluent mybatis provide?

Implementation requirements comparison

We realize and compare it through a typical business demand. If there is a student transcript, the structure is as follows:

create table `student_score`
(
    id           bigint auto_increment comment 'Primary key ID' primary key,
    student_id   bigint            not null comment 'Student number',
    gender_man   tinyint default 0 not null comment 'Gender, 0:female; 1:male',
    school_term  int               null comment 'semester',
    subject      varchar(30)       null comment 'subject',
    score        int               null comment 'achievement',
    gmt_create   datetime          not null comment 'Record creation time',
    gmt_modified datetime          not null comment 'Record the last modification time',
    is_deleted   tinyint default 0 not null comment 'Logical deletion ID'
) engine = InnoDB default charset=utf8;

Now there is a demand: Statistics of passing scores of three subjects ('English ',' Mathematics' and 'Chinese') in 2000 are based on semesters. The lowest, highest and average scores of subjects are counted, and the number of samples needs to be greater than 1. The statistical results are sorted by semesters and disciplines

We can write SQL statements as follows:

select school_term,
       subject,
       count(score) as count,
       min(score)   as min_score,
       max(score)   as max_score,
       avg(score)   as max_score
from student_score
where school_term >= 2000
  and subject in ('English', 'mathematics', 'language')
  and score >= 60
  and is_deleted = 0
group by school_term, subject
having count(score) > 1
order by school_term, subject;
Copy code

The above requirements are implemented with Fluent Mybatis, native Mybatis and Mybatis Plus.

Use Fluent Mybatis to realize the above functions:

Code address: https://gitee.com/fluent-Mybatis/fluent-Mybatis-docs/tree/master/spring-boot-demo/

We can see the ability of fluent api and the rendering effect of IDE on code.

Change to Mybatis native to realize the above functions:

1. Define Mapper interface

public interface MyStudentScoreMapper {
    List<Map<String, Object>> summaryScore(SummaryQuery paras);
}

2. Define the parameter entity SummaryQuery required by the interface

@Data
@Accessors(chain = true)
public class SummaryQuery {
    private Integer schoolTerm;

    private List<String> subjects;

    private Integer score;

    private Integer minCount;
}
Copy code

3. Define the mapper xml file that implements the business logic

<select id="summaryScore" resultType="map" parameterType="cn.org.fluent.Mybatis.springboot.demo.mapper.SummaryQuery">
    select school_term,
    subject,
    count(score) as count,
    min(score) as min_score,
    max(score) as max_score,
    avg(score) as max_score
    from student_score
    where school_term >= #{schoolTerm}
    and subject in
    <foreach collection="subjects" item="item" open="(" close=")" separator=",">
        #{item}
    </foreach>
    and score >= #{score}
    and is_deleted = 0
    group by school_term, subject
    having count(score) > #{minCount}
    order by school_term, subject
</select>
Copy code

4. Implement the business interface (here is the test class, which should correspond to Dao class in practical application)

@RunWith(SpringRunner.class)
@SpringBootTest(classes = QuickStartApplication.class)
public class MybatisDemo {
    @Autowired
    private MyStudentScoreMapper mapper;

    @Test
    public void Mybatis_demo() {
        // Construct query parameters
        SummaryQuery paras = new SummaryQuery()
            .setSchoolTerm(2000)
            .setSubjects(Arrays.asList("English", "mathematics", "language"))
            .setScore(60)
            .setMinCount(1);

        List<Map<String, Object>> summary = mapper.summaryScore(paras);
        System.out.println(summary);
    }
}
Copy code

In short, using Mybatis directly, the implementation steps are quite cumbersome and the efficiency is too low. What's the effect of switching to Mybatis Plus?

Change to Mybatis Plus to realize the above functions:

The implementation of Mybatis Plus is much simpler than that of Mybatis. The implementation effects are as follows

As circled in the red box, writing the Mybatis Plus implementation uses a lot of hard coding of strings (you can use the get lambda method of Entity to partially replace the string coding). Hard coding of strings will create a great threshold for development students. Personally, there are two main points:

  1. Difficulty in memorizing and typing field names
  2. The Entity property follows the runtime error after the database field is changed

Other frameworks, such as TkMybatis, are weaker in encapsulation and ease of use than Mybatis Plus, so they are no longer compared.

Generate code code comparison

Fluent Mybatis generation code settings:

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(
        /** Database connection information**/
        url = url, username = "root", password = "password",
        /** Entity Class parent package path**/
        basePack = "cn.org.fluent.Mybatis.springboot.demo",
        /** Entity Code source directory**/
        srcDir = "spring-boot-demo/src/main/java",
        /** Dao Code source directory**/
        daoDir = "spring-boot-demo/src/main/java",
        /** If the table definition record is created, the record is modified, and the field is deleted logically**/
        gmtCreated = "gmt_create", gmtModified = "gmt_modified", logicDeleted = "is_deleted",
        /** Table to generate file (Table Name: corresponding Entity name)**/
        tables = @Table(value = {"student_score"})
    )
    static class Abc {
    }
}

Mybatis Plus :

public class CodeGenerator {

    static String dbUrl = "jdbc:mysql://localhost:3306/fluent_Mybatis_demo?useSSL=false&useUnicode=true&characterEncoding=utf-8";

    @Test
    public void generateCode() {
        GlobalConfig config = new GlobalConfig();
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setDbType(DbType.MYSQL)
            .setUrl(dbUrl)
            .setUsername("root")
            .setPassword("password")
            .setDriverName(Driver.class.getName());
        StrategyConfig strategyConfig = new StrategyConfig();
        strategyConfig
            .setCapitalMode(true)
            .setEntityLombokModel(false)
            .setNaming(NamingStrategy.underline_to_camel)
            .setColumnNaming(NamingStrategy.underline_to_camel)
            .setEntityTableFieldAnnotationEnable(true)
            .setFieldPrefix(new String[]{"test_"})
            .setInclude(new String[]{"student_score"})
            .setLogicDeleteFieldName("is_deleted")
            .setTableFillList(Arrays.asList(
                new TableFill("gmt_create", FieldFill.INSERT),
                new TableFill("gmt_modified", FieldFill.INSERT_UPDATE)));

        config
            .setActiveRecord(false)
            .setIdType(IdType.AUTO)
            .setOutputDir(System.getProperty("user.dir") + "/src/main/java/")
            .setFileOverride(true);

        new AutoGenerator().setGlobalConfig(config)
            .setDataSource(dataSourceConfig)
            .setStrategy(strategyConfig)
            .setPackageInfo(
                new PackageConfig()
                    .setParent("com.mp.demo")
                    .setController("controller")
                    .setEntity("entity")
            ).execute();
    }
}

Comparison and summary of the three

After reading the implementation of the same function point in the three frameworks, you watchers will certainly have your own judgment. The author also summarizes a comparison here.

-

Mybatis Plus

Fluent Mybatis

code generation

Generate Entity

Generate Entity, and then generate Mapper, Query, Update and SqlProvider through compilation

Generator ease of use

low

high

Symbiotic relationship with Mybatis

The original SqlSessionFactoryBean needs to be replaced

There are no changes to Mybatis. How to use it or how to use it

Dynamic SQL construction method

When the application starts, the dynamic xml fragment is constructed according to the Entity annotation information and injected into the Mybatis parser

During application compilation, the SqlProvider of the corresponding method is compiled according to the Entity annotation, and associated with @ InsertProvider, @ SelectProvider, @ UpdateProvider annotation on the Mapper of Mybatis

Is the dynamic SQL result easy to DEBUG trace

Not easy to debug

It is easy to directly locate the SQLProvider method and set the breakpoint

Dynamic SQL construction

Hard code the field name or use the lambda expression of Entity's get method

Generate the corresponding method name by compiling, and call the method directly

Error discovery after field change

Those expressed by lambda of get method can be compiled and found, while those encoded by fields cannot be compiled and found

It can be found at compile time

Dynamic SQL construction method for different fields

Through interface parameter mode

Through the interface name method, the coding efficiency of Fluent API is higher

Syntax rendering features

nothing

Through the key variables select, update, set, and, or, the IDE syntax can be used for rendering, which is more readable

Fluent Mybatis actual combat

Next, let's see how to use Fluent Mybatis to add, delete, modify and query.

Introduce dependency

Create a new Maven project, set the project compilation level to Java8 or above, and introduce Fluent Mybatis dependency package.

<dependencies>
    <!-- introduce fluent-mybatis Run dependent package, scope by compile -->
    <dependency>
        <groupId>com.github.atool</groupId>
        <artifactId>fluent-mybatis</artifactId>
        <version>1.9.3</version>
    </dependency>
    <!-- introduce fluent-mybatis-processor, scope Set to provider Required for compilation, not for runtime -->
    <dependency>
        <groupId>com.github.atool</groupId>
        <artifactId>fluent-mybatis-processor</artifactId>
        <version>1.9.3</version>
    </dependency>
</dependencies>

Create table

create schema fluent_mybatis;

create table hello_world
(
    id           bigint unsigned auto_increment primary key,
    say_hello    varchar(100) null,
    your_name    varchar(100) null,
    gmt_created   datetime   DEFAULT NULL COMMENT 'Creation time',
    gmt_modified datetime   DEFAULT NULL COMMENT 'Update time',
    is_deleted   tinyint(2) DEFAULT 0 COMMENT 'Delete logically'
) ENGINE = InnoDB
  CHARACTER SET = utf8 comment 'Simple demonstration table';

Create the Entity class corresponding to the database table

Create the Entity class corresponding to the database table: helloworlditentity. You only need to do three simple actions:

  1. Name the Entity class and field according to the hump naming rules
  2. HelloWorldEntity inherits the IEntity interface class
  3. Annotate the helloworlditentity class with @ FluentMybatis
@FluentMybatis
public class HelloWorldEntity extends RichEntity {
    private Long id;

    private String sayHello;

    private String yourName;

    private Date gmtCreated;

    private Date gmtModified;

    private Boolean isDeleted;

    // get, set, toString methods

   @Override
   public Class<? extends IEntity> entityClass() {
      return HelloWorldEntity.class;
   }
}

Perform compilation.

IDE compilation:

Maven compile: mvn clean compile

gradle clean compile

Configure data source

  1. DataSource datasource configuration
  2. mapper scan path for Mybatis
  3. SqlSessionFactoryBean of Mybatis
@ComponentScan(basePackages = "cn.org.atool.fluent.mybatis.demo1")
@MapperScan("cn.org.atool.fluent.mybatis.demo1.entity.mapper")
@Configuration
public class HelloWorldConfig {
    /**
     * Set dataSource property
     *
     * @return
     */
    @Bean
    public DataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/fluent_mybatis?useUnicode=true&characterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        return dataSource;
    }

    /**
     * SqlSessionFactoryBean defining mybatis
     *
     * @param dataSource
     * @return
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean;
    }

   @Bean
   public MapperFactory mapperFactory() {
      return new MapperFactory();
   }
}

It's very simple. Here, you don't need to configure any Mybatis xml file or write any · interface, but you already have powerful functions of adding, deleting, modifying and checking, and it's Fluent API. Let's write a test to witness the magic power of Fluent Mybatis!

test

Inject the Mapper class corresponding to helloworldantity: HelloWorldMapper, which is generated during the compilation of Fluent Mybatis.

Use HelloWorldMapper to delete, insert, query and modify.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = HelloWorldConfig.class)
public class HelloWorldTest {
    /**
     * Fluent Mybatis Mapper class generated at compile time
     */
    @Autowired
    HelloWorldMapper mapper;

    @Test
    public void testHelloWorld() {
        /**
         * For the convenience of demonstration, delete the data first
         */
        mapper.delete(mapper.query()
            .where.id().eq(1L).end());
        /**
         * insert data
         */
        HelloWorldEntity entity = new HelloWorldEntity();
        entity.setId(1L);
        entity.setSayHello("hello world");
        entity.setYourName("Fluent Mybatis");
        entity.setIsDeleted(false);
        mapper.insert(entity);
        /**
         * Query data with id = 1
         */
        HelloWorldEntity result1 = mapper.findOne(mapper.query()
            .where.id().eq(1L).end());
        /**
         * The console prints the query results directly
         */
        System.out.println("1. HelloWorldEntity:" + result1.toString());
        /**
         * Update record with id = 1
         */
        mapper.updateBy(mapper.updater()
            .set.sayHello().is("say hello, say hello!")
            .set.yourName().is("Fluent Mybatis is powerful!").end()
            .where.id().eq(1L).end()
        );
        /**
         * Query data with id = 1
         */
        HelloWorldEntity result2 = mapper.findOne(mapper.query()
            .where.sayHello().like("hello")
            .and.isDeleted().eq(false).end()
            .limit(1)
        );
        /**
         * The console prints the query results directly
         */
        System.out.println("2. HelloWorldEntity:" + result2.toString());
    }
}

Output:

1. HelloWorldEntity:HelloWorldEntity{id=1, sayHello='hello world', yourName='Fluent Mybatis', gmtCreate=null, gmtModified=null, isDeleted=false}
2. HelloWorldEntity:HelloWorldEntity{id=1, sayHello='say hello, say hello!', yourName='Fluent Mybatis is powerful!', gmtCreate=null, gmtModified=null, isDeleted=false}

Amazing! Let's check the results in the database again

Now, we have demonstrated the powerful functions of Fluent Mybatis through a simple example. Before further introducing the more powerful functions of Fluent Mybatis, we reveal why we only write the Entity class corresponding to a data table, but have a series of database operation methods of adding, deleting, modifying and querying.

According to the @ Fluent Mybatis annotation on the Entity class, Fluent Mybatis will automatically compile and generate a series of files in the target directory and class directory during compilation:

-w300

The specific functions of these documents are as follows:

  • Mapper / * mapper: the mapper interface of mybatis defines a series of general data operation interface methods.
  • Dao / * BaseDao: Dao implements the base class. All DaoImpl inherit their own base classes. According to the principle of hierarchical coding, we will not directly use Mapper class in Service class, but reference Dao class. We implement specific data operation methods according to conditions in Dao implementation class.
  • Wrapper / * query: the core class of fluent mybatis, which is used to construct dynamic sql and query conditions.
  • Wrapper / * updater: the core class of fluent mybatis, which is used to dynamically construct update statements.
  • Helper / * mapping: Entity table field and Entity attribute mapping definition class
  • Helper / * segment: the specific function implementation of query and Updater, including several implementations: select, where, group by, having by, order by, limit
  • IEntityRelation: interface for handling Entity Association (one-to-one, one to many, many to many)
  • Ref: refers to the quick entry tool class of the object generated by Fluent Mybatis

summary

The above is just the way that Fluent Mybatis routinely implements addition, deletion, modification and query. Now Fluent Mybatis has launched a special form level addition, deletion, modification and query, which is realized by declaration. Official description: https://juejin.cn/post/7033388050012962852 .

·········· END ··············