百木园-与人分享,
就是让自己快乐。

MyBatis 及 MyBatis Plus 纯注解方式配置(Spring Boot + Postgresql)

说明

当前的版本为

  • MyBatis 3.5.9
  • MyBatis Plus 3.5.1
  • Spring Boot 2.6.4
  • Postgresql 42.3.3

与 Spring Boot 结合使用 MyBatis

以下说明Spring Boot下完全以注解方式进行的配置, 覆盖大部分功能场景

项目依赖

需要以下的依赖, 版本由Spring Boot指定, 或者参考上面的版本号

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

配置

1.增加 MapperScan 注解

这个注解可以和@SpringBootApplication放在一起, 也可以放在单独的一个 @Configuration

@SpringBootApplication
@MapperScan(basePackages = \"com.yourdomain.demo.commons.impl.mapper\")
public class CommonsBoot {
    public static void main(String[] args) {
        SpringApplication.run(CommonsBoot.class, args);
    }
}

2. 准备POJO(PO或者DTO)

public class RoleItemDTO {
    private Integer id;
    private int roleId;
    private int itemId;
    private Date createTime;

    // getters and setters
    // ...
}

3. 准备Mapper

Mapper必须位于 @MapperScan 指定的包路径, 其中 @Repository 用于指定 Bean 名称, 内部用 @Select @Update @Delete @Insert 等编写对应操作的SQL

@Repository(\"roleItemMapper\")
public interface RoleItemMapper {

    @Select(\"SELECT * FROM test_role_item WHERE role_id = #{roleId}\")
    List<RoleItemDTO> listByRoleId(@Param(\"roleId\") int roleId);
}

4. 调用

测试用例

@ExtendWith(SpringExtension.class)
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RoleItemTest {
    private static final Logger log = LoggerFactory.getLogger(RoleItemTest.class);

    @Resource
    private RoleItemMapper roleItemMapper;

    @Test
    @Order(1)
    void insert() {
        RoleItemDTO rc = new RoleItemDTO();
        rc.setItemId(1);
        rc.setRoleId(2);
        rc.setCreateTime(new Date());
        Assertions.assertThat(roleItemMapper.insert(rc)).isEqualTo(1);
        List<RoleItemDTO> dummies = roleItemMapper.listByRoleId(2);
        log.info(JacksonUtil.compress(dummies));
        Assertions.assertThat(dummies).isNotNull();

    }
}

注解使用

典型用例

public interface VillageMapper {
    @Results({
        @Result(property = \"vid\",         column = \"id\"),
        @Result(property = \"villageName\", column = \"name\"),
        @Result(property = \"district\",    column = \"district\")
    })
    @Select(\"SELECT id, name, district from village WHERE id = #{id}\")
    Village selectVillage(int id);

    @Insert(\"INSERT into village(name,district) VALUES(#{villageName}, #{district})\")
    void insertVillage(Village village);

    @Update(\"UPDATE village SET name=#{villageName}, district =#{district} WHERE id =#{vid}\")
    void updateVillage(Village village);

    @Delete(\"DELETE FROM village WHERE id =#{id}\")
    void deleteVillage(int id);
}

传参注解@Param${},#{}

在Mapper接口中, 使用@Param(\"paramName\")可以对传入的参数进行命名, 在SQL中通过${paramName}#{paramName}取值, 避免歧义.

  1. ${}会在SQL字符串中直接替换, 非必要不建议使用这种方式
  2. #{}是根据参数类型进行填充, 对于字符串会增加引号包围, 是通常使用的传参方式
  3. #{}支持对象传入, 在引用时通过 #{obj.variable}的方式取值, 参考下面 Insert 的例子

Select和结果集映射

对于普通类型例如primitive类型, 数值(Integer, Long, Double, BigDecimal等), 字符串String, 日期Date, MyBatis都已经做了自动转换不需要手工设定. 对于特殊类型, 例如对应Postgresql的 Array, Jsonb, 需要使用注解 @Results 进行指定, 在注解中, 对于已经有默认handler的字段可以省略, 只需要添加特殊指定hanler的字段.

Array类型字段的例子, 对templates字段的转换进行指定

// POJO定义
public class ViewPO implements Serializable {
    private Integer id;
    private String name;
    private Integer[] templates;
}

// Mapper方法
@Results({
    @Result(property = \"templates\", column = \"template_ids\", typeHandler = ArrayTypeHandler.class)
})
@Select(\"select * from test_view where id = #{id}\")
ViewPO select(@Param(\"id\") int id);

JSONB类型字段的例子, 对ext字段的转换进行指定

// POJO定义
public class AccountDTO {
    private Long id;
    private String name;
    private int val;
    private BigDecimal amount;
    private Ext ext;

    public static class Ext {
        private Integer id;
        private String name;
        private BigDecimal level;
        // getters and setters
    }

    // getters and setters
}

// Mapper方法
@Results({
    @Result(property = \"ext\", column = \"ext\", typeHandler = JacksonTypeHandler.class)
})
@Select(\"select * from test_account where amount > #{amountGt}\")
List<AccountDTO> list(@Param(\"amountGt\") BigDecimal amountGt);

Insert和自增主键

@Insert(\"INSERT INTO role_item (role_id, item_id, create_time) VALUES (#{po.roleId}, #{po.itemId}, #{po.createTime})\")
@Options(useGeneratedKeys=true, keyProperty=\"id\")
int insert(@Param(\"po\") RoleItemPO po);

Delete

@Delete(\"DELETE FROM role_item WHERE item_id = #{itemId}\")
int deleteByItemId(@Param(\"itemId\") int itemId);

Update

@Update(\"update widget set name=#{po.name}, manufacturer=#{po.manufacturer} where id=#{id}\")
public void updateById(@Param(\"id\") int id, @Param(\"po\") Widget po);

与 Spring Boot 结合使用 MyBatis Plus

MyBatis Plus 相对于原生 MyBatis, 增加了对常用CRUD方法的包装, 减小了手工编写SQL的工作量. MyBatis Plus 对多主键的表支持不太好, 如果数据库中的某个数据表使用了联合主键, 建议使用原生的 MyBatis 注解对这个数据表进行操作

项目依赖

将 MyBatis 的依赖替换为MyBatis Plus, 参考上面的版本号, 本文使用的是3.5.1

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

配置

1.增加 MyBatisPlus 的配置类

因为除了设置 MapperScan, 还要设置分页插件, 因此放到了单独的 @Configuration

@Configuration
@MapperScan(basePackages = \"com.yourdomain.demo.common.mapper\")
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
        return interceptor;
    }

}

2. 准备POJO(PO或者DTO)

如果使用 MyBatis Plus, 常用的三个注解为

  1. @TableName 表名, autoResultMap设置为true, 下面 ext 字段的 typeHandler 才会生效
  2. @TableId 唯一主键, 这个注解在当前类中只允许出现一次. 唯一主键修饰的id字段需要使用包装类, 例如 Integer或Long, 在Insert时这个字段赋值NULL, 才能正确获取生成的自增ID值
  3. @TableField 字段映射关系, 当字段名与变量名不能直接对应时使用, 当类型转换需要特殊处理时也需要使用这个注解
@TableName(value = \"test_users\", autoResultMap = true)
public class UserPO implements Serializable {
    @TableId(value = \"id\", type = IdType.AUTO)
    private Long id;
    private String userName;
    private String password;
    @TableField(\"creat_time\")
    private Date createdAt;
    @TableField(\"update_time\")
    private Date updatedAt;
    @TableField(typeHandler = JacksonTypeHandler.class)
    private JsonNode ext;
}

3. 准备 Mapper

对于 MyBatis Plus, 最常见的 Mapper 初始化方式为扩展 BaseMapper, 这个接口已经实现了 selectById, selectList, insert, delete, deleteById, updateById 等常用方法

@Repository(\"userMapper\")
public interface UserMapper extends BaseMapper<UserPO> {

}

注意

  1. 如果 BaseMapper 自带方法不能满足功能需求, 可以通过 @Select, @Update, @Insert, @Delete 等注解添加自定义接口, 实际上就是按原生 MyBatis 的方式处理.
  2. 如果添加了 @Select 查询并且结果也是标准POJO, 结果中的 JSONB 类型字段并不会自动赋值(输出为null). 如果需要转换, 需要按原生 MyBatis 一样在方法上添加 @Results 注解.

4. 调用

调用方式与原生 MyBatis 一致

注解使用

MyBatis Plus 的常用注解主要有两个, 一个是 Wrapper(有多个实现类), 一个是 Page, 分别用于查询条件和分页条件.

QueryWrapper 用于条件,排序和聚合

QueryWrapper 自带了常用的逻辑方法 eq(相等), ne(不相等), lt/gt(小于/大于), lte/gte(小于等于/大于等于), like(两边%), likeLeft(左%), likeRiht(右%), exists(条件子查询), in(数组中包含), 以及 groupBy, orderBy 方法

  1. 三参数格式中, 第一个参数为本条件是否加入查询的条件判断, 便于根据输入进行条件组合
  2. 三参数的第二参数(或二参数的第一参数), 其值为数据库的字段名, 这点与 LambdaQueryWrapper 不同, 需要注意
List<FieldPO> fields = fieldMapper.selectList(new QueryWrapper<FieldPO>().eq(\"t_id\", tId).orderByAsc(\"id\"));

一个复杂一点的例子, 注意最后一个exists例子中的传参方式

QueryWrapper<ItemPO> wrapper = new QueryWrapper<ItemPO>()
    .eq(name != null, \"item_name\", name)
    .like(nameLike != null, \"item_name\", nameLike)
    .like(descLike != null, \"desc\", descLike)
    .eq(type > 0, \"item_type\", type)
    .eq(state > 0, \"item_state\", state)
    .eq(creatorId > 0, \"creator_id\", creatorId)
    .in(idIn != null && idIn.size() > 0, \"id\", idIn)
    .exists(roleId > 0, \"select 1 from role_item where role_item.item_id=item.id and role_item.role_id = {0}\", roleId);

LambdaQueryWrapper POJO字段方式

LambdaQueryWrapper 与 QueryWrapper 的区别在与条件参数为POJO的取值方法名而不是数据库字段, 从设计上看这样可以完全隔离数据库层的信息, 是更好的一种实现, 例子

LambdaQueryWrapper<AccountDTO> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
    userLambdaQueryWrapper.likeRight(AccountDTO::getName , \"acc\").lt(AccountDTO::getVal , 40).last(\"limit 5\");

但是当前 MyBatis Plus 的实现在高版本JDK上会提示警告, 在 JDK9+ 上运行时会提示An illegal reflective access operation has occurred #issue. 建议还是继续用 QueryWrapper, 待这部分的实现修复后再迁移.

Page 分页

列表的排序和聚合都是通过Wrapper指定的, Page 用于列表中的分页. 前提: 在 Configuration 中, 通过 MybatisPlusInterceptor 添加 PaginationInnerInterceptor, 参考前面的配置部分, 需要配置正确的 DbType.

通过页数和每页记录数创建Page对象

Page page = new Page<>(vo.getPageNum(), vo.getPageSize());

结合Wrapper进行查询

@Override
public List<ItemPO> listByArgs(
        Page<ItemPO> page,
        String name,
        int type) {
    QueryWrapper<ItemPO> wrapper = new QueryWrapper<ItemPO>()
            .eq(name != null, \"item_name\", name)
            .eq(type > 0, \"item_type\", type);
    return itemMapper.selectPage(page, wrapper).getRecords();
}

如果需要总数, 可以直接返回selectPage(page, wrapper)的结果.

混合使用 MyBatis 原生方式和 MyBatis Plus

因为 MyBatis Plus 是对 MyBatis 功能的包装, 底层依然是 MyBatis, 所以在使用 MyBatis Plus 的过程中, MyBatis 的原生方法依然可用.

例如, 可以在原生方法中, 使用 MyBatis Plus 的 Wrapper

@Repository(\"userMapper\")
public interface UserMapper extends BaseMapper<UserPO> {

    @Select(\"select * from u_users ${ew.customSqlSegment}\")
    List<UserPO> selectAll(@Param(Constants.WRAPPER) Wrapper<UserPO> wrapper);
}

其它

批量执行

批量执行可以通过两种方式,

MyBatis 原生环境

在 MyBatis 原生环境下, 可以通过获取 sqlSession 后手工发起

@Resource(name = \"sqlSessionFactory\")
private SqlSessionFactory sqlSessionFactory;

public void addItems(List<ItemPO> items) {
    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
    try {
        for (ItemPO item : items) {
            itemMapper.insert(item);
        }
        session.commit();
        session.clearCache();
    } catch (Exception e) {
        log.error(\"Exception in batch update\", e);
        session.rollback();
    } finally {
        session.close();
    }
}

MyBatis Plus 的 saveBatch

在 MyBatis Plus 中, 可以通过扩展 ServiceImpl<Mapper, POJO> 类, 直接使用其中的 saveBatch() 方法

public class FieldServiceImpl
        extends ServiceImpl<FieldMapper, FieldPO>
        implements FieldService {

    @Override
    public Result saveSomething(List<FieldPO> pos) {
        // 在方法中使用 saveBatch
        saveBatch(pos);
    }
}

动态表名

如果需要在查询中指定表名, 最简单的方式是通过${}方式传参, 例子如下

@Select(\"SELECT * FROM ${tableName} WHERE item_type=#{itemType} \" +
        \"AND item_name=#{itemName} AND item_id=#{itemId} AND label=#{label}\")
LabelMapDTO selectFromTable(
        @Param(\"tableName\") String tableName,
        @Param(\"itemType\") int itemType,
        @Param(\"itemName\") String itemName,
        @Param(\"itemId\") String itemId,
        @Param(\"label\") String label);

@Select(\"SELECT * FROM ${tableName} WHERE item_type=#{itemType} \" +
        \"AND item_name=#{itemName} AND item_id=#{itemId}\")
List<LabelMapDTO> selectListFromTable(
        @Param(\"tableName\") String tableName,
        @Param(\"itemType\") int itemType,
        @Param(\"itemName\") String itemName,
        @Param(\"itemId\") String itemId);

来源:https://www.cnblogs.com/milton/p/16100143.html
本站部分图文来源于网络,如有侵权请联系删除。

未经允许不得转载:百木园 » MyBatis 及 MyBatis Plus 纯注解方式配置(Spring Boot + Postgresql)

相关推荐

  • 暂无文章