mybatis-plus的简单使用

一、前置场景准备(基于springboot)

pom

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.2</version>
    </parent>
    <groupId>com.chenpei.springboot</groupId>
    <artifactId>springboot-mybatis-plus</artifactId>
    <version>1.0-SNAPSHOT</version>
<dependencies>
    <!--引入mybatis-plus的springboot启动器-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3</version>
    </dependency>
    <!--引入mybatis-plus的依赖-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus</artifactId>
        <version>3.5.3</version>
    </dependency>
<!--    数据库相关-->
    <dependency><!--注意这里要在resource下作适配-->
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-3-starter</artifactId>
        <version>1.2.18</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.19</version>
    </dependency>
<!--    数据库引擎-->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.0.33</version>
    </dependency>
<!--    测试依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>3.1.0</version>
    </dependency>
</dependencies>

application.yml

pring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:#mybatis-plus默认开启驼峰命名
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: chen13515216766
      url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8

 实体类:

@TableName("t_emp")//当表名和实体类名不一样时要指定对应的表名
public class Employee {
    @TableId//这里可以指定主键的列名,默认跟属性名一样
    Integer eid;//要用实体类指定,因为使用实体类默认值是null,
    // 在更新表格操作中,避免因为0而改了所有本不应该改的信息(mybatis-plus更新时,属性为null不修改)
    String empName;
    String empSex;
    Integer empAge;
    String empEmail;
    Integer did;

    @Override
    public String toString() {
        return "Employee{" +
                "eid=" + eid +
                ", empName='" + empName + '\'' +
                ", empSex='" + empSex + '\'' +
                ", empAge=" + empAge +
                ", empEmail='" + empEmail + '\'' +
                ", did=" + did +
                '}';
    }

    public Integer getEid() {
        return eid;
    }

    public void setEid(Integer eid) {
        this.eid = eid;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    public String getEmpSex() {
        return empSex;
    }

    public void setEmpSex(String empSex) {
        this.empSex = empSex;
    }

    public Integer getEmpAge() {
        return empAge;
    }

    public void setEmpAge(Integer empAge) {
        this.empAge = empAge;
    }

    public String getEmpEmail() {
        return empEmail;
    }

    public void setEmpEmail(String empEmail) {
        this.empEmail = empEmail;
    }

    public Integer getDid() {
        return did;
    }

    public void setDid(Integer did) {
        this.did = did;
    }
}

二、基于mapper的接口强化

A、我们不需要xml文件,只需要指定接口,也不需要在接口上加@Mapper注解,代替用@MapperScan("mapper接口所在的文件夹")

B、Mapper内容如下:

public interface EmployeeMapper extends BaseMapper<Employee> {
    //泛型指定要操作表对应的实体类
    //里面什么都不用指定
}

C、主启动类的注解:

@SpringBootApplication
@MapperScan("com.chenpei.springboot.mappers")//指定mapper所在包
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

D、测试

@SpringBootTest
public class MyTest {
    //注入mapper的代理对象
    @Autowired
    EmployeeMapper employeeMapper;
    @Test
    public void testOne(){
        //根据id批量查询数据
        ArrayList<Integer> numall= new ArrayList<>();
        numall.add(3);
        numall.add(4);
        List<Employee> employees = employeeMapper.selectBatchIds(numall);
        System.out.println(employees);
        //测试更新数据,根据id更新,主键属性不能为空
        Employee employee = new Employee();
        employee.setEid(13);
        //仅仅修改姓名属性
        employee.setEmpName("春空千鹤");
        int i = employeeMapper.updateById(employee);
        System.out.println(i==1);//如果成功控制台为true
        //测试删除方法
       employee.setEid(19);
        int rel = employeeMapper.deleteById(employee);
        System.out.println(rel);


    }
}

三、基于service层的再增强(直接通过Service层直接调用crud方法)

A、在实际业务中我们会碰到查询等情况,service层在业务中更多仅是传递的作用,这时增强service层就显得很有必要。

B、值得一提的是,Mapper接口并不能丢掉,因为会被service继承

C、增强Service层需要我们的Service层接口和Service实现类都继承特定的接口,接口或类中也要指定特定的泛型实现

service层如下:

public interface EmployeeService extends IService<Employee> {
    //继承该接口中有一半方法已经有默认实现,但是还有一半没有默认实现。
}



@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService{
//继承接口是为了实现接口中没有被实现的方法,在ServiceImpl中得到了实现,
// 当中的两个泛型,分别是Employee的mapper接口,另一个是这个接口对应的实体类
}

D、这样的方式相比于直接使用mapper接口来讲有两个变化

  1. 这种方式,service层就相当于天然开启了事务
  2. 会有更多的方法,比如批量添加等等

E、下面我们进行测试:

@SpringBootTest
public class MyTest {
    EmployeeService employeeService;
    
    @Test
    public void twoTest(){
        //除了接口提供的增删改查方法,Service方式强化的crud还提供了批量插入和根据id有无判断
        //是插入还是修改的方法。
//        Employee meEmployee = new Employee();
//        meEmployee.setEid(14);
//        meEmployee.setEmpName("闻善");
        //首先测试有id的情况,会update
//        boolean rel = employeeService.saveOrUpdate(meEmployee);
//        Employee myEmployee = new Employee(null,"向羽","男",45,"xiangyu@123.com",1);
        //测试没有id变插入方法
//        boolean b = employeeService.saveOrUpdate(myEmployee);
//        System.out.println(rel);
//        System.out.println(b);
        //测试批量插入方法
        ArrayList<Employee> myEmployees = new ArrayList<>();
        Employee myEmployee = new Employee(19,"彭于晏","男",36,"pengyuyan@123.com",1);
        Employee myEmployeeTwo = new Employee(20,"Jack","男",24,"jack@123.com",3);
        myEmployees.add(myEmployee);
        myEmployees.add(myEmployeeTwo);
        boolean saved = employeeService.saveBatch(myEmployees);
        System.out.println(saved);
    }
}

四、分页插件的使用

A、mybatis-plus自身就整合了分页插件。同时Mybatis提供了一个专门放置插件的集合,一旦该集合中被放入相关插件,就会起效,所以我们第一步是要将集合注入bean容器,并将分页插件放入集合(在主启动类进行)

@SpringBootApplication
@MapperScan("com.chenpei.springboot.mappers")
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        //向专门放置mybatisPlus的插件的集合中放置分页插件的实现
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //因为mybatisPlus集成了分页插件,所以直接装入相关实例PaginationInnerInterceptor即可·
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

B、在mybatis-plus提供的方法中使用分页插件

@SpringBootTest
public class MyTest {
    
    EmployeeMapper employeeMapper;
    
    @Test
    public void testOne(){
        //首先要用IPage的实现类Page指定分页的当前页和每页容量(跟之前的PageHelper指定内容一致)
        Page<Employee> myPage = new Page<>(1,3);
        //调用提供的获取页面的方法,传入page对象,第二个参数设置查询条件,null值为全部查询
        employeeMapper.selectPage(myPage,null);
        //上面方法执行完毕后就会将分页后相关信息注入咱自己创建的Page对象(跟之前的创建PageInfo对象,传入list对象功能一样)
        List<Employee> records = myPage.getRecords();//获取当前页的数据集合
        long total = myPage.getTotal();//获取总记录条数,即记录条数
        long myPageSize = myPage.getSize();//获取每页的页容量
        System.out.println("数据为: " + records +"\n总记录数: " + total +
                "\n本页有数据: " + myPageSize + "条");
    }
}

C、如何在自定义的方法中使用分页插件

在mapper接口中声明方法,并将返回值设置为IPage类型

public interface EmployeeMapper extends BaseMapper<Employee> {
    //声明自定义方法,将返回值指定为IPage接口
    IPage<Employee> getAllEmployee(IPage<Employee> myIPage);


}

xml映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace = 接口的全限定符 -->
<mapper namespace="com.chenpei.springboot.mappers.EmployeeMapper">
<!--虽然接口中定义的返回值为Ipage接口,但xml文件中还是指定为返回集合泛型的映射-->
    <select id="getAllEmployee" resultType="com.chenpei.springboot.pojo.Employee">
        select * from t_emp<!--注意这里不能加;,不然无法加limit语句,切记-->
    </select>



</mapper>

在application.xml中指定映射文件位置:

mybatis-plus:
  #指定映射文件地址,默认地址为resource的mapper文件夹下
  mapper-locations: classpath:/mappers/*.xml

测试方法中调用

@Test//上面省略了mapper接口的注入
    public void twoTest(){
        //指定分页数据
        Page<Employee> myPage = new Page<>(1,3);
        //调用自定义方法,并传入page对象作为参数
        employeeMapper.getAllEmployee(myPage);
        //下面正常进行即可
        List<Employee> records = myPage.getRecords();//获取当前页的数据集合
        long total = myPage.getTotal();//获取总记录条数,即记录条数
        long myPageSize = myPage.getSize();//获取每页的页容量
        System.out.println("数据为: " + records +"\n总记录数: " + total +
                "\n本页有数据: " + myPageSize + "条");
    }

D、相关报错:

lineNumber: 1; columnNumber: 1; 前言中不允许有内容。

这是因为你映射文件地址指定有问题

五、Wrapper实现类的使用(用来给mybatis-plus提供的各种方法拼接条件语句)

A、Wrapper 接口的结构(部分):

  • QueryWrapper是最常用的实现类,可用于删除/查询/修改场景
  • UpdateWrapper则只能用于修改场景。

 B、实现大概逻辑:

  1. 创建QueryWrapper对象或者UpdateWrapper对象
  2. 用对象调用相关方法,指定拼接到语句的条件(具体方法可查官网:条件构造器 | MyBatis-Plus),传入参数大概是列名和被判断的值,方法即条件
  3. 每次调用方法之后都会返回本身对象类型,因此可以链式调用
  4. 将设置好的Wrapper对象放入方法中,执行后即使拼接了条件的结果

C、具体方法的尝试

  • 大于、小于、等于
@Test//省略了mapper接口的注入,下面所有测试方法亦如是
    public void testOne(){
       //创建Wrapper
        QueryWrapper<Employee> myFirst = new QueryWrapper<>();
        //翻译:条件是emp_age这列中,值大于18的
        myFirst.gt("emp_age", 18)
                //链式调用,eid这列小于20
                .lt("eid",20)
                //emp_sex这列数据中等于“女”
                .eq("emp_sex","女");
        //注:上面这三个条件(替他条件方法一样)在默认情况下使用and拼接
        List<Employee> employees = employeeMapper.selectList(myFirst);
        System.out.println(employees);
    }
  • 那么条件和条件之间如何由默认的and变为or
 myFirst.gt("emp_age", 18).or()
//or方法加载gt和lt直接,就是说gt和lt中间的拼接条件变为or了
                //链式调用,eid这列小于20
                .lt("eid",20)//这里没有or所以还是默认的and拼接
                //emp_sex这列数据中等于“女”
                .eq("emp_sex","女");
  • 模糊查询(省略结构)

        //查询名字中有暴的,会帮助我们在前后加%
        myFirst.like("emp_name","暴");
       
  • 排序
//查询结果按照emp_age升序排序,如果emp_age相同则根据eid降序排列
        //这种中间就是,不用担心冲突
        myFirst.orderByAsc("emp_age").orderByDesc("eid");

D、UpdateWrapper的不同之处

  • 因为其是QueryWrapper的子类,所以他也拥有上面的方法,同时多出来了set方法,用来给wrapper设置数据。
  • 那么为什么要有set方法
  1. 因为update相关方法都需要传入对象,为空的值不会被修改,那么如果我们真想把某个值改为空值就不行了。
  2. 将要修改的数据直接通过set方法放在UpdateWrapper中就可以了,不用再自己创建对象。
  • 使用:
 @Test
    public void testOne(){
       //创建Wrapper
      UpdateWrapper<Employee> updateWrapper = new UpdateWrapper<>();
      //放置条件和要修改的数据
        updateWrapper.set("emp_sex","男").set("emp_email",null).eq("emp_name","闻善");
        //执行方法
        int update = employeeMapper.update(null, updateWrapper);
        System.out.println(update);

    }

E、LambdaQueryWrapper和LambdaUpdateWrapper跟QueryWrapper和UpdateWrapper的不同

  • 允许使用lambda表达式来指定列名参数,减少自己拼接字符串出现错误。
  • lambda表达式就是简写的单方法接口实现
  1. 当接口有参数且有多个语句,仅能简写为:(arg1,arg2)->{}
  2. 当方法没有参数,且语句只有一个则能简写为:()->语句;如果有返回值会自动返回
  3. 上面单语句无参数的情况,还能通过方法引用再简化:如语句为System.out.println(),可以变为System.out:pritln 也不用写括号和箭头了。
  • 例子:
@Test
    public void testOne(){
       //创建Wrapper
      LambdaUpdateWrapper<Employee> updateWrapper = new LambdaUpdateWrapper<>();
      //放置条件和要修改的数据,指定列名使用方法引用,这样就直接通过实体类属性找列名了
        updateWrapper.set(Employee::getEmpEmail,"wenshan@qq.com").eq(Employee::getEmpName,"闻善");
        //执行方法
        int update = employeeMapper.update(null, updateWrapper);
        System.out.println(update);

    }

六、注解实现实体类与表的自定义映射

A、@TableName

  • 用在实体类上,指定实体类对应的表名(默认使用类名寻找表名)

B、@TableId

  • 用在实体类的属性值上,指定对应的主键名,其有两个属性
  1. value 指定对应的列名
  2. type 指定插入数据时,主键的赋值方式,默认使用雪花算法

C、@TableField

  • 用在实体类非主键的属性上指定对应的列名

D、展示

@TableName("t_emp")//当表名和实体类名不一样时要指定对应的表名
public class Employee {
    //当主键名和表列名不一致时可以使用@TableId中的value属性指明
    //type属性指定主键赋值方式,mybatis-plus默认使用雪花算法赋值,要用long类型。
    //如果指定为AUTO则变为自增长类型。
    @TableId( type= IdType.AUTO)
    Integer eid;//要用实体类指定,因为使用实体类默认值是null,
    // 在更新表格操作中,避免因为0而改了所有本不应该改的信息(mybatis-plus更新时,属性为null不修改)
    @TableField("emp_name")//这里有驼峰映射没必要,仅为了展示使用
    String empName;
    String empSex;
    Integer empAge;
    String empEmail;
    Integer did;
//省略必要结构
}

E、全局指定实体类和表明的映射及主键增长策略

mybatis-plus:
  global-config:
    db-config:
      table-prefix: t_   #给每个类名前加上t_后寻找表映射
      id-type: auto #指定全局主键策略为自增长

F、雪花算法和自增长的使用场景

  • 雪花算法或者uuid

当一个实体类映射多个表时,使用防止不同表的自增长导致主键冲突。

  • 自增长

当一个实体类对应一个表时可以使用自增长。

七、逻辑删除和物理删除的快速实现

(一)逻辑删除和物理删除的区别

A、物理删除

真的就是在数据库将数据删除了,删除后这条数据在数据库中就不是真实存在的

B、逻辑删除

通过表中某个字段的变化代表改数据被删除,例如有个deleted字段,若改字段值为0则说明未被删除,为1则说明该数据已被删除,这样在查询时将已删除数据过滤掉即可,实际的数据并未在数据库中被改变。

(二)利用mybatis-plus快速实现逻辑删除

A、用@TableLogic注解表明作为逻辑删除标识的列对应的属性

@TableLogic//将部门号标注为是否删除标识字段(省个事,懒得增加表结构了)
    Integer did;

可以通过两个属性指定删除和未删除的标识

    /**
     * 默认逻辑未删除值(该值可无、会自动获取全局配置)
     */
    String value() default "";

    /**
     * 默认逻辑删除值(该值可无、会自动获取全局配置)
     */
    String delval() default "";

B、指定删除标识规则(在application中全局指定)

mybatis-plus:
  global-config:
    db-config:
      #logic-delete-field: did #指定全局的逻辑删除标识字段
      logic-delete-value: 2 # 指定被删除的数据标识,默认值为1
      logic-not-delete-value: 1 #指定未被删除的数据标识,默认值为0

(三)指定逻辑删除后删除和查询操作的变化

A、删除操作

删除操作由delete变为update set 标识删除字段;

B、查询操作

每次查询都会拼接上条件 标识字段 = 未被删除标识符

八、乐观锁和悲观锁的实现 

(一)乐观锁和悲观锁的区别

A、乐观锁就是保证当并发的数据修改和查询操作时,保证修改者只能有一个,当第二个人来修改时,检查是否为最新数据,如果不是就驳回,等待其再次请求

B、悲观锁就是当一个人修改数据时,其他用户指定等该用户操作完毕后才能进行数据库操作。

(二)mybatis-plus实现乐观锁

A、首先要给实体类增加属性,同时对应表加上version字段,然后在对饮version属性上添加@Version标签

@TableName("t_emp")//当表名和实体类名不一样时要指定对应的表名
public class Employee {
    @TableId( type= IdType.AUTO)
    Integer eid;
    @TableField("emp_name")
    String empName;
    String empSex;
    Integer empAge;
    String empEmail;
    @TableLogic
    Integer did;
    @Version //标识为版本字段
    Integer version;
}//省略结构

C、增加乐观锁插件

@Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        //向专门放置mybatisPlus的插件的集合中放置分页插件的实现
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //因为mybatisPlus集成了分页插件,所以直接装入相关实例PaginationInnerInterceptor即可·
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        //增加乐观锁插件
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }

C、这样之后,每次对数据进行修改时,就会在version字段加上1,当并发操作存在时,修改者都会携带version属性,当version属性值跟数据库一致时,就可以修改,如果不一致,则需要重新查询再修改。下面进行测试

@Test
    public void testOne(){
   //模拟并发操作,两个用户同时获取数据
        Employee employee1 = employeeMapper.selectById(6);
        Employee employee2 = employeeMapper.selectById(6);
        //用户一率先修改,赠加了version,
        employee1.setEmpName("忆长安");
        employeeMapper.updateById(employee1);
        //用户二同时准备修改,但是发现version不一致,修改失败;
        employee2.setEmpName("侬江南");
        employeeMapper.updateById(employee2);
    }

九、防止全表更新和删除操作

非常简单,直接在mybatis-plus的插件集合中增加一个拦截器即可

@Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        //向专门放置mybatisPlus的插件的集合中放置分页插件的实现
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //因为mybatisPlus集成了分页插件,所以直接装入相关实例PaginationInnerInterceptor即可·
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        //增加乐观锁插件
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        //增加防全局更新和删除拦截器
        mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return mybatisPlusInterceptor;
    }

测试:

@Test
    public void testOne(){
        //模拟全表删除操作
        employeeMapper.delete(null);
    }

因为我们配置了拦截器,所以会被拦截并报错:Prohibition of table update operation

十、利用mybatisX插件实现mybatis-plus的逆向工程

A、首先要在插件市场中下载该插件,然后按下面步骤进行即可

 ok后

然后选择要进行逆向的表,右键,选中插件选项

 

 

 

 完成后生成了包括接口,xml映射文件,service接口及实现类的curd强化

 B、利用插件进行自定义方法按照指定格式命名方法可以快速生成xml文件对应结构

以查询为例:

以select开头即可

命名完毕后选中option+回车即可快速生成。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值