MybatisPlus学习总结

发布于:2024-03-29 ⋅ 阅读:(159) ⋅ 点赞:(0)

MybatisPlus.xmind

一、MybatisPlus快速入门

1.基本介绍

官网:
简介 | MyBatis-Plus
MyBatis Plus是一个基于MyBatis的增强工具,它简化了MyBatis的使用,提供了一系列的增强功能,使开发更加方便快捷。
MyBatis Plus的主要特点包括:

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

2.快速入门

准备数据库和表

 DROP TABLE IF EXISTS `user`;

CREATE TABLE `user`
(
    id BIGINT NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);

INSERT INTO `user` (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

创建springboot项目

引入依赖
   <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
配置文件
# 应用服务 WEB 访问端口
server.port=8082
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mapper/*xml
#指定Mybatis的实体目录
#mybatis.type-aliases-package=com.pkq.springboot.mybatis.entity
# MySQL数据库驱动
#这个驱动也可以省略,可以根据使用的MySQL自动加载相应的驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/mybatisPlus?serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
# 数据库用户名和密码
spring.datasource.username=root
spring.datasource.password=123456



MybatisPlus默认开启驼峰映射

实体类
package com.pkq.demo.pojo;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * ClassName: User
 * Package: com.pkq.demo.pojo
 * Description:
 *
 * @Author pkq
 * @Create 2024/3/22 15:42
 * @Version 1.0
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")//指定表名
@Builder
public class User {
    private Integer id;
    private  String name;
    private Integer age;
    private String email;
}

如果表的名字和实体类名字一样,就不要进行二者之间的映射

mapper接口
package com.pkq.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.pkq.demo.pojo.User;

/**
 * ClassName: UserMapper
 * Package: com.pkq.demo.mapper
 * Description:
 *
 * @Author pkq
 * @Create 2024/3/22 15:48
 * @Version 1.0
 */
public interface UserMapper extends BaseMapper<User> {
}

扫描mapper

在boot启动类添加@MapperScan进行扫描Mapper文件夹

package com.pkq.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.pkq.demo.mapper")
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

测试
package com.pkq.demo;

import com.pkq.demo.mapper.UserMapper;
import com.pkq.demo.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.List;

/**
 * ClassName: TestUser
 * Package: com.pkq.demo
 * Description:
 *
 * @Author pkq
 * @Create 2024/3/22 15:52
 * @Version 1.0
 */
@SpringBootTest
public class TestUser {
    @Resource
    private UserMapper userMapper;
    @Test
    public void testUser(){
        //通过条件查询来查询一个list,如果没有条件,则写null
        List<User> users = userMapper.selectList(null);
        for (User user:users){
            System.out.println(user);
        }

    }
}

User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

image.png

二、增删改查

1.BaseMapper源码

image.png

package com.baomidou.mybatisplus.core.mapper;
public interface BaseMapper<T> extends Mapper<T> {
/**
* 插入一条记录
* @param entity 实体对象
*/
int insert(T entity);
/**
* 根据 ID 删除
* @param id 主键ID
*/
int deleteById(Serializable id);
/**
* 根据实体(ID)删除
* @param entity 实体对象
* @since 3.4.4
*/
更多Java –大数据 – 前端 – UI/UE - Android - 人工智能资料下载,可访问百度:尚硅谷官网(www.atguigu.com)
int deleteById(T entity);
/**
* 根据 columnMap 条件,删除记录
* @param columnMap 表字段 map 对象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根据 entity 条件,删除记录
* @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where
语句)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 删除(根据ID 批量删除)
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);
/**
* 根据 ID 修改
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);
/**
* 根据 whereEntity 条件,更新记录
* @param entity 实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成
where 语句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER)
Wrapper<T> updateWrapper);
/**
* 根据 ID 查询
* @param id 主键ID
*/
T selectById(Serializable id);
/**
* 查询(根据ID 批量查询)
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);
/**
* 查询(根据 columnMap 条件)
* @param columnMap 表字段 map 对象
*/
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object>
columnMap);
/**
更多Java –大数据 – 前端 – UI/UE - Android - 人工智能资料下载,可访问百度:尚硅谷官网(www.atguigu.com)
* 根据 entity 条件,查询一条记录
* <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常
</p>
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
List<T> ts = this.selectList(queryWrapper);
if (CollectionUtils.isNotEmpty(ts)) {
if (ts.size() != 1) {
throw ExceptionUtils.mpe("One record is expected, but the query
result is multiple records");
}
return ts.get(0);
}
return null;
}
/**
* 根据 Wrapper 条件,查询总记录数
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T>
queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* <p>注意: 只返回第一个字段的值</p>
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录(并翻页)
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER)
Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
*/
<P extends IPage<Map<String, Object>>> P selectMapsPage(P page,
@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

2.insert

image.png

   @Test
    public void testInsertUser(){
        //新增用户信息
        User user=new User();
        user.setName("皮卡丘");
        user.setAge(20);
        user.setEmail("pkq@pkq.com");
        int row = userMapper.insert(user);
        System.out.println("受影响的行数:"+row);
        System.out.println(user);

    }

image.png

最终执行插入操作以后,id的值不是自增的,因为MyBatis-Plus在实现插入数据时,会默认基于雪花算法的策略生成id
:

3.delete

image.png

3.1通过id删除

@Test
public void testDeleteById(){
//通过id删除用户信息
//DELETE FROM user WHERE id=?
    int result = userMapper.deleteById(1475754982694199298L);
    System.out.println("受影响行数:"+result);
}

3.2通过id批量删除

 @Test
    public void testDeleteBatchIds(){
//通过多个id批量删除
//DELETE FROM user WHERE id IN ( ? , ? , ? )
        List<Long> idList = Arrays.asList(1L, 2L, 3L);
        int result = userMapper.deleteBatchIds(idList);
        System.out.println("受影响行数:"+result);
    }

3.3通过map来删除

@Test
public void testDeleteByMap(){
    //根据map集合中所设置的条件删除记录
    //DELETE FROM user WHERE name = ? AND age = ?
    Map<String, Object> map = new HashMap<>();
    map.put("age", 23);
    map.put("name", "张三");
    int result = userMapper.deleteByMap(map);
    System.out.println("受影响行数:"+result);
}

4.update

@Test
public void testUpdateById(){
    User user = new User(4L, "admin", 22, null);
    //UPDATE user SET name=?, age=? WHERE id=?
    int result = userMapper.updateById(user);
    System.out.println("受影响行数:"+result);
}

5.select

5.1根据id查询信息

@Test
public void testSelectById(){
    //根据id查询用户信息
    //SELECT id,name,age,email FROM user WHERE id=?
    User user = userMapper.selectById(4L);
    System.out.println(user);
}

5.2根据多个id查询

@Test
public void testSelectBatchIds(){
    //根据多个id查询多个用户信息
    //SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
    List<Long> idList = Arrays.asList(4L, 5L);
    List<User> list = userMapper.selectBatchIds(idList);
    list.forEach(System.out::println);
}

5.3通过map条件进行查询

@Test
public void testSelectByMap(){
    //通过map条件查询用户信息
    //SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
    Map<String, Object> map = new HashMap<>();
    map.put("age", 22);
    map.put("name", "admin");
    List<User> list = userMapper.selectByMap(map);
    list.forEach(System.out::println);
}

5.4查询所有数据

  @Test
    public void testUser(){
        //通过条件查询来查询一个list,如果没有条件,则写null
      //SELECT id,name,age,email FROM user
        List<User> users = userMapper.selectList(null);
        for (User user:users){
            System.out.println(user);
        }

    }

**通过观察BaseMapper中的方法,大多方法中都有Wrapper类型的形参,此为条件构造器,可针 **
**对于SQL语句设置不同的条件,若没有条件,则可以为该形参赋值null,即查询(删除/修改)所 **
有数据

6.通用service

MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑

说明:
通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删
除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,
泛型 T 为任意实体对象
建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承
Mybatis-Plus 提供的基类
官网地址:https://baomidou.com/pages/49cc81/#service-crud-%E6%8E%A5%E5%8F%
A3
:::
MybatisPlus为了开发更加快捷,对业务层也进行了封装,直接提供了相关的接口和实现类;我们在进行业务层开发时,可以继承它提供的接口和实现类,使得编码更加高效;
实现流程:

  1. 定义一个服务扩展接口,该接口继承公共接口IService;
  2. 定义一个服务实现类,该类继承ServiceImpl<Mapper,Entity>,并实现自定义的扩展接口;

注意事项:
1.ServiceImpl父类已经注入了UserMapper对象,名称叫做baseMapper,所以当前实现类直接可以使用baseMapper完成操作2.因为ServiceImpl已经实现了IService下的方法,所以当前服务类没有必要再次实现
思想:共性的业务代码交给框架封装维护,非共性的业务,在接口UserService定义,然后在当前的服务类下实现;

image.png

6.1创建service接口及其实现类

package com.pkq.demo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.pkq.demo.pojo.User;

/**
 * ClassName: UserService
 * Package: com.pkq.demo.service
 * Description:
 *
 * @Author pkq
 * @Create 2024/3/23 14:44
 * @Version 1.0
 */
public interface UserService extends IService<User> {
}

package com.pkq.demo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pkq.demo.mapper.UserMapper;
import com.pkq.demo.pojo.User;
import com.pkq.demo.service.UserService;
import org.springframework.stereotype.Service;

/**
 * ClassName: UserServiceImpl
 * Package: com.pkq.demo.service.impl
 * Description:
 *
 * @Author pkq
 * @Create 2024/3/23 14:44
 * @Version 1.0
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

6.2测试查询的记录总数

package com.pkq.demo;

import com.pkq.demo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * ClassName: MybatisPlusTest
 * Package: com.pkq.demo
 * Description:
 *
 * @Author pkq
 * @Create 2024/3/23 14:47
 * @Version 1.0
 */
@SpringBootTest
public class MybatisPlusTest {
    @Autowired
    private UserService userService;
    @Test
    public void testIserVice(){
        long count = userService.count();
        System.out.println(count);
    }
}

image.png

6.3测试批量插入

 @Test
    public void testSaveBatch(){
        ArrayList<User> userArrayList=new ArrayList<>();
        for(int i=0;i<5;i++){
            User user = new User();
            user.setName("pkq"+i);
            user.setAge(20+i);
            userArrayList.add(user);
        }
        userService.saveBatch(userArrayList);
        List<User> list = userService.list();
        for (User user:list){
            System.out.println(user);
        }
    }

image.png

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d13baac] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1398428044 wrapping com.mysql.cj.jdbc.ConnectionImpl@71179b6f] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email FROM user
==> Parameters: 
<==    Columns: id, name, age, email
<==        Row: -1954500607, 皮卡丘, 20, pkq@pkq.com
<==        Row: 1, Jone, 18, test1@baomidou.com
<==        Row: 2, Jack, 20, test2@baomidou.com
<==        Row: 3, Tom, 28, test3@baomidou.com
<==        Row: 4, Sandy, 21, test4@baomidou.com
<==        Row: 5, Billie, 24, test5@baomidou.com
<==        Row: 562069505, pkq0, 20, null
<==        Row: 704675842, pkq1, 21, null
<==        Row: 704675843, pkq2, 22, null
<==        Row: 767590402, pkq3, 23, null
<==        Row: 767590403, pkq4, 24, null
<==      Total: 11
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d13baac]
User(id=-1954500607, name=皮卡丘, age=20, email=pkq@pkq.com)
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
User(id=562069505, name=pkq0, age=20, email=null)
User(id=704675842, name=pkq1, age=21, email=null)
User(id=704675843, name=pkq2, age=22, email=null)
User(id=767590402, name=pkq3, age=23, email=null)
User(id=767590403, name=pkq4, age=24, email=null)

  /**
     * @Description 测试条件查询,且仅返回一个
     * getOne:sql查询的结果必须为1条或者没有,否则报错 !!!!
     */
    @Test
    public void test2(){
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
        wrapper.gt(User::getAge,20);
        User one = userService.getOne(wrapper);
        System.out.println(one);
    }

    /**
     * @Description 根据条件批量查询
     */
    @Test
    public void test3(){
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
        wrapper.gt(User::getAge,20);
        List<User> list = userService.list(wrapper);
        System.out.println(list);
    }

    /**
     * @Description 根据条件批量查询并分页
     */
    @Test
    public void test4(){
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
        wrapper.gt(User::getAge,20);
        //构建分页对象
        IPage<User> page=new Page<>(2,3);
        userService.page(page,wrapper);
        System.out.println(page.getRecords());
        System.out.println(page.getPages());
        System.out.println(page.getTotal());
    }

    /**
     * @Description 测试服务层save保存单条操作
     */
    @Test
    public void test5(){
        User user1 = User.builder().name("wangwu").userName("laowang4").
                email("444@163.com").age(20).password("333").build();
        boolean isSuccess = userService.save(user1);
        System.out.println(isSuccess?"保存成功":"保存失败");
    }

    /**
     * @Description 测试服务层批量保存
     */
    @Test
    public void test6(){
        User user2 = User.builder().name("wangwu2").userName("laowang2").
                email("444@163.com").age(20).password("333").build();
        User user3 = User.builder().name("wangwu3").userName("laowang3").
                email("444@163.com").age(20).password("333").build();
        boolean isSuccess = userService.saveBatch(Arrays.asList(user2, user3));
        System.out.println(isSuccess?"保存成功":"保存失败");
    }
    
    /**
     * @Description 根据id删除操作
     */
    @Test
    public void test7(){
        boolean isSuccess = userService.removeById(17l);
        System.out.println(isSuccess?"保存成功":"保存失败");
    }

    /**
     * @Description 根据条件批量删除
     */
    @Test
    public void test8(){
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
        wrapper.gt(User::getId,12)
                .gt(User::getAge,20);
        boolean remove = userService.remove(wrapper);
        System.out.println(remove);
    }

    /**
     * @Description 测试根据id更新数据
     */
    @Test
    public void test9(){
        //UPDATE tb_user SET password=?, t_name=? WHERE id=?
        User user2 = User.builder().name("wangwu2").password("333").id(3l).build();
        boolean success = userService.updateById(user2);
        System.out.println(success);
    }

    /**
     * @Description 测试根据条件批量更新
     */
    @Test
    public void test10(){
        LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate(User.class);
        //UPDATE tb_user SET age=? WHERE (id IN (?,?,?))
        wrapper.in(User::getId,Arrays.asList(1l,3l,5l)).set(User::getAge,40);
        boolean update = userService.update(wrapper);
        System.out.println(userService);
    }

三、常用注解

经过以上的测试,在使用MyBatis-Plus实现基本的CRUD时,我们并没有指定要操作的表,只是在 Mapper接口继承BaseMapper时,设置了泛型User,而操作的表为user表
由此得出结论,MyBatis-Plus在确定操作的表时,由BaseMapper的泛型决定,即实体类型决
定,且默认操作的表名和实体类型的类名一致

如果实体类的名字和表的名字不一致,我们就需要使用@TableName注解

@TableName

描述:表名注解,标识实体类所对应的表
使用的位置:实体类

@TableName("t_user")
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

**在开发的过程中,我们经常遇到以上的问题,即实体类所对应的表都有固定的前缀,例如t_或tbl_ **
**此时,可以使用MyBatis-Plus提供的全局配置,为实体类所对应的表名设置默认的前缀,那么就 **
不需要在每个实体类上通过@TableName标识实体类对应的表

mybatis-plus:
	configuration:
	# 配置MyBatis日志
		log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
	global-config:
		db-config:
			# 配置MyBatis-Plus操作表的默认前缀
			table-prefix: t_

@TableId

  • 描述:主键注解
  • 使用位置:实体类主键字段
@TableName("t_user")
public class User {
    @TableId
    private Long uid;
    private String name;
    private Integer age;
    private String email;
}
  • MyBatis-Plus在实现CRUD时,会默认将id作为主键列,并在插入数据时,默认基于雪花算法的策略生成id
  • 如果实体类和表中的主键不是id,而且其他字段,比如uid,那么就会报错,我们需要在实体类的uid属性上面添加@TableId注解标识为主键

@TableId的value属性

如果实体类主键对应属性为id,表中主键字段是uid,则需要通过@TableId注解的value属性,指定表中的主键字段

@TableName("t_user")
public class User {
    @TableId(value="uid")
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

@TableId的type属性

type属性用来定义主键策略

常用主键策略
描述
IdType.ASSIGN_ID(默认) 基于雪花算法的策略生成数据id,和数据库id是否设置自增无关
IdType.AUTO 使用数据库的自增策略,注意:该类型要确保数据库设置了id自增,否则无效
生成策略 应用场景 特点
IdType.AUTO 数据库主键自增(确保数据库设置了 主键自增 否则无效) 1.使用数据库自带的主键自增值;
2.数据库自增的主键值会回填到实体类中;
3.数据库服务端生成的;
IdType.ASSIGN_ID 主键类型为number类型或数字类型String 1.MP客户端生成的主键值;
2.生成的主键值是数字形式的字符串
3.主键对应的类型可以是数字类型或者数字类型的字符串
4.底层基于雪花算法,让数据库的唯一标识也参与id的生成运算,保证id在分布式环境下,全局唯一(避免id的主键冲突问题);
IdType.ASSIGN_UUID 主键类型为 string(包含数字和字母组成) 1.生成的主键值包含数字和字母组成的字符串;
2.注意事项:如果数据库中主键值是number类型的,可不可用
全局配置主键生成策略
mybatis-plus:
	configuration:
		# 配置MyBatis日志
		log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
	global-config:
		db-config:
			# 配置MyBatis-Plus操作表的默认前缀
			table-prefix: t_
			# 配置MyBatis-Plus的主键策略
			id-type: auto

雪花算法

背景

  • 要选择一个合数发方案来面对数据规模的增长,面对逐渐增长的访问压力和数据量
  • 数据库的扩展方式主要有:业务分库,主从复制,数据库分表

数据库分表

不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。
单表数据拆分有两种方式:垂直分表和水平分表。示意图如下
image.png

垂直分表

把表中不经常使用而且占很大空间的列拆分出去

水平分表

当表中的数据记录很多的时候,考虑水平分表,但是也会导致更加复杂,需要考虑全局唯一性id

主键自增:可以按照范围来分段,比如1999999放到表1,10000001999999放到表2
如果分段过大,导致单表依然可能存在性能问题,如果分段过小,会导致切分后子表数量过多。
优点:可以随着数据的增加,平滑的扩充新表,比如现在用户100万,若增加到500万,只需要增加新的表即可,原有数据不需要变化。
缺点:分布不均匀,可能有的分段存储的数据量只有很少几条,而其他分段存储数据量很多

**取模 **
①同样以用户 ID 为例,假如我们一开始就规划了 10 个数据库表,可以简单地用 user_id % 10 的值来
表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 5 的子表中,ID 为 10086 的用户放到编号
为 6 的子表中。
②复杂点:初始表数量的确定。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题。
③优点:表分布比较均匀。
④缺点:扩充新的表很麻烦,所有数据都要重分布

雪花算法

雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
①核心思想:
长度共64bit(一个long型)。
首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。
10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。

image.png

@TableField

MyBatis-Plus在执行SQL语句时,要保证实体类中的属性名和
表中的字段名一致 。如果实体类中的属性名和字段名不一致的情况,会出现什么问题呢?

  • 如果实体类属性用的是驼峰命名,数据库表的字段使用下划线风格,MybatisPlus会自动把下划线命名风格转化为驼峰命名

  • 如果实体类的属性和表中的字段命名不一样,需要在实体类属性上面添加@TableField注解来设置属性对应的字段名
    :::tips
    注解@TableField作用:

  • 指定表中普通字段与实体类属性之间的映射关系;

  • 忽略实体类中多余属性与表中字段的映射关系(@TableField(exist = false));

以下情况可以省略:

  • 名称一样
  • 数据库字段使用_分割,实体类属性名使用驼峰名称(自动开启驼峰映射)
    :::
@TableName("t_user")
public class User {
    @TableId(type=IdType.ASSIGN_ID)
    private Long id;
	@TableField("userName")
    private String name;
    private Integer age;
    private String email;
	//增删改查操作时,忽略该属性
    @TableField(exist = false)
    private String address;
}

@TableLogic(逻辑删除)

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
  • 使用场景:可以进行数据恢复
  • 本质:修改操作

实际在删除数据时,为了数据留痕,一般选择逻辑删除,也就是为删除表添加逻辑删除字段,通过修改字段状态值来表示数据是否被删除;
image.png
修改表的结构
image.png
0表示未删除,1表示删除

# 设置mp运行时行为
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台输出sql
  global-config:
    db-config:
      logic-delete-field: is_delete # 约定全局删除字段
      logic-delete-value: 1
      logic-not-delete-value: 0

实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")//指定表名
public class User {
    private Integer id;
    private  String name;
    private Integer age;
    private String email;
    @TableLogic
    private Integer isDelete;
}

删除前数据库信息
image.png

 @Test
    public void testDeletetUser(){
        List<Long> list= Arrays.asList(1L,2L,3L);
        int result = userMapper.deleteBatchIds(list);
        System.out.println(result);

    }

image.png
测试查询功能,看看能否查询到逻辑删除的数据
image.png
被逻辑删除的数据是查询不到的。

四、条件构造器和常用接口

1.wrapper

image.png

  • Wrapper : 条件构造抽象类,最顶端父类
    • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
      • QueryWrapper : 查询条件封装
      • UpdateWrapper : Update 条件封装
      • AbstractLambdaWrapper : 使用Lambda 语法
        • LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
        • LambdaUpdateWrapper : Lambda 更新封装Wrapper

2.QueryWrapper

eq( ) :  等于 =
ne( ) :  不等于 <> 或者 !=
gt( ) :  大于 >
ge( ) :  大于等于  >=
lt( ) :  小于 <
le( ) :  小于等于 <=
between ( ) :  BETWEEN1 AND2 
notBetween ( ) :  NOT BETWEEN1 AND2 
in( ) :  in
notIn( ) :not in

sql中反向查询eg:not like != 等等,查询时是不会走索引的;

2.1数据库准备

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_user  没有给自增
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of tb_user
-- ----------------------------
BEGIN;
INSERT INTO `tb_user` VALUES (1, '赵一伤', '123456', 'zys', 19, 'zys@itcast.cn');
INSERT INTO `tb_user` VALUES (2, '钱二败', '123456', 'qes', 18, 'qes@itcast.cn');
INSERT INTO `tb_user` VALUES (3, '孙三毁', '123456', 'ssh', 20, 'ssh@itcast.cn');
INSERT INTO `tb_user` VALUES (4, '李四摧', '123456', 'lsc', 20, 'lsc@itcast.cn');
INSERT INTO `tb_user` VALUES (5, '周五输', '123456', 'zws', 20, 'zws@itcast.cn');
INSERT INTO `tb_user` VALUES (6, '吴六破', '123456', 'wlp', 21, 'wlp@itcast.cn');
INSERT INTO `tb_user` VALUES (7, '郑七灭', '123456', 'zqm', 22, 'zqm@itcast.cn');
INSERT INTO `tb_user` VALUES (8, '王八衰', '123456', 'wbs', 22, 'wbs@itcast.cn');
INSERT INTO `tb_user` VALUES (9, '张无忌', '123456', 'zwj', 25, 'zwj@itcast.cn');
INSERT INTO `tb_user` VALUES (10, '赵敏', '123456', 'zm', 26, 'zm@itcast.cn');
INSERT INTO `tb_user` VALUES (11, '赵二伤', '123456', 'zes', 25, 'zes@itcast.cn');
INSERT INTO `tb_user` VALUES (12, '赵三伤', '123456', 'zss1', 28, 'zss1@itcast.cn');
INSERT INTO `tb_user` VALUES (13, '赵四伤', '123456', 'zss2', 29, 'zss2@itcast.cn');
INSERT INTO `tb_user` VALUES (14, '赵五伤', '123456', 'zws', 39, 'zws@itcast.cn');
INSERT INTO `tb_user` VALUES (15, '赵六伤', '123456', 'zls', 29, 'zls@itcast.cn');
INSERT INTO `tb_user` VALUES (16, '赵七伤', '123456', 'zqs', 39, 'zqs@itcast.cn');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

2.2基本查询

要求:查询用户中姓名包含"伤",密码为"123456",且年龄为19或者25或者29,查询结果按照年龄降序排序;

package com.pkq.demo;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.pkq.demo.mapper.UserMapper;
import com.pkq.demo.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.List;

/**
 * ClassName: QueryWrapperTest
 * Package: com.pkq.demo
 * Description:
 *
 * @Author pkq
 * @Create 2024/3/28 10:40
 * @Version 1.0
 */
@SpringBootTest
public class QueryWrapperTest {
    @Resource
    private UserMapper userMapper;
	   /**
     * @Description 测试条件查询
     * 要求:查询用户中姓名包含"伤",密码为"123456",且年龄为19或者25或者29,查询结果按照年龄降序排序;
     * 如果查询的条件过于复杂,mp还适合么?
     *        简单的操作,直接使用mp
     *        但是非常复杂的操作,比如多表关联查询 复杂条件查询等,建议使用xml下sql实现;
     */
    @Test
    public void test1(){
        /**
         * SELECT id,user_name,password,name,age,email FROM tb_user
         * WHERE (user_name LIKE ? AND password = ? AND age IN (?,?,?)) ORDER BY age DESC
         *
         * Parameters: %伤%(String), 123456(String), 19(Integer), 25(Integer), 29(Integer)
         */
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.like("user_name","伤")
                .eq("password","123456")
                .in("age",19,25,29)
                .orderByDesc("age");
        List<User> userList = userMapper.selectList(queryWrapper);
        for (User user:userList){
            System.out.println(user);
        }

    }
}

image.png

2.3QueryWrapper逻辑查询or

2.3.1 OR查询说明
  1. 通过QueryWrapper多条件查询时,默认使用and关键字拼接SQL;
  2. 通过QueryWrapper调用or()方法时,底层会使用or关键字拼接方法左右的查询条件;
2.3.2代码

业务要求:查询用户名为"lisi"或者年龄大于23的用户信息;

/**
     * 查询用户名为"lisi"或者年龄大于23的用户信息;
     */
    @Test
    public void test2(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name","lisi")
                .or()
                .gt("age",23);
        List<User> userList = userMapper.selectList(queryWrapper);
        for (User user:userList){
            System.out.println(user);
        }
    }

image.png

2.4QueryWrapper实现模糊查询like

2.4.1模糊查询常用方法
  • like(“表列名”,“条件值”); 作用:查询包含关键字的信息,底层会自动添加匹配关键字,比如:%条件值%
  • likeLeft(“表列名”,“条件值”); 作用:左侧模糊搜索,也就是查询以指定条件值结尾的数据,比如:%条件值
  • likeRight(“表列名”,“条件值”);作用:右侧模糊搜索,也就是查询以指定条件值开头的数据,比如:条件值%
2.4.2代码
/**
     * 模糊查询
     */
    @Test
    public void testWrapper3(){
        //1.创建查询条件构建器
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        //2.设置条件
        wrapper.likeLeft("user_name","zhang");
        /*
            SELECT id,user_name,password,name,age,email
             from tb_user
             where user_name like ?
             %zhang
         */
        List<User> users = userMapper.selectList(wrapper);
        System.out.println(users);
    }

image.png

2.5QueryWrapper排序查询

2.5.1 核心方法
  • orderByAsc 升序排序,方法内可传入多个字段
  • orderByDesc 降序排序,方法内可传入多个字段

Wrapper 条件构造器中 order by 排序相关的方法如下:

orderBy(boolean condition, boolean isAsc, R... columns)

// 按 id, age 字段进行升序
wrapper.orderBy(true, true, "id", "age");

参数说明:

  • condition : 是否组装条件;
  • isAsc : 是否升序,true 为升序,false 为降序;
2.5.2 代码实现

需求:先根据age升序排序,如果年龄相同则按照id降序排序;

 /**
     * 需求:先根据age升序排序,如果年龄相同则按照id降序排序;
     */
    @Test
    public void testWrapper4(){
        //1.创建查询条件构建器
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        //2.设置条件
        wrapper.orderByAsc("age").orderByDesc("id");
        /*
            select * from tb_user where user_name = ? or age < ? and name in (?,?) order by age asc
         */
        List<User> users = userMapper.selectList(wrapper);
        System.out.println(users);
    }

image.png

2.6QueryWrapper限定字段查询

2.6.1方法说明

MP查询时,默认将表中所有字段数据映射查询,但是有时我们仅仅需要查询部分字段信息,这是可以使用select()方法限定返回的字段信息,避免I/O资源的浪费;
示例:
wrapper.select(“字段1”,“字段2”,…)

2.6.2 代码示例
@Test
public void testWrapper5(){
    //1.创建查询条件构建器
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //2.设置条件
    wrapper.eq("user_name","lisi")
    .or()
    .lt("age",23)
    .in("name","李四","王五")
    //.orderBy(true,true,"age")
    .orderByDesc("age")
    .select("id","user_name");
    /*
             select id,user_name from tb_user where user_name = ? or age < ? and name in (?,?) order by age asc
          */
    List<User> users = userMapper.selectList(wrapper);
    System.out.println(users);
}

2.7QueryWrapper实现分页条件查询

4.7.1 方法说明
//参数1:分页对象
//参数2:查询条件
mapper.selectPage(page,wrapper);
4.7.2 代码实现

需求:查询年龄大于等于23的用户信息,并显示第2页,每页大小为4;

 @Test
    public void testWrapper6(){
        int currentPage=2;//当前第几页
        int currentPageSize=4;//每页有多少条
        //创建分页对象
        IPage<User> pageInfo=new Page<>(currentPage,currentPageSize);
        QueryWrapper<User> wrapper=new QueryWrapper<>();
        wrapper.gt("age",23);
        //分页查询 pageInfo==userIPage -->true
        IPage<User> userIPage = userMapper.selectPage(pageInfo,wrapper);
        //获取分页参数
        long pages = pageInfo.getPages();//总页数
        long current = userIPage.getCurrent();//当前页
        List<User> records = userIPage.getRecords();//当前页的数据
        long total = userIPage.getTotal();//总记录数
        long size = userIPage.getSize();//每页显示条数
        System.out.println("总页数:"+pages);
        System.out.println("当前页:"+current);
        System.out.println("总记录数:"+total);
        System.out.println("每页显示条数:"+size);

        System.out.println("当前页的数据:");
        for (User user:records){
            System.out.println(user);
        }
    }
JDBC Connection [HikariProxyConnection@1202874820 wrapping com.mysql.cj.jdbc.ConnectionImpl@71e7adbb] will not be managed by Spring
==>  Preparing: SELECT COUNT(*) AS total FROM tb_user WHERE (age > ?)
==> Parameters: 23(Integer)
<==    Columns: total
<==        Row: 8
<==      Total: 1
==>  Preparing: SELECT id,user_name,password,name,age,email FROM tb_user WHERE (age > ?) LIMIT ?,?
==> Parameters: 23(Integer), 4(Long), 4(Long)
<==    Columns: id, user_name, password, name, age, email
<==        Row: 13, 赵四伤, 123456, zss2, 29, zss2@itcast.cn
<==        Row: 14, 赵五伤, 123456, zws, 39, zws@itcast.cn
<==        Row: 15, 赵六伤, 123456, zls, 29, zls@itcast.cn
<==        Row: 16, 赵七伤, 123456, zqs, 39, zqs@itcast.cn
<==      Total: 4
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6ba060f3]
总页数:2
当前页:2
总记录数:8
每页显示条数:4
当前页的数据:
User(id=13, userName=赵四伤, password=123456, name=zss2, age=29, email=zss2@itcast.cn)
User(id=14, userName=赵五伤, password=123456, name=zws, age=39, email=zws@itcast.cn)
User(id=15, userName=赵六伤, password=123456, name=zls, age=29, email=zls@itcast.cn)
User(id=16, userName=赵七伤, password=123456, name=zqs, age=39, email=zqs@itcast.cn)

3.LambdaQueryWrapper查询

3.1 使用QueryWrapper开发存在的问题

  1. 使用QueryWrapper查询数据时需要手写对应表的列名信息,及其容易写错,开发体验不好;
  2. 使用QueryWrapper查询数据时,表的列名硬编码书写,后期一旦表结构更改,则会带来很大的修改工作量,维护性较差;

LambdaQueryWrapper可以解决上述出现问题,开发推荐使用;

3.2 代码实现

@Test
    public void testLambdaQueryWrapper1() throws Exception{
        // LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();

        //        wrapper.like("user_name", "%伤%")
        //                .eq("password","123456")
        //                .ge("age", 28)
        //                .between("age",29 , 39);  // 包含边界值
        wrapper.like(User::getUserName, "伤")
                .eq(User::getPassword, "123456")
                .in(User::getAge, Arrays.asList(19,25,29))
                .orderByDesc(User::getAge)
                .select(User::getId, User::getUserName);
        List<User> users = userMapper.selectList(wrapper);
        System.out.println(users);
    }

image.png

4.LambdaQueryWrapper删除和更新操作

image.png

4.1 条件删除

/**
     * 条件删除
     * @throws Exception
*/
@Test
public void testWrapper5() throws Exception{

  LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().eq(User::getUserName, "武大郎");
  int i = userMapper.delete(wrapper);
  System.out.println(i);
}

4.2条件更新

image.png

/**
     * 条件更新
     * @throws Exception
*/
@Test
public void testWrapper6() throws Exception{

  /**
     * UPDATE tb_user SET t_name=? WHERE (id = ?)
     */
  // 参数1: 最新的值
  User user = new User();
  user.setUserName("张三丰");

  // 参数2:更新时条件
  LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();
  wrapper.eq(User::getId, 15);

  int update = userMapper.update(user, wrapper);
  System.out.println(update);
}


/**
     * 条件更新
     * @throws Exception
     */
@Test
public void testWrapper7() throws Exception{
  /**
         * UPDATE tb_user SET t_name=?, user_name=? WHERE (id = ?)
         */
  // 参数1: 最新的值
  // 参数2:更新时条件
  LambdaUpdateWrapper<User> wrapper = Wrappers.<User>lambdaUpdate();
  wrapper.eq(User::getId, 15)
    .set(User::getUserName, "张三丰666")
    .set(User::getName,"zsf666");

  int update = userMapper.update(null, wrapper);
  System.out.println(update);
}

image.png

5.自定义查询接口实现分页查询

目前我们使用MP自带的分页插件可以很友好的实现分页查询操作,但是如果一些查询需要我们自定义SQL,那该如何实现分页查询操作呢?

5.1 自定义接口中直接传入Page分页对象即可

public interface UserMapper extends BaseMapper<User> {
    /**
     * 查询大于指定id的用户信息,并分页查询实现
     * @param page
     * @param id
     * @return
     */
    IPage<User> findGtIdByPage(IPage<User> page, @Param("id") Long id);
}

5.2定义xml映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pkq.demo.mapper.UserMapper">

	<select id="findGtIdByPage" resultType="com.pkq.demo.pojo.User">
		 select id,user_name,password,name,age,email from tb_user where id > #{id}
	</select>
</mapper>

5.3测试

 @Test
    public void test2(){
        IPage<User> userIPage=new Page<>();
        userMapper.findGtIdByPage(userIPage,5L);
        //获取分页参数
        long pages = userIPage.getPages();//总页数
        long current = userIPage.getCurrent();//当前页
        List<User> records = userIPage.getRecords();//当前页的数据
        long total = userIPage.getTotal();//总记录数
        long size = userIPage.getSize();//每页显示条数
        System.out.println("总页数:"+pages);
        System.out.println("当前页:"+current);
        System.out.println("总记录数:"+total);
        System.out.println("每页显示条数:"+size);

        System.out.println("当前页的数据:");
        for (User user:records){
            System.out.println(user);
        }
    }
==>  Preparing: SELECT COUNT(*) AS total FROM tb_user WHERE id > ?
==> Parameters: 5(Long)
<==    Columns: total
<==        Row: 11
<==      Total: 1
==>  Preparing: select id,user_name,password,name,age,email from tb_user where id > ? LIMIT ?
==> Parameters: 5(Long), 10(Long)
<==    Columns: id, user_name, password, name, age, email
<==        Row: 6, 吴六破, 123456, wlp, 21, wlp@itcast.cn
<==        Row: 7, 郑七灭, 123456, zqm, 22, zqm@itcast.cn
<==        Row: 8, 王八衰, 123456, wbs, 22, wbs@itcast.cn
<==        Row: 9, 张无忌, 123456, zwj, 25, zwj@itcast.cn
<==        Row: 10, 赵敏, 123456, zm, 26, zm@itcast.cn
<==        Row: 11, 赵二伤, 123456, zes, 25, zes@itcast.cn
<==        Row: 12, 赵三伤, 123456, zss1, 28, zss1@itcast.cn
<==        Row: 13, 赵四伤, 123456, zss2, 29, zss2@itcast.cn
<==        Row: 14, 赵五伤, 123456, zws, 39, zws@itcast.cn
<==        Row: 15, 张三丰666, 123456, zsf666, 29, zls@itcast.cn
<==      Total: 10
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1db87583]
总页数:2
当前页:1
总记录数:11
每页显示条数:10
当前页的数据:
User(id=6, userName=吴六破, password=123456, name=wlp, age=21, email=wlp@itcast.cn)
User(id=7, userName=郑七灭, password=123456, name=zqm, age=22, email=zqm@itcast.cn)
User(id=8, userName=王八衰, password=123456, name=wbs, age=22, email=wbs@itcast.cn)
User(id=9, userName=张无忌, password=123456, name=zwj, age=25, email=zwj@itcast.cn)
User(id=10, userName=赵敏, password=123456, name=zm, age=26, email=zm@itcast.cn)
User(id=11, userName=赵二伤, password=123456, name=zes, age=25, email=zes@itcast.cn)
User(id=12, userName=赵三伤, password=123456, name=zss1, age=28, email=zss1@itcast.cn)
User(id=13, userName=赵四伤, password=123456, name=zss2, age=29, email=zss2@itcast.cn)
User(id=14, userName=赵五伤, password=123456, name=zws, age=39, email=zws@itcast.cn)
User(id=15, userName=张三丰666, password=123456, name=zsf666, age=29, email=zls@itcast.cn)

五、MybatisPlus分页插件

创建配置类MyBatisPlusConfig

package com.pkq.demo.config;

import com.baomidou.mybatisplus.annotation.DbType;
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;

/**
 * ClassName: MybatisPlusConfig
 * Package: com.pkq.demo.config
 * Description:
 *
 * @Author pkq
 * @Create 2024/3/28 10:11
 * @Version 1.0
 */
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInterceptor.setOverflow(false);
        // 设置最大单页限制数量,-1不受限制
        paginationInterceptor.setMaxLimit(-1L);
        //如果配置多个插件,切记分页最后添加
        interceptor.addInnerInterceptor(paginationInterceptor);
        return interceptor;
    }
}

测试

@Test
    public void testPage(){
        int currentPage=2;//当前第几页
        int currentPageSize=4;//每页有多少条
        //创建分页对象
        IPage<User> pageInfo=new Page<>(currentPage,currentPageSize);
        //分页查询 pageInfo==userIPage -->true
        IPage<User> userIPage = userMapper.selectPage(pageInfo,null);
        //获取分页参数
        long pages = userIPage.getPages();//总页数
        long current = userIPage.getCurrent();//当前页
        List<User> records = userIPage.getRecords();//当前页的数据
        long total = userIPage.getTotal();//总记录数
        long size = userIPage.getSize();//每页显示条数
        System.out.println("总页数:"+pages);
        System.out.println("当前页:"+current);
        System.out.println("总记录数:"+total);
        System.out.println("每页显示条数:"+size);

        System.out.println("当前页的数据:");
        for (User user:records){
            System.out.println(user);
        }
    }

image.png
image.png

六、 MP代码生成器

6.1 开发现状

开发中当有一个新的业务要实现时,通常我们需要构建一下信息:

  • 定义PO类数据库表和实体类的映射 Java Bean,打各种mp的注解。
  • 定义DAO层需要编写接口 Mapper ,接口 Mapper 需要去继承 MP 中的 BaseMapper 接口。
  • 定义Service层编写 Service 层接口和实现类。业务接口需要去继承 MP 中的 IService,业务实现类需要继承 MP 中的 ServiceImpl 和 实现业务接口。
  • 定义Controller层编写 Controller 并标注 Spring MVC 中的相关注解。 显然上述存在固定的流程,且存在大量重复操作,you now 代码价值低且没效率!

6.2 MP逆向工程介绍

针对目前开发的现状,MP的代码生成器就可以一展身手了;
通过MP代码生成器可以生成模板性的代码,减少手工操作的繁琐,使开发人员聚焦于业务开发之上,提升开发效率;
AutoGenerator 类是MyBatis-Plus 的核心代码生成器类,通过 AutoGenerator 可以快速生成 Mapper接口、Entity实体类、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

6.3 代码生成

参考代码:资料\mp_generator

或者是gitee开源链接:https://gitee.com/jitheima/mp_generator.git

完整代码:
image.png
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

说明:以后在项目中使用时,先在本工程生成,然后就可以把代码拷贝到对应的项目目录中使用了;

6.4代码生成说明

代码生成说明

当有一个新的业务实现时,对接口的功能实现上,我们通常来说需要构建下面的信息:

  • PO类数据库表和实体类的映射 Java Bean。
  • DAO层需要编写接口 Mapper ,接口 Mapper 需要去继承 MP 中的 BaseMapper 接口。
  • Service层编写 Service 层接口和实现类。业务接口需要去继承 MP 中的 IService,业务实现类需要继承 MP 中的 ServiceImpl 和 实现业务接口。
  • Controller层编写 Controller 并标注 Spring MVC 中的相关注解。

从上面的各类代码中可以放下,代码都是模板性的,如果用手工copy、修改的方式来实现,太烦人也没效率,而这时就是代码生成器小展身手的时候,使用代码生成器生成模板性的代码,减少手工操作的繁琐,集中精力在业务开发上,提升开发效率。
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Mapper接口、Entity实体类、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

具体使用步骤

  1. 打开 CodeGenerator 类
  2. 修改对应的参数即可相关参数介绍:
DB_TYPE: 数据库类型, 默认是 MySQL
DB_NAME: 数据库名,用户修改为自己的数据库
HOST_NAME: 数据库IP, 默认 localhost
JDBC_USERNAME: 数据库用户名, 默认:root
JDBC_PASSWORD: 数据库密码,默认:root
TABLES: 需要生成代码的表, 数组
PACKAGE_PARENT_NAME: 代码生成的包结构
IS_DTO: 是否生成DTO, 默认:false
AUTHOR: 作者名称, 默认:itheima
  1. 运行main方法执行生成代码
  2. 拷贝到自己的项目中即可

以后在项目中使用,在这里生成后,可以把代码拷贝到对应的目录里使用,在整个黑马头条项目开发阶段,使用了当前生成的mapper和实体类。

七、MybatisX插件[扩展]

7.1 MybatisX插件介绍

MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。

安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装。

功能:

  • Java 与 XML 调回跳转
  • Mapper 方法自动生成 XML

mybatisx-001.gif

7.2 基于MybatisX实现逆向工程

image.png

image.png
image.png

八.乐观锁[扩展]

当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:

  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对,就更新失败

乐观锁就是当前操作者认为在自己操作资源的过程中,其他人操作相同资源的可能性极低,所以无需加锁,而是通过设置一个版本号来加以约束;
悲观锁:排它锁,比如synchronized关键字就是悲观锁,当前线程做操作时,不允许其它线程做操作;
乐观锁:当前线程做操作时,允许其它线程做操作,但是如果其它线程做了操作,则当前操作失败
乐观锁在数据库中有什么优势?
避免长事务场景锁定数据资源,导致其它线程操作该资源时阻塞,如果阻塞过多,那么导致数据库连接资源耗尽,进而数据库宕机了;(事务阻塞,不会释放连接资源)
本质上就是在操作前,先获取操作行的version版本号,然后再做前天操作,然后最后再更新这一行,更新时,给sql条件一个判断版本号的sql片段: select version,xxx from user where id=100; version=30 --> 做其他操作(20s);—> update user set xxx,version=version+1 where xxxx and version=30;
使用场景:
1.业务操作周期长,如果业务整个加入事务,导致数据库资源锁定周期过长,性能降低;
2.如果资源争抢过于激烈,会导致失败重试次数过多,导致性能降低;
示例:
image.png

@Data
@NoArgsConstructor//主要用于mybatis底层反射构建user实体类对象
@AllArgsConstructor//主要是lombok基于构建者模式构建对象
@Builder
/**
 * 如果变的名称与实体类名称一致,该注解可省略
 */
@TableName("tb_user")//指定表名
public class User {
    private Integer id;
    private  String userName;
    private String password;
    private String name;
    private Integer age;
    private String email;
    @TableLogic
    private Integer isDeleted;
    @Version
    private Integer version;

}

配置乐观锁拦截器:

    /**
     * 注册插件
     * @return
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //构建mp的插件注册器bean,通过该bean可批量注册多个插件
        MybatisPlusInterceptor plusInterceptor = new MybatisPlusInterceptor();
        //配置乐观锁拦截器
        OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
        //注册
        plusInterceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
        return plusInterceptor;
    }

测试:

 /**
     * @Description 测试乐观锁
     */
    @Test
    public void testOp(){
        User user = userMapper.selectById(5);
        System.out.println(user);
        user.setName("zws777");
        userMapper.updateById(user);
    }

使用mp的乐观锁,需要先自己根据主键id查询用户信息,信息中包含了此时的version数据,然后再更新,更新时会将查询的version值作为更新条件取更新;
效果:
image.png
image.png

九、MP字段自动填充

1.MP字段自动填充

1.1 背景说明

说明:
image.png
实际开发中有些字段存在大量的重复操作,比如create_time update_time等,需要经常在实体类中设置new Date(),比较繁琐,可以借助MP的自动填充功能

1.2 定义实体类

@TableName("tb_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
	//指定插入时自动填充的字段
    @TableField(value = "create_time",fill = FieldFill.INSERT)
    private Date createTime;
  	//自定插入或者更新时自动填充的字段
    @TableField(value = "update_time",fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

1.3 配置填充规则

package com.itheima.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;


@Component
public class FillDataHandler implements MetaObjectHandler {

    /**
     * 定义自动填充的方法
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        //设置insert操作时的时间点
        metaObject.setValue("createTime",new Date());
        //设置update操作时的时间点
        metaObject.setValue("updateTime",new Date());
    }

    /**
     * 定义更新时填充的方法
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        //设置update操作时的时间点
        metaObject.setValue("updateTime",new Date());
    }
}

1.4 测试

更新测试:

    /**
     * @Description 根据主键id更新用户信息(开发最常用)
     */
    @Test
    public void test04(){
        long id=12l;
        //把主键信息和要更新的信息封装到实体类中,设置什么值,就更新什么值,为null的属性,不做处理
        User user = User.builder().id(id).userName("张三丰2").age(120).build();
        //UPDATE tb_user SET real_name=?, age=? WHERE id=?
        //如何获取real_name字段的? user--->User.class--->通过反射获取userName Field字段对象---》
        // 获取字段上的注解对象----》value值就可获取--》real_name
        int count = userMapper.updateById(user);
        System.out.println(count);
    }

插入测试:

    /**
     * @Description 测试插入
     */
    @Test
    public void test02(){
        User user =
                User.builder()
                        .userName("itheima4")
                        .name("itcast4")
                        .age(14)
                        .email("itcast@itcast4.cn")
                        .password("44444")
                        .build();
        //插入数据
        int count = userMapper.insert(user);
        System.out.println("受影响行数:"+count);
    }
本文含有隐藏内容,请 开通VIP 后查看