8.SpringBoot整合MyBatis_Plus

1. 整合MyBatis-Plus

1)、什么是MyBatis-Plus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

mybatis plus 官网

建议安装 MybatisX 插件

特点:

润物无声

只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。

效率至上

只需简单配置,即可快速进行 CRUD 操作,从而节省大量时间。

丰富功能

热加载、代码生成、分页、性能分析等功能一应俱全。

2)、整合MyBatis-Plus
第一步:引入依赖
<!-- mybatis-plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>

注意:引入 MyBatis-Plus 之后请不要再次引入 MyBatis,以避免因版本差异导致的问题。mysql依赖需要自己引入。

第二步:了解自动配置情况
  • MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对mybatis-plus的定制
  • SqlSessionFactory 自动配置好。底层是容器中默认的数据源
  • mapperLocations 自动配置好的。有默认值。classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下。
  • 容器中也自动配置好了SqlSessionTemplate会话对象
  • @Mapper 标注的接口也会被自动扫描;或者在主配置类上直接 添加@MapperScan(“com.igeek.springboot.mapper”) 批量扫描。
第三步:配置文件

在 application.properties或者yaml 配置文件中添加 MySQL 数据库的相关配置:

application.properties

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?unicode=true&characterEncode=utf8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root

application.yml

spring:
  # 配置数据源的基础信息
  datasource:
    #JDBC属性配置
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?unicode=true&characterEncode=utf8&serverTimezone=GMT%2B8
    username: root
    password: root

注意:

1、这里的 url 使用了 ?serverTimezone=GMT%2B8 后缀,因为8.0版本的jdbc驱动需要添加这个后缀,否则运行测试用例报告如下错误:

java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more

2、这里的 driver-class-name 使用了 com.mysql.cj.jdbc.Driver ,在 jdbc 8 中 建议使用这个驱动,否则运行测试用例的时候会有 WARN 信息

第四步:启动类

在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹

@SpringBootApplication
@MapperScan("com.igeek.springboot.mapper")
public class DemomptestApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemomptestApplication.class, args);
    }
}
第五步:实体类

创建包 entity 编写实体类 User.java(此处使用了 Lombok 简化代码)

表数据在官网上

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}
第六步:mapper映射

创建包 mapper 编写Mapper 接口

UserMapper.java

@Repository
public interface UserMapper extends BaseMapper<User> {
    
}
第七步:service业务

创建包 service编写IService 接口UserService.java

public interface UserService extends IService<User> {

}

创建包 service编写UserServiceImpl实现类

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {

}
第八步:查看sql输出日志
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
第九步:测试
@SpringBootTest
class SpringbootCh05MybatisplusApplicationTests {

    @Autowired
    private UserService userService;

    //查询列表
    @Test
    public void testSelectAll(){
        //SELECT id,name,age,email FROM user
        List<User> list = userService.list(null);
        System.out.println(list);
    }
}
3)、CRUD - 查询
1.查询
1.1 通过多个id批量查询

完成了动态sql的foreach的功能

//通过多个id批量查询
@Test
public void testSelectByIds(){
    //SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
    List<User> list = userService.listByIds(Arrays.asList(1,2,3));
    System.out.println(list);
}
1.2 简单的条件查询

通过map封装查询条件

注意:map中的key对应数据库中的列名。如:数据库user_id,实体类是userId,这时map的key需要填写user_id

//简单的条件查询
@Test
public void testSelectByMap(){
    //Map集合的key一定要与表中字段一致
    Map<String,Object> map = new HashMap();
    map.put("name","Jone");
    map.put("age",18);
    //SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
    List<User> list = userService.listByMap(map);
    System.out.println(list);
}
2.分页
2.1 分页插件

MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能

2.1.1 添加分页插件

配置类中添加@Bean配置

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig {
	
    // 将分页插件加入至IOC容器中
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //添加内部拦截器
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInnerInterceptor.setOverflow(false);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        paginationInnerInterceptor.setMaxLimit(-1L);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }

}

2.1.2 测试selectPage分页

测试:最终通过page对象获取相关数据

//分页查询
@Test 
public void testSelectPage() {   
    Page<User> page = new  Page(1,3);   
    //返回对象得到分页所有数据
    Page<User> userPage =  userMapper.selectPage(page, null);  
    //总页数
    long pages = userPage.getPages(); 
    //当前页  
    long current = userPage.getCurrent(); 
    //查询数据集合
    List<User> records = userPage.getRecords(); 
    //总记录数
    long total = userPage.getTotal(); 
    //下一页
    boolean hasNext = userPage.hasNext();  
    //上一页
    boolean hasPrevious = userPage.hasPrevious(); 
    
    System.out.println(pages);   
    System.out.println(current);   
    System.out.println(records);   
    System.out.println(total);   
    System.out.println(hasNext);   
    System.out.println(hasPrevious); 
}

2.1.3 测试selectMapsPage分页

当指定了特定的查询列时,希望分页结果列表只返回被查询的列,而不是很多null值

测试selectMapsPage分页:结果集是Map

@Test
public void testSelectMapsPage() {
    //Page不需要泛型
    Page<Map<String, Object>> page = new Page<>(1, 5);
    Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, null);
    List<Map<String, Object>> records = pageParam.getRecords();
    records.forEach(System.out::println);
    System.out.println(pageParam.getCurrent());
    System.out.println(pageParam.getPages());
    System.out.println(pageParam.getSize());
    System.out.println(pageParam.getTotal());
    System.out.println(pageParam.hasNext());
    System.out.println(pageParam.hasPrevious());
}
2.2 Controller控制层
//分页查询
@GetMapping("/dynamic_table")
public String findAllByPage(
    @RequestParam(value="now",defaultValue="1") Integer pageNow,
    Model model
){
    //封装Page对象 第一个参数:当前页码  第二个参数:每页展示多少条记录
    IPage page = new Page(pageNow,2);
    //Service提供page来进行分页
    IPage userPage = userService.page(page);
    /**
     * 将数据放至请求域
     * records  记录列表
     * total  总记录数
     * size 每页显示条数
     * current 当前页
     * pages  总页数
     */
    model.addAttribute("page",userPage);
    return "table/dynamic_table";
}
2.3 页面
<tbody>
    <tr class="gradeX" th:each="user : ${page.records}">
        <td>[[${user.id}]]</td>
        <td th:text="${user.name}">Internet</td>
        <td th:text="${user.age}">Win 95+</td>
        <td th:text="${user.email}">4</td>
        <td>
            <input type="button" class="btn btn-danger" value="删除" /> |
            <input type="button" class="btn btn-warning" value="修改" />
        </td>
    </tr>
</tbody>
<div class="span6">
    <div class="dataTables_info" id="dynamic-table_info">
        总[[${page.pages}]]页数,总共[[${page.total}]]记录数,
        当前第[[${page.current}]]页,每页显示[[${page.size}]]记录
    </div>
</div>
<div class="dataTables_paginate paging_bootstrap pagination">
    <ul>
        <li class="prev disabled" th:class="${page.current eq 1}?'prev disabled':'prev'">
            <a th:href="@{/dynamic_table(now=${page.current}-1)}">← Previous</a>
        </li>
        <li class="active" th:class="${now eq page.current}?'active':''"
            th:each="now : ${#numbers.sequence(1,page.pages)}">
            <a th:href="@{/dynamic_table(now=${now})}">[[${now}]]</a>
        </li>
        <li class="next" th:class="${page.current eq page.pages}?'next disabled':'next'">
            <a th:href="@{/dynamic_table(now=${page.current}+1)}">Next → </a>
        </li>
    </ul>
</div>
4)、CRUD功能 - 添加
1.MP的主键策略
ASSIGN_ID 策略

MyBatis-Plus默认的主键策略是:ASSIGN_ID (使用了雪花算法)

@TableId(type = IdType.ASSIGN_ID)
private String id;

雪花算法:分布式ID生成器

雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。

核心思想:

长度共64bit(一个long型)。

首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。

41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。

10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。

12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QU0FWhQg-1642326781167)(image/image-20210406131246721.png)]

优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。

AUTO 自增策略

需要在创建数据表的时候设置主键自增

实体字段中配置 @TableId(type = IdType.AUTO)

@TableId(type = IdType.AUTO)
private Long id;

要想影响所有实体的配置,可以设置全局主键配置

#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto
2.插入操作
//添加
@Test
public void testAdd() {
    User user = new User();
    user.setName("lucy");
    user.setAge(20);
    user.setEmail("1243@qq.com");
    int insert = userMapper.insert(user);
    System.out.println(insert);
}
5)、CRUD功能 - 删除
1.物理删除
1.1 根据id删除记录
@Test
public void testDeleteById(){
    int result = userMapper.deleteById(5L);
    system.out.println(result);
}
1.2 批量删除
@Test
public void testDeleteBatchIds() {
    int result = userMapper.deleteBatchIds(Arrays.asList(8, 9, 10));
	system.out.println(result);
}
1.3 简单条件删除
@Test
public void testDeleteByMap() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", "Helen");
    map.put("age", 18);
    int result = userMapper.deleteByMap(map);
    system.out.println(result);
}

Controller层

@Autowired
private UserService userService;

@GetMapping("/user/delete/{id}")
public String deleteUser(@PathVariable("id") Long id,
                         @RequestParam(value = "pn",defaultValue = "1")Integer pn,
                         RedirectAttributes ra){

    userService.removeById(id);

    ra.addAttribute("pn",pn);
    return "redirect:/dynamic_table";
}
2.逻辑删除
2.1 物理删除和逻辑删除

**物理删除:**真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据

**逻辑删除:**假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录

逻辑删除的使用场景:

可以进行数据恢复

有关联数据,不便删除

分析客户的行为

2.2 逻辑删除实现流程

2.2.1 数据库修改

添加 deleted字段

ALTER TABLE `user` ADD COLUMN `deleted` int DEFAULT 0;

2.2.2 实体类修改

添加deleted 字段,并加上 @TableLogic 注解

@TableLogic
private Integer deleted;

2.2.3 配置(可选)

application.properties 加入以下配置,此为默认值,如果你的默认值和mp默认的一样,该配置可无

mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

2.2.4 测试

测试后发现,数据并没有被删除,deleted字段的值由0变成了1

测试后分析打印的sql语句,是一条update

注意:被删除前,数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作

@Test
public void testLogicDelete() {  
    int result = userMapper.deleteById(1L);
    system.out.println(result);
}

2.2.5 测试逻辑删除后的查询

MyBatis Plus中查询操作也会自动添加逻辑删除字段的判断

@Test
public void testLogicDeleteSelect() {
    List<User> users = userMapper.selectList(null);
    users.forEach(System.out::println);
} 
6)、CRUD功能 - 修改(自动填充和乐观锁)
1.更新操作

注意:update时生成的sql自动是动态sql

UPDATE user SET age=? WHERE id=? 
//修改
@Test 
public void testUpdate() {   
    User user = new User();   
    user.setId(1340868235401764865L);   
    user.setName("lucymary");   
    int count = userMapper.updateById(user);   
    System.out.println(count); 
}
2.自动填充

需求描述:

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。

我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作

1.1 数据库修改

在User表中添加datetime类型的新的字段 create_time、update_time

1.2 实体类修改

实体上增加字段并添加自动填充注解

//create_time
@TableField(fill = FieldFill.INSERT) 
private Date createTime;  

//update_time
@TableField(fill = FieldFill.INSERT_UPDATE) 
private Date updateTime; 
1.3 实现元对象处理器接口

注意:不要忘记添加 @Component 注解

@Component 
public class MyMetaObjectHandler implements MetaObjectHandler {    
    //mp执行添加操作,这个方法执行   
    @Override   
    public void insertFill(MetaObject metaObject) {     
        this.setFieldValByName("createTime",new Date(),metaObject);     
        this.setFieldValByName("updateTime",new Date(),metaObject);   
                                                  
    }    
    
    //mp执行修改操作,这个方法执行   
    @Override   
    public void updateFill(MetaObject metaObject) {     
        this.setFieldValByName("updateTime",new Date(),metaObject);   
    } 
}
3、乐观锁
场景

主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新

乐观锁实现方式:

1.取出记录时,获取当前version

2.更新时,带上这个version

3.执行更新时, set version = newVersion where version = oldVersion

4.如果version不对,就更新失败

4、乐观锁实现流程
4.1 修改实体类

添加 @Version 注解

@Version
private Integer version;
4.2 创建配置文件

创建包config,创建文件MybatisPlusConfig.java

此时可以删除主类中的 @MapperScan 扫描注解

@Configuration
public class MyConfig {

    // 将插件加入至IOC容器中
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        //添加乐观锁插件
        OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
        return interceptor;
    }

}
7)、条件构造器和常用接口
1.wrapper介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S6q2SWNS-1642326781173)(image/wps1.jpg)]

Wrapper : 条件构造抽象类,最顶端父类

​ AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件

​ QueryWrapper : 查询条件封装

​ UpdateWrapper : Update 条件封装

AbstractLambdaWrapper : 使用Lambda 语法

​ LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper

​ LambdaUpdateWrapper : Lambda 更新封装Wrapper

@SpringBootTest
public class QueryWrapperTests { 
    @Autowired
    private UserMapper userMapper;
}
2.测试用例
2.1 ge、gt、le、lt、isNull、isNotNull
@Test
public void testQuery() {
    QueryWrapper<User>queryWrapper = newQueryWrapper<>();
    queryWrapper.isNull("name").ge("age", 12).isNotNull("email");  
    int result = userMapper.delete(queryWrapper);
    System.out.println("delete return count = " + result);
}
2.2

注意:seletOne()返回的是一条实体记录,当出现多条时会报错

@Test
public void testSelectOne() {
    QueryWrapper<User>queryWrapper = newQueryWrapper<>();
    queryWrapper.eq("name", "Tom");
    Useruser = userMapper.selectOne(queryWrapper);
    //只能返回一条记录,多余一条则抛出异常
    System.out.println(usr);
} 
2.3 between、notBetween

包含大小边界

@Test
public void testSelectCount() {
    QueryWrapper<User>queryWrapper = newQueryWrapper<>();
    queryWrapper.between("age", 20, 30);  
    Integer count = userMapper.selectCount(queryWrapper); 
    //返回数据数量
    System.out.println(count);
}
2.4 like、notLike、likeLeft、likeRight

selectMaps()返回Map集合列表,通常配合select()使用

@Test
public void testSelectMaps() {
    QueryWrapper<User>queryWrapper = newQueryWrapper<>();
    queryWrapper.select("name", "age").like("name", "e").likeRight("email", "5");
    List<Map<String, Object>>maps = userMapper.selectMaps(queryWrapper);
    //返回值是Map列表
    maps.forEach(System.out::println);
}
2.5 orderBy、orderByDesc、orderByAsc
@Test
public void testSelectListOrderBy() {
    QueryWrapper<User>queryWrapper = newQueryWrapper<>();
    ueryWrapper.orderByDesc("age", "id");
    List<User>users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
3.查询方式
查询方式说明
setSqlSelect设置 SELECT 查询字段
whereWHERE 语句,拼接 + WHERE 条件
andAND 语句,拼接 + AND 字段=值
andNewAND 语句,拼接 + AND (字段=值)
orOR 语句,拼接 + OR 字段=值
orNewOR 语句,拼接 + OR (字段=值)
eq等于=
allEq基于 map 内容等于=
ne不等于<>
gt大于>
ge大于等于>=
lt小于<
le小于等于<=
like模糊查询 LIKE
notLike模糊查询 NOT LIKE
inIN 查询
notInNOT IN 查询
isNullNULL 值查询
isNotNullIS NOT NULL
groupBy分组 GROUP BY
havingHAVING 关键词
orderBy排序 ORDER BY
orderAscASC 排序 ORDER BY
orderDescDESC 排序 ORDER BY
existsEXISTS 条件语句
notExistsNOT EXISTS 条件语句
betweenBETWEEN 条件语句
notBetweenNOT BETWEEN 条件语句
addFilter自由拼接 SQL
last拼接在最后,例如:last(“LIMIT 1”)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

what's your name.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值