1.简介
- MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
- mybatis和mybatis-plus的依赖只能二选一
- mybatis-plus提供了通用mapper和service,不需要我们像mybatis一样手动编写接口和sql映射文件。
- mybatis-plus自动生成主键id使用的是雪花算法,且自动包含主键回填的功能,可以直接获取到主键id,位数比较长,所以我们在设计表的时候需要将id设置成32位。
- mybatis-plus可以自动将表中下划线的字段转化成实体类中的驼峰形式。
2.通用BaseMapper接口用法
- mybatis-plus会根据BaseMaper的泛型实体类,自动帮我们写sql。
- Ioc容器只能存在类所对应的bean,不能存在接口对应的bean,所以我们需要在UserMapper上添加@Repository注解。
- mybatis-plus查询时,将根据我们数据库中的表对应BaseMaper的泛型实体类,进行一一对应,然后赋值。
- BaseMapper中自带增删改查的方法,如果查询条件很复杂,可以添加条件构造器queryWrapper.
- mapper接口层需要继承BaseMapper<User>,并标名实体类。
@Repository // dao层 public interface UserMapper extends BaseMapper<User> { }
3.通用service接口用法
- 通用service对BaseMapper进一步封装,方法更多,如批量添加。
- 其中saveOrUpdate类型方法是添加或者更新,当传入实体类有id时,就执行更新操作,没有id,就执行添操作。
- ServiceImpl实现了IService,提供了IService中基础功能的实现
- 若ServiceImpl无法满足业务需求,则可以使用自定的UserService定义方法,并在实现类中实现。
- service接口层需要实现IService<User>,并标名实体类。
public interface UserService extends IService<User> { }
- service实现类需要继承ServiceImpl<UserMapper, User>,并实现我们刚刚的接口。
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }
4.常用注解
-
@TableName(“t_user”)
- 将实体类与数据库表名一一对应。默认BaseMapper将实体类名的小写映射成数据库中的表名,所以我们没有告诉它操作哪个表,它也能找到对应的表,以进行增删改查操作。如果数据库表名与实体类名不一致,可以使用这个注解进行映射。
- 如果想要给每个实体类添加前缀,可以配置全局的前缀,这样就不用每个实体类都去使用@TableName进行映射了。
-
@TableId(value = “uid”,type = IdType.AUTO)
1, @TableId注解的value属性用于指定主键的字段,实体类字段与表中字段不同时可以用。
2. @TableId注解的type属性用于指定主键生成策略,不想用雪花算法生成id时,可以换成自增策略,需要数据库开启id自增,且type = IdType.AUTO。@Data @NoArgsConstructor @AllArgsConstructor //@TableName("t_user")// 将实体类映射到数据库表名 public class User { // 映射表中id字段,主键自增策略已在配置文件中设置,此处省略type设置 @TableId(value = "id") private Long uid; private String name; private Integer age; private String email; }
-
@TableField
解决实体类属性和表中字段不一致的问题,将实体类属性和表中字段进行对应。
-
@TableLogic
逻辑删除,直接用在实体类属性上即可。
// 逻辑删除 @TableLogic private Integer isDeleted;
5.条件构造器(通过添加条件,进一步删改查)
-
QueryWrapper
QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.like("name", "t").between("age", 23, 24) .isNotNull("email") .orderBy(true, false, "age"); List<User> list = userService.list(queryWrapper);
-
条件优先级
queryWrapper.like("name", "t").and(i -> i.gt("age", 20).or().isNull("email"));
-
组装select
QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.select("name","age","email"); // 返回的是map集合,很多个map,一个map包含一个人的数据 //{name=tyu0, age=20, email=test1@baomidou.com} List<Map<String, Object>> maps = userService.listMaps(queryWrapper); for (int i = 0; i < maps.size(); i++) { System.out.println(maps.get(i)); }
-
组装子查询
使用子查询的话,如果实体类字段名与数据库表中字段不一致,会出现查到数据为null的情况。QueryWrapper<User> queryWrapper = new QueryWrapper<>(); // 子查询条件 queryWrapper.inSql("id","select id from t_user where age >= 22"); queryWrapper.like("name","t"); // 再调用查全部,指定查询条件即可实现子查询 List<User> list = userService.list(queryWrapper); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }
-
组装查询条件
/** * 组装查询条件(复杂方式) * 根据用户传入的参数进行查询,如果该参数存在,就添加该查询条件,反之不添加 */ @Test public void querryWapperselectTest() { String name = "23"; int age = 12; QueryWrapper<User> queryWrapper = new QueryWrapper<>(); if (StringUtils.isNotBlank(name)){ queryWrapper.like("name","t"); } if (age>=23){ queryWrapper.ge("age",23); } if (age<=23){ queryWrapper.le("age",23); } List<User> list = userService.list(queryWrapper); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } } /** * 组装查询条件(简单方式) * 根据用户传入的参数进行查询,如果该参数存在,就添加该查询条件,反之不添加 */ @Test public void querryWapperselect2Test() { String name = "23"; int age = 12; QueryWrapper<User> queryWrapper = new QueryWrapper<>(); // 根据判断条件,确定是否需要添加该查询条件 queryWrapper.like(StringUtils.isNotBlank(name),"name","t"); queryWrapper.ge(age>=23,"age",23); queryWrapper.le(age<=23,"age",23); List<User> list = userService.list(queryWrapper); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } }
更新的时候,可以直接在条件构造器中设置修改的值,不用创建对象。
/** * 使用UpdateWrapper实现更新功能功能 * 更新年龄大于等于23,或者邮箱为空,名字全部改为ikun */@Test public void updateWapperTest() { UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); updateWrapper.ge("age",23).or().isNull("email").set("name","ikun"); boolean res = userService.update(null, updateWrapper); System.out.println(res); }
3. #### LambdaQueryWrapper
- lambda表达式可以防止字段名写错,通过函数式接口访问实体类中属性对应的字段名。
/** * 使用lambdaQueryWrapper进行组装条件的查询。 */ @Test public void lambdaquerryWapperTest() { String name = "23"; int age = 12; LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>(); // 使用lambda表达式的函数式接口封装查询条件 lambdaQueryWrapper.like(StringUtils.isNotBlank(name),User::getName,"t"); lambdaQueryWrapper.ge(age>=23,User::getAge,23); lambdaQueryWrapper.le(age<=23,User::getAge,23); List<User> list = userService.list(lambdaQueryWrapper); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } }
与LambdaQueryWrapper类似,此处略。
6.插件
mybatisplus使用分页插件需要创建配置类,然后配置分页插件。
- 配置类
@Configuration @MapperScan("pers/jl/mapper")// 推荐将扫描mapper包的注解卸载mybatisplus的配置类上 public class MybatisPlusConfig { // 配置一个分页插件 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ // 创建一个mybatisplus拦截器对象 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加分页插件,并设置分页数据库类型为Mysql interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
- 测试案例
/** * 分页测试 */ @Test public void pageTest(){ Page<User> page = userService.page(new Page<>(2, 3)); // 分页各参数说明 System.out.println("查询到的数据:"+page.getRecords());// 查询到的数据 System.out.println("总记录数:"+page.getTotal());// 总记录数 System.out.println("总页数:"+page.getPages());// 总页数 System.out.println("当前页码:"+page.getCurrent());// 当前页码 System.out.println("当前页数据条数:"+page.getSize());// 当前页数据条数 System.out.println("是否有下一页:"+page.hasNext());// 是否有下一页 System.out.println("是否有上一页:"+page.hasPrevious());// 是否有上一页 }
- 自定义分页功能
将我们查询到的结果进行分页(自己写的sql语句)。- 定义接口
/** * 通过年龄查询用户信息并分页 * @param page Mybatis-plus所提供的分页对象,必须位于第一个参数的位置 * @param age * @return */ // 自定义分页功能 // @Param是用户通过接口,来向sql传递参数的注解方式,以便使用sql来查询数据库。 // @Param("page") Page<User> page用于将查询结果,实现分页功能 Page<User> pageByMySelf(@Param("page") Page<User> page, @Param("age") Integer age);
- 编写SQL映射文件
<!--这里需要在application.yml中配置别名User--> <!--这里#{age}就是用户通过接口传递过来的参数--> <!--这里sql查询数据并没有分页,用户传过来的page对象就是用来实现分页功能的--> <select id="pageByMySelf" resultType="User"> select id,name,age,email from t_user where age > #{age} </select>
- 设置实体类别名
# 配置包内实体类别名 type-aliases-package: pers.jl.pojo
- 测试案例
/** * 自定义分页功能 */ @Test public void pageTest2(){ // 测试自定义分页功能 Page<User> page = userMapper.pageByMySelf(new Page<>(2, 3), 20); // 查询到的数据 System.out.println("查询到的数据:"+page.getRecords());// 5,6,8 }
乐观锁可以通过在数据库中添加一个version字段实现的。
乐观锁是每次修改都要检查版本号,版本号一致才会修改成功。
悲观锁是只有上一个用户执行完,下一个用户才能使用。
- 没有加乐观锁
/** * 测试商品加锁和不加锁修改的不同 */ @Test public void lockTest(){ // 小李取数据 Product productLi = productMapper.selectById(1); System.out.println("小李查询到的金额:"+productLi.getPrice()); // 小王取数据 Product productWang = productMapper.selectById(1); System.out.println("小王查询到的金额:"+productWang.getPrice()); //小李修改数据+50 productLi.setPrice(productLi.getPrice()+50); productMapper.updateById(productLi);// 150 //小王修改数据-30 productWang.setPrice(productWang.getPrice()-30); productMapper.updateById(productWang);// 70 // 老板查询数据 Product productLaoBan = productMapper.selectById(1); // 不加锁的话,小李的修改被小王的修改覆盖掉了,无法实现预期的目的。 System.out.println("老板查询到的金额:"+productLaoBan.getPrice());// 70 }
- 使用MybatisPlus实现乐观锁优化之后
- 需要先在实体类version字段上添加@Version,标识乐观锁版本号注解
@Data @AllArgsConstructor @NoArgsConstructor public class Product { private Long id; private String name; private Integer price; @Version // 标识乐观锁版本号注解 private Integer version; }
- 配置类中添加乐观锁插件
// 添加乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
- 优化后的代码
/** * 测试商品加锁和不加锁修改的不同 * 通过乐观锁解决修改冲突的问题 */ @Test public void lockTest(){ // 小李取数据 Product productLi = productMapper.selectById(1); System.out.println("小李查询到的金额:"+productLi.getPrice()); // 小王取数据 Product productWang = productMapper.selectById(1); System.out.println("小王查询到的金额:"+productWang.getPrice()); //小李修改数据+50 productLi.setPrice(productLi.getPrice()+50); productMapper.updateById(productLi);// 150 //小王修改数据-30 productWang.setPrice(productWang.getPrice()-30); int res = productMapper.updateById(productWang);// 70 // 操作失败,重试 if (res == 0){ // 获取最新的版本号 Product productNew = productMapper.selectById(1); // 修改数据 productNew.setPrice(productNew.getPrice()-30); // 重新更新 productMapper.updateById(productNew); } // 老板查询数据 Product productLaoBan = productMapper.selectById(1); // 不加锁的话,小李的修改被小王的修改覆盖掉了,无法实现预期的目的。 System.out.println("老板查询到的金额:"+productLaoBan.getPrice());// 70 }
- 需要先在实体类version字段上添加@Version,标识乐观锁版本号注解
7.通用枚举
表中某些字段的值是固定的
- 创建通用枚举类
/** * 创建一个枚举类,用于给实体类的字段常量赋值 */ @Getter @AllArgsConstructor public enum SexEnum { // 根据枚举字段,设置枚举变量,创建对象时可以直接调用。 MaLE(1,"男"), FEMALE(2,"女"); // 枚举字段 @EnumValue// 该注解标记数据存储的值是sex private final Integer sex; private final String sexName; }
- 实体类添加该字段
private SexEnum sex;
- 案例测试
/** * 插入一条数据,查看枚举是否配置成功 */ @Test public void EnumTest(){ User user = new User(); user.setName("小红2"); user.setAge(11); user.setEmail("2174694433@qq.com"); user.setSex(SexEnum.MaLE); usermapper.insert(user); }
8.代码生成器
可以生成mybatis映射器(mapper接口和xml映射文件)、表实体类、service层接口和实现类、controller层。
直接生成模块,节约开发时间。
- 导入依赖
<!--代码生成器相关依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency>
- 复制模板,修改相应代码
public class MybatisPlusCodeTest { public static void main(String[] args) { FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false", "root", "1234") .globalConfig(builder -> { // 设置作者 builder.author("luge") // 开启 swagger 模式 //.enableSwagger() // 覆盖已生成文件 .fileOverride() // 指定输出目录 .outputDir("C://Users//ASUS//mybatis_plus"); }) .packageConfig(builder -> { // 设置父包名 builder.parent("pers.jl") // 设置模块名 .moduleName("mybatisplus") // 设置mapperXml生成路径 .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "C://Users//ASUS//mybatis_plus")); }) .strategyConfig(builder -> { // 设置需要生成的表名 builder.addInclude("t_user") .addInclude("t_product") // 设置过滤表前缀 .addTablePrefix("t_"); }) // 使用Freemarker引擎模板,默认的是Velocity引擎模板 .templateEngine(new FreemarkerTemplateEngine()) .execute(); } }
9.多数据源
两张表不在一个数据库中,需要分别查询
- 引入依赖
<!--多数据源依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
- 配置文件
# 多数据源测试
spring:
# 配置数据源信息
datasource:
dynamic:
# 设置默认的数据源或者数据源组,默认值即为master
primary: master
# 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 1234
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 1234
mybatis-plus:
configuration:
# 配置日志功能,可以查看sql
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 全局配置主键生成策略为id自增,这样就不用每个实体类的主键字段都去设置
id-type: auto
# 配置包内实体类别名
type-aliases-package: pers.jl.pojo
- 指定数据源注解
@DS(“master”),指定操作的数据源,用于类上或方法上,可以在多数据源时发挥作用。
@Service
@DS("slave_1")
public class ProductServiceImpl extends ServiceImpl<ProductMapper,Product> implements ProductService {
}
- 测试案例
/**
* 多数据源查询测试
*/
@Test
public void dataSourceTest(){
User byId = userService.getById(1);
Product byId1 = productService.getById(1);
System.out.println(byId);
System.out.println(byId1);
}
10.MybatisX插件
- 可以在mapper接口和xml文件快速跳转。
- 代码快速生成,跟mybatisPlus的代码生成类似,没有controller层,但是xml文件更详细。可以生成mybatis映射器(mapper接口和xml映射文件)、表实体类、service层接口和service层实现类。
- 在接口中只需要写接口名字(见名知意的那种),就可以快速生成CRUD接口,并自动帮我们生成sql语句。
- mapper接口
/** * @author ASUS * @description 针对表【t_user】的数据库操作Mapper * @createDate 2023-04-10 16:26:37 * @Entity pers.jl.pojo.User */ public interface UserMapper extends BaseMapper<User> { // 快速生成接口和sql映射文件,多表联查非常有用,可以帮我们生成sql int insertAll(User user); int delByIdAndEmail(@Param("id") Long id, @Param("email") String email); int updateIdAndAge(@Param("id") Long id, @Param("age") Integer age); List<User> selectByIdAndAge(@Param("id") Long id, @Param("age") Integer age); }
- sql映射文件
<select id="selectByIdAndAge" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from t_user where id = #{id,jdbcType=NUMERIC} AND age = #{age,jdbcType=NUMERIC} </select> <insert id="insertAll"> insert into t_user (id, name, age, email, sex, is_deleted) values (#{id,jdbcType=NUMERIC}, #{name,jdbcType=VARCHAR}, #{age,jdbcType=NUMERIC}, #{email,jdbcType=VARCHAR}, #{sex,jdbcType=NUMERIC}, #{isDeleted,jdbcType=NUMERIC}) </insert> <delete id="delByIdAndEmail"> delete from t_user where id = #{id,jdbcType=NUMERIC} AND email = #{email,jdbcType=VARCHAR} </delete> <update id="updateIdAndAge"> update t_user set id = #{id,jdbcType=NUMERIC}, age = #{age,jdbcType=NUMERIC} </update>