说明

当前的版本为

  • 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 imc_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 imc_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 唯一主键, 这个注解在当前类中只允许出现一次
  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> { @Select("select * from test_users ${ew.customSqlSegment}")
List<UserPO> selectAll(@Param(Constants.WRAPPER) Wrapper<UserPO> wrapper);
}

注意这里传入的参数为 Wrapper, 这是 MyBatis Plus 提供的一个传参便捷工具类, 下面会单独说明

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, 参考前面的配置部分

通过页数和每页记录数创建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, PO> 类, 直接使用其中的 saveBatch() 方法

public class FieldServiceImpl
extends ServiceImpl<FieldMapper, FieldPO>
implements FieldService { @Override
public Result saveSomething(List<FieldPO> pos) {
// 在方法中使用 saveBatch
saveBatch(pos); } }

MyBatis 及 MyBatis Plus 纯注解方式配置(Spring Boot + Postgresql)的更多相关文章

  1. 纯注解方式配置spring+springMVC

    1.新建类initConfig,继承AbstractAnnotationConfigDispatcherServletInitializer,并重写getRootConfigClasses().get ...

  2. SSH深度历险(十) AOP原理及相关概念学习+AspectJ注解方式配置spring AOP

    AOP(Aspect Oriented Programming),是面向切面编程的技术.AOP基于IoC基础,是对OOP的有益补充. AOP之所以能得到广泛应用,主要是因为它将应用系统拆分分了2个部分 ...

  3. 0053 用注解方式配置Spring MVC

    按照0052中的办法,如果一个站点设计有1000个请求,那就得写1000个controller,还得配置1000个<bean id="helloController" cla ...

  4. Spring 使用纯注解方式完成IoC

    目录 创建一个简单的Person类 使用xml方式配置Spring容器并获取bean的过程 创建xml配置文件 进行测试 使用纯注解方式配置Spring容器并获取bean的过程 创建spring配置类 ...

  5. spring 纯注解方式 与AOP

    spring注解方式 以前我也使用过纯注解方式.现在在这里做个记录 我们先认识几个我们都耳熟能详的注解 @configuration :从spring3.0这个注解就可以用于定义配置类,可以替换xml ...

  6. Spring boot 基于注解方式配置datasource

    Spring boot 基于注解方式配置datasource 编辑 ​ Xml配置 我们先来回顾下,使用xml配置数据源. 步骤: 先加载数据库相关配置文件; 配置数据源; 配置sqlSessionF ...

  7. atitit.ajax bp dwr 3.的注解方式配置使用流程总结 VO9o.....

    atitit.ajax bp dwr 3.的注解方式配置使用流程总结 VO9o..... 1. 安装配置 1 1.1. 下载  dwr.jar 1M 1 1.2. 配置注解方式..web.xml 1 ...

  8. atitit.ajax bp dwr 3.的注解方式配置使用流程总结.....

    atitit.ajax bp dwr 3.的注解方式配置使用流程总结..... 1. 下载  dwr.jar 1M 1 2. 配置注解方式..web.xml 1 3. Class 配置 2 4. 测试 ...

  9. Spring Aop实例@Aspect、@Before、@AfterReturning@Around 注解方式配置

    用过spring框架进行开发的人,多多少少会使用过它的AOP功能,都知道有@Before.@Around和@After等advice.最近,为了实现项目中的输出日志和权限控制这两个需求,我也使用到了A ...

随机推荐

  1. ospf应用简单

    ospf应用简单 OSPF (SPF) 属于链路状态路由选择协议,并且是公有标准, 理论上是没有网络规模限制的: 支持网络的层次化设计,可以将网络分为2层.   层,是通过"区域" ...

  2. 6张图为你分析Kafka Producer 消息缓存模型

    摘要:发送消息的时候, 当Broker挂掉了,消息体还能写入到消息缓存中吗? 本文分享自华为云社区<图解Kafka Producer 消息缓存模型>,作者:石臻臻的杂货铺. 在阅读本文之前 ...

  3. Spring AOP Xml配置过程及解释

    目录 Spring AOP(基于xml) 专业术语: 基于xml的声明式AspectJ 具体实践 Spring AOP(基于xml) 目前主流的AOP框架有两个,分别是Spring AOP和Aspec ...

  4. 依图语音API的C#封装以及调用进行语音转写的处理

    对于语音识别,一般有实时语音识别和语音文件的识别处理等方式,如在会议.培训等场景中,可以对录制的文件进行文字的转录,对于转录文字的成功率来说,如果能够转换90%以上的正确语音内容,肯定能减轻很多相关语 ...

  5. 关于List、Set、Map接口讲解

    概述 List.Set接口都是继承于Collection主接口,而Map为独立接口 1.List接口下有ArrayList.Vector.LinkedList实现类 2.Set接口下有HashSet. ...

  6. 【仿真】Carla之收集数据快速教程 (附完整代码) [7]

    收集过程可视化展示,随后进入正文: 参考与前言 看到仿真群对这类任务下(用carla收集数据然后再做训练等) 需求量大,顺手马上写一个好了,首先收集数据需要考虑清楚: 收集什么数据,需要什么样的数据格 ...

  7. 羽夏看Win系统内核—— x64 番外篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  8. VT 入门番外篇——初识 VT

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  9. [WPF] 假装可变字体

    1. 可变字体 上图中的两个动画,一个文字直接变粗,一个渐渐变粗,我觉得后者会更有趣.但普通的字体可达不到这种效果,例如微软雅黑,无论怎么调整它的 FontWeight,实际上它也只有三种粗细: 这时 ...

  10. java面向对象思想之继承

    一.什么是继承 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为.可以联系生活进行理解,相当于父亲和儿子的关系.父亲有的属 ...