Mybatis-Flex实战

一,依赖引入

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.12</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mybatis-flex</groupId>
            <artifactId>mybatis-flex-spring-boot-starter</artifactId>
            <version>1.5.4</version>
        </dependency>
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </dependency>

二,插件引入

这里的插件是为了,我们能在代码中直接引入Def类,进行操作,如果没有这个类,需要我们手动去target的generated-sources中,把代码设置为root根目录下才可以,为了方便,我们直接用插件即可

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                        <!--                    <path>-->
                        <!--                        <groupId>org.projectlombok</groupId>-->
                        <!--                        <artifactId>lombok-mapstruct-binding</artifactId>-->
                        <!--                        <version>${lombok-mapstruct-binding.version}</version>-->
                        <!--                    </path>-->
                        <!--                    <path>-->
                        <!--                        <groupId>org.mapstruct</groupId>-->
                        <!--                        <artifactId>mapstruct-processor</artifactId>-->
                        <!--                        <version>${org.mapstruct.version}</version>-->
                        <!--                    </path>-->
                        <path>
                            <groupId>com.mybatis-flex</groupId>
                            <artifactId>mybatis-flex-processor</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>

三,实际操作

在需要查询的类中引入:

import static com.mybatis.flex.entity.table.PersonTableDef.PERSON;

1.新增操作:

这里要注意的是,默认的新增方法,没有忽略null值,比如你的实体类没有设置createtime的值,那在插入语句中,就会把createtime设置为null,但是我们需要通过填充手段设置它的值,所以,这块建议使用insertSelective,会帮助我们自动忽略null值,当然也可以直接使用insert(entity,ignoreNulls)

  • insert(entity):插入实体类数据,不忽略 null 值。
  • insertSelective(entity):插入实体类数据,但是忽略 null 的数据,只对有值的内容进行插入。这样的好处是数据库已经配置了一些默认值,这些默认值才会生效。
  • insert(entity, ignoreNulls):插入实体类数据。
  • insertWithPk(entity):插入带有主键的实体类,不忽略 null 值。
  • insertSelectiveWithPk(entity):插入带有主键的实体类,忽略 null 值。
  • insertWithPk(entity, ignoreNulls):带有主键的插入,此时实体类不会经过主键生成器生成主键。
  • insertBatch(entities):批量插入实体类数据,只会根据第一条数据来构建插入的字段内容。
  • insertBatch(entities, size):批量插入实体类数据,按 size 切分。

2.删除操作

  • deleteById(id):根据主键删除数据。如果是多个主键的情况下,需要传入数组,例如:new Integer[]{100,101}
  • deleteBatchByIds(ids):根据多个主键批量删除数据。
  • deleteBatchByIds(ids, size):根据多个主键批量删除数据。
  • deleteByMap(whereConditions):根据 Map 构建的条件来删除数据。
  • deleteByCondition(whereConditions):根据查询条件来删除数据。
  • deleteByQuery(queryWrapper):根据查询条件来删除数据。

3.修改操作

  1.简单更新

  • update(entity):根据主键来更新数据,若实体类属性数据为 null,该属性不会新到数据库。
  • update(entity, ignoreNulls):根据主键来更新数据到数据库。
  • updateByMap(entity, whereConditions):根据 Map 构建的条件来更新数据。
  • updateByMap(entity, ignoreNulls, whereConditions):根据 Map 构建的条件来更新数据。
  • updateByCondition(entity, whereConditions):根据查询条件来更新数据。
  • updateByCondition(entity, ignoreNulls, whereConditions):根据查询条件来更新数据。
  • updateByQuery(entity, queryWrapper):根据查询条件来更新数据。
  • updateByQuery(entity, ignoreNulls, queryWrapper):根据查询条件来更新数据。
  • ~updateNumberAddByQuery(fieldName, value, queryWrapper):执行类似 update table set field = field + 1 where ... 的场景。~
  • ~updateNumberAddByQuery(column, value, queryWrapper):执行类似 update table set field = field + 1 where ... 的场景。~
  • ~updateNumberAddByQuery(fn, value, queryWrapper):执行类似 update table set field = field + 1 where ... 的场景。~

  2.部分字段更新(比如希望吧某些字段的值设置为null)

Account account = UpdateEntity.of(Account.class, 100);
//Account account = UpdateEntity.of(Account.class);
//account.setId(100);

account.setUserName(null);
account.setAge(10);

accountMapper.update(account);

 3.部分字段更新增强,比如希望把某个字段在原来的基础上加一

//比如这样的sql:
update tb_account
set user_name = ?, age = age + 1 where id = ?

//那么我们需要这样写
Account account = UpdateEntity.of(Account.class, 100);

account.setUserName(null);

// 通过 UpdateWrapper 操作 account 数据
UpdateWrapper wrapper = UpdateWrapper.of(account);
wrapper.setRaw("age", "age + 1")

accountMapper.update(account);

//或者通过def类
Account account = UpdateEntity.of(Account.class, 100);

account.setUserName("Michael");

// 通过 UpdateWrapper 操作 account 数据
UpdateWrapper wrapper = UpdateWrapper.of(account);
wrapper.set(ACCOUNT.AGE, ACCOUNT.AGE.add(1))

accountMapper.update(account);

//或者
Account account = UpdateEntity.of(Account.class, 100);

account.setUserName("Michael");

// 通过 UpdateWrapper 操作 account 数据
UpdateWrapper wrapper = UpdateWrapper.of(account);
wrapper.set(ACCOUNT.AGE, select().from(...))

accountMapper.update(account);

4.查询操作

 1.简单查询

这几个就没啥说的了,操作很简单

  • selectOneById(id):根据主键查询数据。
  • selectOneByMap(whereConditions):根据 Map 构建的条件来查询数据。
  • selectOneByCondition(whereConditions):根据查询条件查询数据。
  • selectOneByQuery(queryWrapper):根据查询条件来查询 1 条数据。
  • selectListByIds(ids):根据多个主键来查询多条数据。
  • selectListByMap(whereConditions):根据 Map 来构建查询条件,查询多条数据。
  • selectListByMap(whereConditions, count):根据 Map 来构建查询条件,查询多条数据。
  • selectListByCondition(whereConditions):根据查询条件查询多条数据。
  • selectListByCondition(whereConditions, count):根据查询条件查询多条数据。
  • selectListByQuery(queryWrapper):根据查询条件查询数据列表。
  • selectListByQuery(queryWrapper, consumers):根据查询条件查询数据列表。

 2.游标查询

众所周知,当一次查询数据量太多时,如果一下把系统内存打满,会出现内存溢出的问题,例如在大数据量的excel导出时,就很有必要使用这个了

Cursor<T> selectCursorByQuery(QueryWrapper queryWrapper);

使用实例如下:

Db.tx(() -> {
    Cursor<Account> accounts = accountMapper.selectCursorByQuery(query);
    for (Account account : accounts) {
        System.out.println(account);
    }
    return true;
});

以上的示例中,数据库并不是把所有的数据一次性返回给应用,而是每循环 1 次才会去数据库里拿 1 条数据,这样,就算有 100w 级数据,也不会导致我们应用内存溢出,同时,在 for 循环中, 我们可以随时终止数据读取。

但由于游标查询是在 for 循环的时候,才去数据库拿数据。因此必须保证 selectCursorByQuery 方法及其处理必须是在事务中进行,才能保证其链接并未与数据库断开。

3.分页查询

单表分页

  • paginate(pageNumber, pageSize, queryWrapper):分页查询。
  • paginate(pageNumber, pageSize, whereConditions):分页查询。

多表联查

  • paginateAs(pageNumber, pageSize, queryWrapper, asType):分页查询。
  • paginateAs(page, queryWrapper, asType):分页查询。

4.多表联查

其实有很多种方式,我这里只举例用join的方式,简单的场景可以用flex,我觉得复杂的sql,大家还是写xml来的比较快

扫描二维码关注公众号,回复: 16753701 查看本文章

1.字段一一对应

1、定义 ArticleDTO 类,ArticleDTO 里定义 tb_account 表的字段映射。

public class ArticleDTO {

    private Long id;
    private Long accountId;
    private String title;
    private String content;

    //以下用户相关字段
    private String userName;
    private int age;
    private Date birthday;
}

2、使用 QueryWrapper 构建 left join 查询,查询结果通过 ArticleDTO 类型接收。

QueryWrapper query = QueryWrapper.create()
        .select(ARTICLE.ALL_COLUMNS)
        .select(ACCOUNT.USER_NAME,ACCOUNT.AGE,ACCOUNT.BIRTHDAY)
        .from(ARTICLE)
        .leftJoin(ACCOUNT).on(ARTICLE.ACCOUNT_ID.eq(ACCOUNT.ID))
        .where(ACCOUNT.ID.ge(0));

List<ArticleDTO> results = mapper.selectListByQueryAs(query, ArticleDTO.class);
System.out.println(results);

 2.字段名不一致

public class ArticleDTO {

  private Long id;
  private Long accountId;
  private String title;
  private String content;

  //以下用户字段 和 用户表定义的列不一致,表定义的列为 user_name
  private String authorName;
  private int authorAge;
  private Date birthday;
}

那么, QueryWrapper 需要添加 as,修改如下:

QueryWrapper query = QueryWrapper.create()
    .select(ARTICLE.ALL_COLUMNS)
    .select(ACCOUNT.USER_NAME.as(ArticleDTO::getAuthorName)
            ,ACCOUNT.AGE.as(ArticleDTO::getAuthorAge)
            ,ACCOUNT.BIRTHDAY
    )
    .from(ARTICLE)
    .leftJoin(ACCOUNT).on(ARTICLE.ACCOUNT_ID.eq(ACCOUNT.ID))
    .where(ACCOUNT.ID.ge(0));

List<ArticleDTO> results = mapper.selectListByQueryAs(query, ArticleDTO.class);
System.out.println(results);

3.一对多自动映射

public class AccountVO {

    private Long id;
    private String userName;
    private int age;

    //账户拥有的 图书列表
    private List<Book> books;
}
List<AccountVO> bookVos = QueryChain.of(accountMapper)
    .select(
        ACCOUNT.ID,
        ACCOUNT.USER_NAME,
        ACCOUNT.AGE,
        BOOK.TITLE,
        BOOK.CONTENT,
     )
    .from(ACCOUNT)
    .leftJoin(BOOK).on(ACCOUNT.ID.eq(BOOK.ACCOUNT_ID))
    .where(ACCOUNT.ID.ge(100))
    .listAs(AccountVO.java);

如果外边的一层和里边的一层有的字段名相同,flex帮我们设置了别名,同样会帮我们自动映射

public class AccountVO {

    private Long id;
    private String name;
    private int age;

    //账户拥有的 图书列表
    private List<Book> book;
}
public class Book {
    private Long id;
    private Long accountId;
    private String name;
}

在以上的嵌套定义中, AccountVO 以及 Book 都包含了 id 和 name 的定义,假设我们查询的方法如下:

List<AccountVO> bookVos = QueryChain.of(accountMapper)
    .select(
        ACCOUNT.ID,
        ACCOUNT.NAME,
        ACCOUNT.AGE,
        BOOK.ID,
        BOOK.NAME,
     )
    .from(ACCOUNT)
    .leftJoin(BOOK).on(ACCOUNT.ID.eq(BOOK.ACCOUNT_ID))
    .where(ACCOUNT.ID.ge(100))
    .listAs(AccountVO.java);

其执行的 SQL 如下:

select tb_account.id, tb_account.name, tb_account.age,
    tb_book.id as tb_book$id, -- Flex 发现有重名时,会自动添加上 as 别名
    tb_book.name as tb_book$name  -- Flex 发现有重名时,会自动添加上 as 别名
from tb_account
left join tb_book on tb_account.id = tb_book.account_id
where tb_account.id >= 100

此时,查询的数据可以正常映射到 AccountVO 类。

注意,在 QueryWrapper 的 select(...) 中,MyBatis-Flex 在 多表查询 的情况下,且有相同的字段名时,MyBatis-Flex 内部会主动帮助用户添加上 as 别名,默认为:表名$字段名

注:如果复杂的嵌套查询,有字段名是相同的,必须指定具体的查询字段,不可以使用*查询 

5. 批量操作

在 MyBatis-Flex 中,提供了许多批量操作(批量插入、批量更新等)的方法,当有多个方法的时候,会经常导致误用的情况

BaseMapper.insertBatch 方法

通过 BaseMapper.insertBatch 执行时,会通过 accounts 去组装一个如下的 SQL:

insert into tb_account(id,nickname, .....) values
(100,"miachel100", ....),
(101,"miachel101", ....),
(102,"miachel102", ....),
(103,"miachel103", ....),
(104,"miachel104", ....),
(105,"miachel105", ....);

这种有一个特点:在小批量数据执行插入的时候,效率是非常高;但是当数据列表过多时,其生成的 SQL 可能会非常大, 这个大的 SQL 在传输和执行的时候就会变得很慢了。

因此,BaseMapper.insertBatch 方法只适用于在小批量数据插入的场景,比如 100 条数据以内

Db.executeBatch 方法

Db.executeBatch 可以用于进行批量的插入、修改和删除,以下是使用 Db.executeBatch 进行批量插入的示例:

List<Account> accounts = ....
Db.executeBatch(accounts.size(), 1000, AccountMapper.class, (mapper, index) -> {
        Account account = accounts.get(index);
        mapper.insert(account);
    });

 大概意思就是,如果使用basemapper中的insertbatch,那么就要用上边的方法,如果使用Iservice中的方法,就不需要自己包装方法了,直接使用即可,因为Iservice中的批量新增,Flex已经使用它,帮我们封装过了

6.链式操作(flex最值得推荐的写法)

1.查询列表

 List<Article> articles = articleService.queryChain()
            .select(ARTICLE.ALL_COLUMNS)
            .from(ARTICLE)
            .where(ARTICLE.ID.ge(100))
            .list();

//或者如果不在service,可以通过mapper获取
List<Article> articles = QueryChain.of(mapper)
    .select(ARTICLE.ALL_COLUMNS)
    .from(ARTICLE)
    .where(ARTICLE.ID.ge(100))
    .list();

2.查询单个

 List<Article> articles = articleService.queryChain()
            .select(ARTICLE.ALL_COLUMNS)
            .from(ARTICLE)
            .where(ARTICLE.ID.ge(100))
            .one();

3.修改

UpdateChain.of(Account.class)
        .set(Account::getUserName, "张三")
        .setRaw(Account::getAge, "age + 1")
        .where(Account::getId).eq(1)
        .update();

//或者
 //更新数据
    UpdateChain.of(Account.class)
        .set(Account::getAge, ACCOUNT.AGE.add(1))
        .where(Account::getId).ge(100)
        .and(Account::getAge).eq(18)
        .update();

猜你喜欢

转载自blog.csdn.net/weixin_59244784/article/details/132004661