Mybatis plus的使用

Mybatis plus

在这里插入图片描述

官网:https://baomidou.com/

在这里插入图片描述

介绍:

MyBatis-Plus是mybatis的一个增强工具,在Mybatis的基础上只做增强不做改变,为简化开发,提高效率而生。MyBatis-Plus提供了通用的mapper和service,可以在不编写任何sql语句的情况下,快速的实现对表单的CRUD,批量、逻辑删除、分页等操作。

**注意:MyBatis-plus在进行插入的时候默认雪花算法生成id,所以id比较长,数据库表id字段使用bigInt类型。

快速使用

1、创建springboot项目

2、导入依赖

  • mysql驱动的依赖
  • mbatis-plus的依赖
  • lomboku依赖
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>


<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

<!--用于简化实体类开发-->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>


3、lombok插件的使用

安装插件:

在这里插入图片描述

4、配置文件设置

#设置数据源
#数据源类型  com.zaxxer.hikari.HikariDataSource是springboot的默认数据源
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
#数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#链接地址的url,注意mysql的版本有关系,一些版本的配置会不同
#设置当前连接数据库,操作数据库的编码格式characterEncoding=utf-8 --使用mysql版本在5.7,5的版本
#mysql在8的版本时,需要添加时区的配置serverTimezone=GMT
spring.datasource.url=jdbc:mysql://localhost:3306/fileup?characterEncoding=utf-8&userSSL=false
spring.datasource.username=root
spring.datasource.password=root

在这里插入图片描述

5、lombok的使用
  • 注解生成方法

    
    @NoArgsConstructor			//生成无参构造方法
    @AllArgsConstructor			//生成有参构造方法
    @Getter						//生成get方法
    @Setter						//生成set方法
    @EqualsAndHashCode
    public class User {
        private int id;
        private String name;
    
    }
    
    //由于注解太多,所以可以使用@Data注解来创建,效果同上
    @Data
    public class User {
        private int id;
        private String name;
    
    }
    
    

    在这里插入图片描述

6、mapper的创建

在这里插入图片描述

@Autowired注解参数报红色波浪线

在这里插入图片描述

愿意:

IOC容器中只能存在类所对应的bean,不能存在接口所对应的bean

在mapper接口上添加

@Repository

意思:将一个类或一个接口标识为持久层

加入日志功能

只需要在配置文件中加入配置就好

#加入日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

在这里插入图片描述

BaseMapper接口中的方法

1、insert

// 插入一条记录
int insert(T entity);

实现

 public void insertUser()
    {
        User user = new User();
        user.setName("维嘉");
        int result = userMapper.insert(user);
        System.out.println("result:" + result);
        //可以获取的新增数据的表中自动递增的主键
        System.out.println("id:" + user.getId());
    }

如上,可以获取到

新添加数据主键自增的值

注意

mybatis新增主键自增值是经过雪花算法的,所以数据库中主键类型也该设置为bigint

要获取到新增主键值还需要将java中对应表的java类中主键参数设置为Long(long的引用类型)

2、delete

// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
 public void testDelete()
    {
        //通过id删除用户信息
        /*
        ==>  Preparing: DELETE FROM user WHERE id=?
        ==> Parameters: 1615289713650360321(Long)
        */
        int result = userMapper.deleteById(1615289713650360321L);
         
        /*
        根据map集合中所设置的条件删除用户信息
        ==>  Preparing: DELETE FROM user WHERE name = ? AND id = ?
        ==> Parameters: 可乐(String), 1234567892(Integer)
        */
        Map<String, Object> map = new HashMap<>();
        map.put("name","可乐");
        map.put("id",1234567892);
        int result = userMapper.deleteByMap(map);
        
        /*
        通过多个id来实现批量删除
        ==>  Preparing: DELETE FROM user WHERE id IN ( ? , ? , ? )
        ==> Parameters: 1615289713650360322(Long), 1615289713650360323(Long), 1615290575823101953(Long)
         */
        List<Long> longs =
                Arrays.asList(1615289713650360322L, 1615289713650360323L, 1615290575823101953L);
        int result = userMapper.deleteBatchIds(longs);
        System.out.println("result:"+ result);

    }

3、Update

// 根据 whereEntity 条件,更新记录
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);

4、select

// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    public void testSelect()
    {
        /*
        通过id查询用户信息
        ==>  Preparing: SELECT id,name FROM user WHERE id=?
        ==> Parameters: 224305001(Long)
        User user = userMapper.selectById(224305001L);
        System.out.println(user.toString());
         */
        /*
        根据多个id查询多个用户信息
        ==>  Preparing: SELECT id,name FROM user WHERE id IN ( ? , ? )
        ==> Parameters: 224305001(Long), 224305002(Long)
        List<Long> list = Arrays.asList(224305001L,224305002l);
        List<User> users = userMapper.selectBatchIds(list);
        users.forEach(System.out::println);
         */
        /*
        根据map集合中的条件来查询用户信息
        ==>  Preparing: SELECT id,name FROM user WHERE name = ? AND id = ?
        ==> Parameters: 李伟佳(String), 2019240402(Long)
        Map<String,Object> map = new HashMap<>();
        map.put("name","李伟佳");
        map.put("id",2019240402L);
        List<User> users = userMapper.selectByMap(map);
        users.forEach(System.out::println);
         */
        /*
        参数伟条件构造器,如果没有条件则设置为null
        ==>  Preparing: SELECT id,name FROM user
        ==> Parameters: 
         */
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);
    }

自定义一个sql

baseMapper中的方法,无法满足sql需求时,可以自定义一个sql

1、创建映射文件

默认地址为:类路劲下mapper下的任意目录下的xml都是映射文件

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

mybatis-plus的通用service方法

说明:

  • 通用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
  • https://baomidou.com/pages/49cc81/#service-crud-%E6%8E%A5%E5%8F%A3

a>IService

Mybatis-Plus中有一个接口IService和其实现类ServiceImpl,封装到了常见的业务逻辑层
在这里插入图片描述

b>创建Service接口和实现类

在这里插入图片描述

c>service的使用

由于mybatis-plus提供的业务不足,还会需要添加自己的业务,此时就需要自定义service,而自定义的service想调用mybatis-plus所需要的service时,则实现IService

在这里插入图片描述
在这里插入图片描述

将service标记成一个组件,在service的实现类上添加

@Service

d>service的方法

  • 查询总条数
  • 批量添加数据
    public void testCount()
    {
        /*
        查询总记录数
        ==>  Preparing: SELECT COUNT( * ) FROM user
        ==> Parameters:
        long count = userService.count();
        System.out.println(count);
         */

        /*
            批量添加数据
            ==>  Preparing: INSERT INTO user ( id, name ) VALUES ( ?, ? )
            由此可知,是通过单个添加,进行循环实现的
            ==> Parameters: 1615637924315439105(Long), 大哥1(String)
            ==> Parameters: 1615637924390936577(Long), 大哥2(String)
            ==> Parameters: 1615637924390936578(Long), 大哥3(String)
         */
        List<User> users = new ArrayList<>();
        for(int i = 1;i <=3;i++)
        {
            //id默认使用mybatis-plus的雪花算法
            User user = new User();
            user.setName("大哥" + i);
            users.add(user);
        }
        Boolean temp = userService.saveBatch(users);
        System.out.println(temp);
    }

service接口中的方法有很多,可以在mybatis-plus的官网中查看到

https://baomidou.com/pages/49cc81/#activerecord-%E6%A8%A1%E5%BC%8F

MyBatis-Plus提供的注解

a> @TableName

当数据库中表和实体类表名称不一样时:

方法1:

@TableName("数据库表名");
public void 类名(){
    
}

方法2:

通过配置文件做到全局配置

#来设置实体类与对应表的统一前缀
global-config.db-config.table-profix="设置数据库中表相同的前缀"

通过配置文件做到设置前缀功能,实现实体类和数据库表的对应

b> @TableId

b.a> 当实体类对象主键命名方式不为id时

MyBatis-plus默认将实体类的id作为主键,则当数据库表中主键id和实体类主键id命名不同时

public class 类名()
{
	/*
	当表字段主键为id,由于类名(对应的表)有前缀,为了区分,在id前加上u,此时由于mybatis-plus默认将id为主键,uid无法识别,此时会出现错误。
	此时通过@TableId 来解决会出现的错误
	*/
	@Table
	private int uid;
}
b.b> @Table注解的value属性

当表字段主键命名方式不为id且实体类对象主键命名方式不为id时

public class 类名()
{
	/*
	当表字段主键和实体类主键id不相同时
	此时通过@TableId的value属性来解决会出现的错误
	*/
	@Table(value="表主键的字段")
    //或
	@Table("表主键的字段")
	private int id;
}
b.c>@TableId注解的type属性

type作用:

表示当前主键生成策略(mybatis-plus主键id的默认生成为雪花算法)

当用户不需要通过雪花算法来生成id时,则可以使用type标签

当用户不使用mybatis-plus默认主键雪花算法生成算法时:

  • 首先设置表中主键id字段为自增
  • 设置@TableId注解的type属性
public void 类名(){
	//@TableId注解的Type属性来定义主键策略
	@TableId(type=TdType.AUTO)			//设置id字段自增
	private Long id;
}

在这里插入图片描述

通过配置文件来设置主键策略

global-config.db-config.id.type=设置值	

雪花算法

  • 背景

需要选择核实的方案去应对数据规模的增长,以应对渐增长的访问压力和数据量

数据库的扩展方式主要包括:业务分库、主从复制、数据库分表。

  • 数据库分表

将不同业务数据分散存储到不同的数据库服务器,能够支持百万甚至千万用户规模的业务,但如果业务持续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对但表数据进行拆分。

单表数据拆分的格式有两种:垂直拆分和水平拆分。示意图如下:

在这里插入图片描述

  • 垂直分表

垂直拆分适合将表中某些不常用且占了大量空间的列拆分出去

  • 水平分表

水平分表适合表行数特别大的表,有点公司要求单表行数超过5000万就必须分表,这个数字可以作为参考,单并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过1000万就要分表了,而对一些简单的表,即使存储超过1亿行,也可以不分表。

但不管怎么样,当看到表的数据量达到千万级别时,作为架构师就要警觉起来,因为这很可能是架构的性能瓶颈或者隐患。

水平分表相比于垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理。

以下为分表的方式:


主键自增

在这里插入图片描述

取模

在这里插入图片描述

雪花算法

在这里插入图片描述

c> @TableField()

实体类属性和表字段名不相同时

注意:mybatis-plus有一个默认规范,忽略下划线,字母大小写的区别

例如,表字段u_name实体类name,

作用:将实体类中属性和数据库表中字段帮定

public void 类名()
{
	private int id;
	@TableField("u_name")
	private String name;
}

d> @TableLogic

d.a>逻辑删除
  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后再数据库中仍旧能看到此条数据记录
  • 使用场景:可以进行数据恢复
d.c>实现逻辑删除
  • 数据库创建表,其中表添加一列,用于标记是否删除,其中1表删除,0表未删除。

  • 在这里插入图片描述

  • 创建实体类,实体类与表相对应

public void 类名()
{
	private 类型 属性名;
	private 类型 属性名;
	....
	@TableLogic
	private Integer isDeleted;		//表中对应用于是否删除的判断列
}

在这里插入图片描述

在这里插入图片描述

条件构造器

官方文档:https://baomidou.com/pages/10c804/#abstractwrapper

1、wrapper介绍

在这里插入图片描述

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

删除同查询(QueryWrapper,lambdaQueryWrapper)

2、wrapper测试

public void TestWrapper()
{
    //场景:查询用户名包含a,年龄再20~30之间,邮箱信息不为null的用户信息
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    // //数据库中表的字段名 , 条件值
    wrapper.like("name",'a')
            .between("age",20,30)
            .isNotNull("email");
    List<User> users = userMapper.selectList(wrapper);
}

其中like、between、isNotNull都是QueryWrapper的方法,其中第一个参数为表中列的字段名,后面的参数为条件值。

注意:当使用了逻辑删除,所有的查询语句都会添加where语句,后面会有自定义的逻辑删除判断

    public void SortTestWrapper()
    {
        //场景:查询用户信息,按照年龄的降序排序,若年龄相同,则按照id升序排序
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("age")
                .orderByAsc("id");
        List<User> users = userMapper.selectList(wrapper);
    }

删除测试(注意删除同查询使用相同的wrapper

public void deleteTestWrapper()
{
	//删除邮箱地址为null的用户信息
	QueryWrapper<User> wrapper = new QueryWrapper<>();
	wrapper.isNull("email");
	int result = userMapper.delete(wrapper);
}

修改测试 UpdateWrapper

    public void updateWrapper()
    {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        //将年龄大于20并且用户名中包含李的用户信息修改,或学号为2019240423的人
        wrapper.gt("age",20)
                .like("name","李")
                .or()
                .like("id",2019240423L);
        //第一个参数设置要修改的内容,第二参数设置修改的条件,都可以为null
        User user = new User();
        user.setAge(34);
        user.setName("李弟弟");
        /*
        ==>  Preparing: UPDATE user SET name=?, age=? WHERE (age > ? AND name LIKE ? OR id LIKE ?)
        ==> Parameters: 李弟弟(String), 34(Integer), 20(Integer), %李%(String), %2019240423%(String)
        <==    Updates: 2
         */
        int update = userMapper.update(user, wrapper);
        System.out.println(update);
    }

3、wrapper的方法集

https://www.jianshu.com/p/c5537559ae3a

【比较大小: ( =, <>, >, >=, <, <= )】
    eq(R column, Object val); // 等价于 =,例: eq("name", "老王") ---> name = '老王'
    ne(R column, Object val); // 等价于 <>,例: ne("name", "老王") ---> name <> '老王'
    gt(R column, Object val); // 等价于 >,例: gt("name", "老王") ---> name > '老王'
    ge(R column, Object val); // 等价于 >=,例: ge("name", "老王") ---> name >= '老王'
    lt(R column, Object val); // 等价于 <,例: lt("name", "老王") ---> name < '老王'
    le(R column, Object val); // 等价于 <=,例: le("name", "老王") ---> name <= '老王'
【范围:(between、not between、in、not in)】
   between(R column, Object val1, Object val2); // 等价于 between a and b, 例: between("age", 18, 30) ---> age between 18 and 30
   notBetween(R column, Object val1, Object val2); // 等价于 not between a and b, 例: notBetween("age", 18, 30) ---> age not between 18 and 30
   in(R column, Object... values); // 等价于 字段 IN (v0, v1, ...),例: in("age",{1,2,3}) ---> age in (1,2,3)
   notIn(R column, Object... values); // 等价于 字段 NOT IN (v0, v1, ...), 例: notIn("age",{1,2,3}) ---> age not in (1,2,3)
   inSql(R column, Object... values); // 等价于 字段 IN (sql 语句), 例: inSql("id", "select id from table where id < 3") ---> id in (select id from table where id < 3)
   notInSql(R column, Object... values); // 等价于 字段 NOT IN (sql 语句)
【模糊匹配:(like)】
    like(R column, Object val); // 等价于 LIKE '%值%',例: like("name", "王") ---> name like '%王%'
    notLike(R column, Object val); // 等价于 NOT LIKE '%值%',例: notLike("name", "王") ---> name not like '%王%'
    likeLeft(R column, Object val); // 等价于 LIKE '%值',例: likeLeft("name", "王") ---> name like '%王'
    likeRight(R column, Object val); // 等价于 LIKE '值%',例: likeRight("name", "王") ---> name like '王%'
【空值比较:(isNull、isNotNull)】
    isNull(R column); // 等价于 IS NULL,例: isNull("name") ---> name is null
    isNotNull(R column); // 等价于 IS NOT NULL,例: isNotNull("name") ---> name is not null
【分组、排序:(group、having、order)】
    groupBy(R... columns); // 等价于 GROUP BY 字段, ..., 例: groupBy("id", "name") ---> group by id,name
    orderByAsc(R... columns); // 等价于 ORDER BY 字段, ... ASC, 例: orderByAsc("id", "name") ---> order by id ASC,name ASC
    orderByDesc(R... columns); // 等价于 ORDER BY 字段, ... DESC, 例: orderByDesc("id", "name") ---> order by id DESC,name DESC
    having(String sqlHaving, Object... params); // 等价于 HAVING ( sql语句 ), 例: having("sum(age) > {0}", 11) ---> having sum(age) > 11
【拼接、嵌套 sql:(or、and、nested、apply)】
   or(); // 等价于 a or b, 例:eq("id",1).or().eq("name","老王") ---> id = 1 or name = '老王'
   or(Consumer<Param> consumer); // 等价于 or(a or/and b)or 嵌套。例: or(i -> i.eq("name", "李白").ne("status", "活着")) ---> or (name = '李白' and status <> '活着')
   and(Consumer<Param> consumer); // 等价于 and(a or/and b)and 嵌套。例: and(i -> i.eq("name", "李白").ne("status", "活着")) ---> and (name = '李白' and status <> '活着')
   nested(Consumer<Param> consumer); // 等价于 (a or/and b),普通嵌套。例: nested(i -> i.eq("name", "李白").ne("status", "活着")) ---> (name = '李白' and status <> '活着')
   apply(String applySql, Object... params); // 拼接sql(若不使用 params 参数,可能存在 sql 注入),例: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08") ---> date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
   last(String lastSql); // 无视优化规则直接拼接到 sql 的最后,可能存若在 sql 注入。
   exists(String existsSql); // 拼接 exists 语句。例: exists("select id from table where age = 1") ---> exists (select id from table where age = 1)

在这里插入图片描述

4、条件的优先级

其中and的第一个参数使用了lambda的表达式

    @Test
    //条件的优先级
    public void testYXJ()
    {
        //场景:将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
        //解决方法:lambda中的条件优先执行
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.like("user_name","a")
                .and(i->i.gt("age",20).or().isNull("email"));
        //执行后的sql语句
        /*
        update user set user_name=?,email=?where id_delete=0 AND(user_name like > AND (age > ? OR email IS NULL) )
        */
    }

	/*
	注意这是and和or的源码
	Consumer是lambda表达式中的消费者接口
	Param是泛型,是具体需要运行函数的类(也是wrapper的子类)
	*/
    default Children and(Consumer<Param> consumer) {
        return this.and(true, consumer);
    }
    default Children or(Consumer<Param> consumer) {
        return this.or(true, consumer);
    }

5、指定查询的字段

    @Test
    //指定查询的字段
    public void testZI()
    {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.select("user_name","age","email");
        List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
    }

//sql语句
select user_name,age,email from user where is_deleted=0

6、组装子查询

 @Test
 public void testZCX()
 {
     //使用子查询语句查询id小于10的用户信息
     //SELECT * FROM USER WHERE id IN (SELECT id FROM USER WHERE id < 224305007)
     QueryWrapper<User> wrapper = new QueryWrapper<>();
     //inSql方法,第一个参数子查询判断字段,第二个参数,判断的数据
     wrapper.inSql("id","SELECT id FROM USER WHERE id < 224305007");
     List<User> users = userMapper.selectList(wrapper);
     for (User u:users
     ) {
     	System.out.println(u);
     }
 }
/*
==>  Preparing: SELECT id AS uid,name,age FROM user WHERE (id IN (SELECT id FROM USER WHERE id < 224305007))
==> Parameters: 
<==    Columns: uid, name, age
*/

7、UpdateWrapper

在这里插入图片描述

string类型判断的小技巧

//isNotBlank判断某个字符串是否不为空字符串,不为null,不为空白符
StringUtils.isNotBlank();

8、Condition组装条件

场景:在从前端from表单中接受数据,需要做出判断,是否需要在sql语句中添加

在这里插入图片描述

容易出现字段名错误的问题,可以使用lambdaQueryWrapper和lambdaUpdateWrapper

9、lambdaQueryWrapper

在这里插入图片描述

SFunction函数式接口

可以直接通过SFunction找个函数就可以直接访问到当前实体类中属性所对应的字段名
在这里插入图片描述

10、9、lambdaUpdateWrapper

在这里插入图片描述

六、插件

1、分页插件

1、设置插件的配置类

package com.wz.config;

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

@Configuration
//扫描mapper接口的所在的包
@MapperScan("com.wz.mapper")
public class MyBatisPlusConfig {
    //配置mybatis_plus的插件(分页查询)
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor()
    {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //在插件对象中添加一个内部插件(具体需要使用的插件)
        //由于数据库的分页操作并不是全部一样的,所以需要设置数据库的类型 DbType.MYSQL
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

2、测试类使用

package com.wz;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wz.mapper.UserMapper;
import com.wz.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MyBatisPlusPlugTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testPage()
    {
        /*
        查询当前分页数据
        <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param("ew") Wrapper<T> queryWrapper);
        P page :分页对象
        Wrapper<T> queryWrapper : 条件构造器
        limit关键字: index , pageSix ; 当前查询起始 ,每页显示的条数
         */
        //page(a,b)的构造函数,第一个为第几页(1,0为从第一个开始),第二个为每页数条数
        //原理:先查询到总条数,第一个参数为第几页开始,limit关键字的第一个参数经过  (a-1)*3
        Page<User> page = new Page<>(1,3);
        userMapper.selectPage(page,null);
        System.out.println(page);
        System.out.println("有无下一页:"+page.hasNext());
        System.out.println("有无上一页:"+page.hasPrevious());
        System.out.println("总记录数:"+page.getTotal());
        System.out.println("总页数:"+page.getPages());
        System.out.println("获取记录:" + page.getRecords());
        System.out.println("获取当前页页码:" + page.getCurrent() + "获取每页显示的条数:" + page.getSize());
        System.out.println(page.maxLimit());
    }
}

/*输出结果
==>  Preparing: SELECT COUNT(*) AS total FROM user
==> Parameters: 
<==    Columns: total
<==        Row: 30
<==      Total: 1
==>  Preparing: SELECT id AS uid,name,age FROM user LIMIT ?
==> Parameters: 3(Long)
<==    Columns: uid, name, age
<==        Row: 224305001, 马静怡, 12
<==        Row: 224305002, 张泽慧, 16
<==        Row: 224305003, 杨紫晴, 17
<==      Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6002e944]
com.baomidou.mybatisplus.extension.plugins.pagination.Page@2a5abd3c
有无下一页:true
有无上一页:false
总记录数:30
总页数:10
获取记录:[User(uid=224305001, name=马静怡, age=12), User(uid=224305002, name=张泽慧, age=16), User(uid=224305003, name=杨紫晴, age=17)]
获取当前页页码:1获取每页显示的条数:3
null


*/

2、自定义分页功能

场景:在自定义的sql语句中,通过分页插件,实现分页功能

1、mapper类中添加方法

    /*
    通过id查询用户信息并分页
    @param page MyBatis-plus所提供 的分页对象,必须位于第一个参数的位置
     */
    Page<User> selectPageVo(@Param("page")Page<User> page, @Param("uid")Long uid);

/*
参照mybatis-plus 的selectPage
    <P extends IPage<T>> P selectPage(P page, @Param("ew") Wrapper<T> queryWrapper);
该方法返回值为page对象,第一个参数也为page
*/

2、mapper配置文件设置sql语句

    <!--Page<User> selectPageVo(@Param("page")Page<User> page, @Param("id")Integer id);-->
    <select id="selectPageVo" resultType="User">
        select * from user where id > #{uid}
    </select>

3、测试类测试使用

 @Test
    public void testVo()
    {
        Page<User> userPage = new Page<>(1,3);
        userMapper.selectPageVo(userPage,224305007L);
        System.out.println(userPage);
    }



结果:
==>  Preparing: SELECT COUNT(*) AS total FROM user WHERE id > ?
==> Parameters: 224305007(Long)
<==    Columns: total
<==        Row: 24
<==      Total: 1
==>  Preparing: select * from user where id > ? LIMIT ?
==> Parameters: 224305007(Long), 3(Long)
<==    Columns: id, name, age
<==        Row: 224305008, 彭赛, 12
<==        Row: 224305009, 吴焱桦, 23
<==        Row: 224305010, 张锌江, 34
<==      Total: 3


3、乐观锁

a>场景

在这里插入图片描述

b>乐观锁与悲观锁

在这里插入图片描述

悲观锁:就是当前数据库是正在被操作中,其他人无法操作。即小李在使用数据库时,小王不能使用。

乐观锁:通过版本号实现,即获取到数据库中某个数据的版本,当有人在操作该数据库时,结果会使数据库版本提升,其他人再操作的话,版本不对,无法成功

c>模拟修改冲突

1、创建表

在这里插入图片描述

2、添加数据

3、添加实体类

import lombok.Data;

import java.util.logging.Logger;

@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    private Integer version;
}

4、添加mapper接口

public interface ProductMapper  extends BaseMapper<Product> {
}

5、演示

    //乐观锁的演示
    @Test
    public void testProduct()
    {
        System.out.println("小李查询商品价格");
        Product productLi = productMapper.selectById(1);
        System.out.println("小李查询到的商品价格为:" + productLi.getPrice());
        System.out.println("小王查询商品价格");
        Product productWang = productMapper.selectById(1);
        System.out.println("小王查询到的商品价格为:" + productWang.getPrice());
        System.out.println("\n 小李将商品价格+50");
        productLi.setPrice(productLi.getPrice()+50);
        productMapper.updateById(productLi);
        System.out.println("\n小王将商品的价格-30");
        productWang.setPrice(productWang.getPrice()-30);
        productMapper.updateById(productWang);
        Product productLaoBan = productMapper.selectById(1);
        System.out.println("老板查询到的商品价格为:" + productLaoBan.getPrice());
    }
结果信息:
小李查询商品价格
小李查询到的商品价格为:70
小王查询商品价格
小王查询到的商品价格为:70
小李将商品价格+50
小王将商品的价格-30
老板查询到的商品价格为:40


d>乐观锁实现流程

数据库中添加version字段

取出记录时,获取当前version

更新时,version+1,如果where语句中的version版本不对则更新失败

e>MyBatis-Plus实现乐观锁

1、修改实体类

使用@Version注解,指定该属性为乐观锁字段

@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    @Version
    //用于表示乐观锁版本号字段
    private Integer version;
}

2、配置类添加插件

@Configuration
//扫描mapper接口的所在的包
@MapperScan("com.wz.mapper")
public class MyBatisPlusConfig {
    //配置mybatis_plus的插件(分页查询)
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor()
    {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //在插件对象中添加一个内部插件(具体需要使用的插件)
        //由于数据库的分页操作并不是全部一样的,所以需要设置数据库的类型 DbType.MYSQL
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        
        //添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

3、测试

小李查询商品价格
小李查询到的商品价格为:100
小王查询商品价格
小王查询到的商品价格为:100
小李将商品价格+50
小王将商品的价格-30

//原因:当一个人修改数据时,其版本号字段会改变,当其他人修改时,如果版本号不同,则无法修改
如下:修改语句
UPDATE product SET name=?, price=?, version=? WHERE id=? AND version=?
如上:在修改语句中添加version条件判断

老板查询到的商品价格为:150


4、优化修改流程

即在更新表时,判断是否更新成功!!!!!

 //乐观锁的演示
    @Test
    public void testProduct()
    {
        System.out.println("小李查询商品价格");
        Product productLi = productMapper.selectById(1);
        System.out.println("小李查询到的商品价格为:" + productLi.getPrice());
        System.out.println("小王查询商品价格");
        Product productWang = productMapper.selectById(1);
        System.out.println("小王查询到的商品价格为:" + productWang.getPrice());
        System.out.println("\n 小李将商品价格+50");
        productLi.setPrice(productLi.getPrice()+50);
        productMapper.updateById(productLi);
        System.out.println("\n小王将商品的价格-30");
        productWang.setPrice(productWang.getPrice()-30);
        int result = productMapper.updateById(productWang);
        if(result==0)
        {
            //修改失败,重试
            Product productNew = productMapper.selectById(1);
            productNew.setPrice(productNew.getPrice()-30);
            productMapper.updateById(productNew);
        }
        Product productLaoBan = productMapper.selectById(1);
        System.out.println("老板查询到的商品价格为:" + productLaoBan.getPrice());
    }

七、通用枚举

表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现

如何让mybatis-plus扫描枚举,并将枚举中指定的属性值存储在表中

  • @EnumValue注解
  • 配置文件添加扫描配置

a>数据库表添加字段sex

在这里插入图片描述

b>设置枚举类

package com.wz.enums;

import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;

@Getter     //通过lombok添加get方法
public enum  SexEnum {
    MALE(1,"男"),
    FEMALE(2,"女");

    @EnumValue  //将注解所标识的属性的值存储到数据库中
    private Integer sex;
    private String sexName;

    //创建构造器
    SexEnum(Integer sex, String sexName) {
        this.sex = sex;
        this.sexName = sexName;
    }
}

c>实体类中添加枚举属性值

    private SexEnum sex;

d>配置文件中添加扫描枚举配置

#扫描通用枚举的包
mybatis-plus.type-enums-package=com.wz.enums.SexEnum

八、代码生成器

1、引入依赖

mybatis-plus逆向工程(代码生成器的核心依赖)
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.1</version>
</dependency>
freemarker的相关依赖,在实现的功能中会生成一个Freemarker引擎模板
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>

2、快速生成

public class FastAutoGeneratorTest{
	public static void main(String[] args)
	{
         /*
        url:数据库地址
        username:数据库用户名
        password:数据库用户名密码
        baomidou:自定义作者名
        D://:自定义指定输出目录
        com.baomidou.mybatisplus.samples.generator:设置父包名
        system:设置父包模块名
        t_user:数据库中的表名,当前要操作的表
        "t_", "c_" :过来表的前缀,可以多个设置
         */
        FastAutoGenerator.create("url", "username", "password")
        .globalConfig(builder -> {
            builder.author("baomidou") // 设置作者
                .enableSwagger() // 开启 swagger 模式
                .fileOverride() // 覆盖已生成文件
                .outputDir("D://"); // 指定输出目录
        })
        .packageConfig(builder -> {		//设置包
            builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
                .moduleName("system") // 设置父包模块名
                .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://")); // 设置mapperXml生成路径
        })
        .strategyConfig(builder -> {
            builder.addInclude("t_user") // 设置需要生成的表名
                .addTablePrefix("t_", "c_"); // 设置过滤表前缀
        })
        .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
        .execute();
	}
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

九、多数据源

在这里插入图片描述

1、选择或创建两个数据库

2、引入依赖

       <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.5.0</version>
        </dependency>

3、配置多数据源

说明:注释掉之前的数据库连接,添加新配置

jdbc:mysql://localhost:3306/fileup?characterEncoding=utf-8&userSSL=false
spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
      #注意此处master必须和primary后设置的值一样,由于默认master所以直接使用master即可
        master:
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
          #从数据源1
        slave_1:
          url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
          #从数据源2
        slave_2:
          url: ENC(xxxxx) # 内置加密,使用请查看详细文档
          username: ENC(xxxxx)
          password: ENC(xxxxx)
          driver-class-name: com.mysql.jdbc.Driver
       #......省略
       #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2

4、创建service

  • 创建实体类
  • 创建mapper
  • 创建service接口,实现接口

mapper1、

@Repository
public interface PersonMapper extends BaseMapper<Person> {
}

mapper2、

@Repository
public interface UserMapper extends BaseMapper<User> {
}

service接口1、

public interface UserService extends IService<User> {
}

service接口2、

public interface PersonService extends IService<Person> {
}

service接口实现1、

@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {

}

service接口实现2、

@Service
@DS("slave_1")      //指定操作的数据源为slave_1
public class PersonServiceImpl extends ServiceImpl<PersonMapper,Person> implements PersonService {
}

注意:在service层有两个注解

  • @Service 标注为业务层
  • @DS 指定当前业务层操纵的数据源,其中值为在配置文件中设置的数据源名

5、测验

@Autowired
private UserService userService;
@Autowired
private PersonService personService;
@Test
public void test()
{
    System.out.println(userService.getById(224305007L));
    System.out.println(personService.getById(1));
}

结果:
    ==>  Preparing: SELECT id,name,age,sex FROM user WHERE id=?
==> Parameters: 224305007(Long)
<==    Columns: id, name, age, sex
<==        Row: 224305007, 王俊杰, 19, null
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f6b7426]
User(id=224305007, name=王俊杰, age=19, sex=null)

==>  Preparing: SELECT id,name,sex,age FROM person WHERE id=?
==> Parameters: 1(Integer)
<==    Columns: id, name, sex, age
<==        Row: 1, 张三,, 13
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@91a2543]
Person(id=1, name=张三, sex=, age=13)


@DS可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。

十、MyBatisX插件

在这里插入图片描述

idea安装MyBatisX

使用官方文档:https://baomidou.com/pages/ba5b24/#%E5%8A%9F%E8%83%BD

工具1**:

快速定位到mapper接口所对应的映射文件,以及映射文件所对应的接口

在这里插入图片描述

工具2**:代码快速生成

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

工具3、MyBatisX快速生成CRUD

1、注意方法前缀insert、selete、update、delete
在这里插入图片描述

通过输入方法名前缀选择方法
在这里插入图片描述

alt+enter实现快速生成
在这里插入图片描述

生成成功

十一:mybatis使用过程中的问题

1、in语句结果无序变有序

https://blog.csdn.net/GUILTYxc/article/details/132950592

场景
当我在MySQL中使用IN语句进行查询时
例如SELECT * FROM Course WHERE teacher_id in (“6553”,“2145”,“3162”),从课程表里使用教师id查询记录,这时我希望返回的记录是按(“6553”,“2145”,“3162”)的顺序,但是发现并不是,而是(“2145”,“3162”,“6553”)的顺序,进行了排序。

如何解决?
可以使用ORDER BY FIELD语句。
SELECT * FROM Course WHERE teacher_id in (“6553”,“2145”,“3162”) ORDER BY FIELD(tracher_id,“6553”,“2145”,“3162”),其中tracher_id指定排序的字段,之后给出顺序,返回的结果即可满足按IN中指定的顺序排序。

MyBatisPlus
在MyBatis或MyBatisPlus中,可以手动拼接sql语句达到同样的效果。

方法(传入字段名和字段列表):

private String makeOrderByFieldSql(String fieldName, List<String> fields) {

StringBuilder orderSql = new StringBuilder();
orderSql.append("ORDER BY FIELD(").append(fieldName).append(",");
for (String field : fields) {
    orderSql.append("\"").append(field).append("\"").append(",");
}
//去除最后一个 ",",加上括号
orderSql.deleteCharAt(orderSql.length() - 1).append(")");
return orderSql.toString();

}

使用时,构建:

//构建LambdaQueryWrapper
LambdaQueryWrapper<Course> courseLambdaQueryWrapper = new LambdaQueryWrapper<>();
//调用方法生成sql语句
String orderSql = this.makeOrderByFieldSql("teacher_ids", teacherIds);
//使用last函数在查询的最后拼接sql语句
courseLambdaQueryWrapper.in(CourseResource::getTeacherId, resourceIds).last(orderSql);
//查询得到结果
courses = courseService.list(courseLambdaQueryWrapper);

前缀选择方法

[外链图片转存中…(img-Q8l1iFl7-1723616746850)]

alt+enter实现快速生成

image-20230131153334402

生成成功

十一:mybatis使用过程中的问题

1、in语句结果无序变有序

https://blog.csdn.net/GUILTYxc/article/details/132950592

场景
当我在MySQL中使用IN语句进行查询时
例如SELECT * FROM Course WHERE teacher_id in (“6553”,“2145”,“3162”),从课程表里使用教师id查询记录,这时我希望返回的记录是按(“6553”,“2145”,“3162”)的顺序,但是发现并不是,而是(“2145”,“3162”,“6553”)的顺序,进行了排序。

如何解决?
可以使用ORDER BY FIELD语句。
SELECT * FROM Course WHERE teacher_id in (“6553”,“2145”,“3162”) ORDER BY FIELD(tracher_id,“6553”,“2145”,“3162”),其中tracher_id指定排序的字段,之后给出顺序,返回的结果即可满足按IN中指定的顺序排序。

MyBatisPlus
在MyBatis或MyBatisPlus中,可以手动拼接sql语句达到同样的效果。

方法(传入字段名和字段列表):

private String makeOrderByFieldSql(String fieldName, List<String> fields) {

StringBuilder orderSql = new StringBuilder();
orderSql.append("ORDER BY FIELD(").append(fieldName).append(",");
for (String field : fields) {
    orderSql.append("\"").append(field).append("\"").append(",");
}
//去除最后一个 ",",加上括号
orderSql.deleteCharAt(orderSql.length() - 1).append(")");
return orderSql.toString();

}

使用时,构建:

//构建LambdaQueryWrapper
LambdaQueryWrapper<Course> courseLambdaQueryWrapper = new LambdaQueryWrapper<>();
//调用方法生成sql语句
String orderSql = this.makeOrderByFieldSql("teacher_ids", teacherIds);
//使用last函数在查询的最后拼接sql语句
courseLambdaQueryWrapper.in(CourseResource::getTeacherId, resourceIds).last(orderSql);
//查询得到结果
courses = courseService.list(courseLambdaQueryWrapper);
Mybatis Plus 是 Mybatis 的一个增强工具包,提供了许多实用的功能,例如自动分页、自动填充、逻辑删除、多租户等。下面简单介绍一下使用 Mybatis Plus 的步骤: 1. 引入 Mybatis Plus 的依赖: ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.1</version> </dependency> ``` 2. 配置数据源,可以使用 Mybatis Plus 提供的 `MybatisPlusInterceptor` 实现自动分页: ```java @Configuration @MapperScan("com.example.mapper") public class MybatisPlusConfig { @Autowired private DataSource dataSource; @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean(); sqlSessionFactory.setDataSource(dataSource); // 添加分页插件 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); sqlSessionFactory.setPlugins(interceptor); return sqlSessionFactory.getObject(); } } ``` 3. 创建实体类和对应的 Mapper 接口: ```java @Data @AllArgsConstructor @NoArgsConstructor @TableName("user") public class User { @TableId(type = IdType.AUTO) private Long id; private String name; private Integer age; } public interface UserMapper extends BaseMapper<User> { } ``` 4. 使用 Mybatis Plus 提供的通用方法进行 CRUD 操作: ```java @Service public class UserService { @Autowired private UserMapper userMapper; public List<User> list() { return userMapper.selectList(null); } public User getById(Long id) { return userMapper.selectById(id); } public boolean save(User user) { return userMapper.insert(user) > 0; } public boolean updateById(User user) { return userMapper.updateById(user) > 0; } public boolean removeById(Long id) { return userMapper.deleteById(id) > 0; } } ``` 以上就是使用 Mybatis Plus 的简单步骤,更多详细的使用方法可以参考官方文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值