MybatisPlus

MybatisPlus

1.什么是ORM

所有的程序都需要和数据打交道,如果需要将数据持久化存储那么必然使用到数据库,所以编程语言和数据库之间联系就衍生出了一些技术,我们称之为ORM框架。
ORM是Object-Relation Mapping的简称,即:对象关系映射,它的作用是在关系型数据库的表和编程语言中的对象之间作一个映射,方便编程语言和数据库之间交互数据。

2.什么是Mybatis

MyBatis是一款半自动化的ORM框架,半自动化的意思是SQL我们可以完全控制,并不会自动生成sql,它会自动把查询出来的数据转换存储到对象中,也会自动把对象中的数据放到sql里让数据库执行。所以成为半自动,全自动就是SQL也会帮我们生成,完全不用写sql了,比如早期的Hibernate就是全自动的ORM框架。有时候他生成的sql性能较差,所以项目中逐渐使用了MyBatis接管sql,更能优化性能,MyBatis的【官网地址】
它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。同时支持别名、动态sql、日志等功能

3.创建项目

1.依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.5</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <optional>true</optional>
</dependency>

2.配置文件

# 数据库配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis-plus?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: stt123456
# mybatis-plus配置
mybatis-plus:
  configuration:
  	# 配置输出sql到控制台
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.创建实体类

package com.zide.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * @author 石添
 * @date 2024/7/28
 * 类中叫属性,表中叫字段
 * 需要告知Java类,Student类与哪张表映射起来,通过@TableName注解
 * 属性和字段映射需要使用:@TableField注解
 * @TableId:该注解使用在主键上,type属性指定id生成策略,默认是没有策略
 */
@TableName("student")
@Data
public class Student implements Serializable {

    // 设置主键生成策略使用雪花算法
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    // @TableField("username")
    // 如果属性名和字段名一致,或者遵守驼峰命名规范,可以不用配置
    private String username;
    private String pasword;
    private String studentNo;
    private Long majorId;
    private Long classId;
    private String creator;
    private String updater;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    private Integer deleted;
    private Integer version;

}

4.创建Mapper

Mapper提供与数据库交互的功能,Mapper往往都是接口,MyBatis-Plus提供了一个BaseMapper,默认实现了单表的简单的CRUD的方法,批量插入的就没有

package com.zide.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zide.entity.Major;
import com.zide.entity.Student;
import org.apache.ibatis.annotations.Mapper;

public interface StudentMapper extends BaseMapper<Student> {
}

Mapper映射文件,在resources目录下创建mapper目录存放,在MyBatis-Plus中可以完全忽略掉xml文件的,在MyBatis中xml文件是必要配置,来映射表与实体类的关系,包括编写sql

<?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.zide.mapper.IStudentMapper">

</mapper>

5.创建Service

IService中提供了大量的方法,比如批量插入这是BaseMapper中没有的,也算是对它的封装,使用的话需要完成接口继承IService<>,且实现类中继承ServiceImpl<IClassesMapper, Classes>,一般不使用

service接口

public interface IStudentService extends IService<Student> {
}

service实现类

package com.zide.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zide.entity.Classes;
import com.zide.entity.Student;
import com.zide.mapper.IClassesMapper;
import com.zide.mapper.IStudentMapper;
import com.zide.service.IClassesService;
import com.zide.service.IStudentService;
import org.springframework.stereotype.Service;

/**
 * @author 石添
 * @date 2024/7/28
 * 1、MbBatis-Plus给我们提供了默认接口和实现,内置了一些批量,分页的方法
 * 2、可是使用Service代码入侵比较高,后期不需要使用MyBatis-Plus,需要修改大量的service
 * 3、Service提供的方法很多我们可以移到Mapper中实现
 * 4、ServiceImpl<Mapper,Entity>,mapper其实就是将mapper接口的代理对象注入到了Service中 ,不要重新注入就可以调用Mapper层的方法
 * 总结:Service我们开发的时候并不会直接使用
 */
@Service
public class ClassesServiceImpl extends ServiceImpl<IClassesMapper, Classes> implements IClassesService {

}

6.springboot主类

在该类中注上mapper扫描

@SpringBootApplication
@MapperScan("com.zide.mapper")

4.单元测试

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

在test目录下创建与src目录相同的包名

image.png

@Test
public void test1() {
    Student student = new Student();
    student.setUsername("张三1");
    student.setPasword("123456");
    student.setStudentNo("S111");
    student.setMajorId(0L);
    student.setClassId(0L);
    boolean save = studentService.save(student);
    log.info("save===》{}",save);
}

5.自动填充

自动填充分两步:
●在实体类中通过@TableField规定填充时机【新增/修改】
●自定义Handler实现MetaObjectHandler,设置新增和修改时的数据填充规则

@TableName("student")
@Data
public class Student implements Serializable {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    private String username;
    private String pasword;
    private String studentNo;
    private Long majorId;
    private Long classId;

    // 新增时填充
    @TableField(fill = FieldFill.INSERT)
    private String creator;
    // 新增和修改时时填充
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updater;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Integer deleted;

    @TableField(fill = FieldFill.INSERT)
    private Long version;

}
package com.zide.handler;

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

import java.time.LocalDateTime;

/**
 * @author 石添
 * @date 2024/7/28
 */
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    /**
     * 新增数据时的填充规则
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("开始插入填充...");
        LocalDateTime now = LocalDateTime.now();
        // 创建者和修改者,应该从上下文中获取当前用户名
        String creator = "admin";
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, now );
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, now);
        this.strictInsertFill(metaObject, "creator" , String.class, creator);
        this.strictInsertFill(metaObject, "updater", String.class, creator);
        this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
        this.strictInsertFill(metaObject, "version", Long.class, 1L);
    }

    /**
     * 修改数据时的填充规则
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("开始更新填充...");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updater", String.class, "admin");
    }
}

6.批量新增

批量新增我们要考虑一个性能问题,不能在循环中不断的去连接数据库,一条一条的发送sql,做批量发送,一次性执行多条插入,性能比较好。批量执行新增有一个sql

insert into student (id,username,password) values 
(1,"张三","123"),
(2,"张三","123"),
(3,"张三","123");

非批量,性能是极差的
insert into student (id,username,password) values (1,"张三","123"),
insert into student (id,username,password) values (2,"张三","123"),
insert into student (id,username,password) values (3,"张三","123");

批量插入前提:数据库支持批量插入,第二是数据库连接时开启批量插入,mysql开启批量插入的话,需要要在url后添加参数

&rewriteBatchedStatements=true&useServerPrepStmts=true&allowMultiQueries=true

7.修改

修改数据通常根据id修改或者根据其他的特定字段修改一条记录,为避免并发情况下修改数据错乱问题,可以引入锁机制,mybatis-plus提供了乐观锁插件,我们将会一一介绍

普通修改

@Test
public void test3() {
    Student student = new Student();
    student.setId(1817500963008552962L);
    student.setUsername("张三-改");
    studentService.updateById(student);
}

乐观锁插件

乐观锁是一种并发控制机制,用于确保在更新记录时,该记录未被其他事务修改。MyBatis-Plus 提供了 OptimisticLockerInnerInterceptor 插件,使得在应用中实现乐观锁变得简单。

乐观锁原理:
乐观锁的实现通常包括以下步骤:
1读取记录时,获取当前的版本号(version)。
2在更新记录时,将这个版本号一同传递。
3执行更新操作时,设置 version = newVersion 的条件为 version = oldVersion。
4如果版本号不匹配,则更新失败。

首先配置乐观锁插件

@Component
@MapperScan("com.zide.mapper")
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 创建插件对象
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 将乐观锁插件添加到拦截其中
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

其次在实体类中添加@Version注解

@Version
private Long version;

修改测试

Test
public void test4() {
    // 查询
    Student dbStudent = studentService.getById(1817500963008552962L);
    dbStudent.setUsername("乐观锁-张三");
    // 修改
    studentService.updateById(dbStudent);
}

8.删除

逻辑删除

数据删除是一个敏感的操作,数据删除之后如果事务提交数据就无法恢复,现在很多企业有的业务中不会直接将数据真正删除掉,只是做一个逻辑删除。逻辑删除的目的其实就是发现数据后期的作用,对数据进行计算,调整业务的运营方式。磁盘成本比较低。
问题:如果都做逻辑删除,可能导致一张表变得很大,此时考虑有些表不需要设计逻辑删除,有些可以将删除的数据定时同步到中间表中,再将业务表清理一下,保障正常的业务性能。
逻辑删除的实现在表中需要有一个逻辑删除的字段,在实体类中通过@TableLogic注解配置

// 默认0未删除,1已删除
@TableLogic
private Integer deleted;

9.查询

查询的业务是最复杂的,必然涉及到三种查询:单条查询、批量查询、分页查询、分组查询、查询排序等等,还会涉及到条件构造器的使用,条件构造器其实就是构建sql中的where条件

单条查询

@Test
public void test6() {
    studentService.getById(1817504724246560769L);
    // 根据条件构造器
    // QueryWrapper<Student> wrapper = new QueryWrapper<>();
    // wrapper.eq("id",1817504985127989250L).eq("username","张三");
    // 保障该wrapper条件只查出来一个,如果查出多个会报错

    // 2、推荐使用LambdaQueryWrapper
    LambdaQueryWrapper<Student> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(Student::getId,1817504985127989250L).or().eq(Student::getUsername,"张三1");
    Student student = studentService.getOne(wrapper);
    log.info("student===>{}",student);
}

批量查询

@Test
public void test7() {
    // 使用list方法,传入wrapper对象
    List<Student> list = studentService.list(new LambdaQueryWrapper<Student>().like(Student::getUsername, "张三1"));
    log.info("list===>{}",list.size());
}

分页查询

如果是mybatis其实要引入pageHelper依赖,mybatis-plus内置了分页,需要在配置类中配置分页插件

@Component
@MapperScan("com.zide.mapper")
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 创建插件对象
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 将乐观锁插件添加到拦截其中
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 添加分页插件 如果配置多个插件, 切记分页最后添加 limit
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

查询时传入Page对象即可实现分页查询

@Test
public void test8() {
    Page<Student> page = new Page<>();
    // 当前页 1
    // 每页的数据量 20
    page.setCurrent(1);
    page.setSize(10);
    // 条件使用wrapper
    LambdaQueryWrapper<Student> wrapper = new LambdaQueryWrapper<>();
    wrapper.like(Student::getUsername,"张三1");
    List<Student> list = studentService.list(page, wrapper);
    log.info("list===>{}",list);
    //按照上面规则一共有多少页
    log.info("getPages===>{}",page.getPages());
    log.info("getCurrent===>{}",page.getCurrent());
    log.info("getRecords===>{}",page.getRecords());
    //一共有多少条数据
    log.info("getTotal===>{}",page.getTotal());
    //一页可以装多少条数据
    log.info("getSize===>{}",page.getSize());
}

分组查询

需求:按照名字分组,查询每组名字的人数,groupBy

@Test
public void test9() {
    QueryWrapper<Student> wrapper = new QueryWrapper<>();
    // select方法就是 select id,name,count(),sum(),min()
    wrapper.select("username,count(*) as count");
    wrapper.groupBy("username");
    List<Map<String, Object>> result = studentService.listMaps(wrapper);
    log.info("result==>{}",result);
}

查询排序

@Test
public void test10() {
    LambdaQueryWrapper<Student> wrapper = new LambdaQueryWrapper<>();
//        wrapper.orderByAsc(Student::getId);
    wrapper.orderByDesc(Student::getId);
    // 不推荐使用,可能存在sql注入风险
    wrapper.last("limit 10");
    List<Student> list = studentService.list(wrapper);
    log.info("list==>{}",list);
}

自定义Sql

此处其实就是mybatis的内容了,将sql写到xml文件中,通过与Mapper接口关联,service调用Mapper中的方法,间接使用xml文件中的sql片段。如果MyBatis-PLus不用了,或者他的sql满足不了我们的业务需求,就需要自定义sql。

10.最佳实践

我们使用MyBatis-Plus主要实现数据库操作的,MyBatis-Plus目前有基础可以优化的地方:
●service没有必要实现MyBatis-Plus提供的,多一层继承没有必要
●实体类有很多相同的属性,我们可以自定一个BaseEntity
●批量插入我们自己实现一下,自定一个Mapper接口

Entity改造

如果你的表中没有这些字段,切记不要继承,其他实体去继承这个BaseEntity就行

package com.zide.entity.base;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * @author 石添
 * @date 2024/7/29
 */
@Data
public class BaseEntity implements Serializable {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    @TableField(fill = FieldFill.INSERT)
    private String creator;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updater;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    // 默认0未删除,1已删除
    @TableLogic
    private Integer deleted;

    @TableField(fill = FieldFill.INSERT)
    @Version
    private Long version;
}

自定义Mapper

其他Mapper继承这个Mapper,并且serivice接口不用继承了以及seriviceIpl也不用继承了

package com.zide.mapper.base;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.toolkit.Db;

import java.util.Collection;

/**
 * 自定义Mapper,其他的Mapper继承我们自定义的这个Mapper
 * 扩充BaseMapper不具备的功能
 * 比如:批量插入,批量更新,可以通过MyBatis-Plus提供的Db工具类实现
 * @param <T>
 */
public interface ZiDeMapper<T> extends BaseMapper<T> {

    // 批量新增
    default boolean saveBatch(Collection<T> collection) {
        // 批量插入
        return Db.saveBatch(collection);
    }

}

11.Mybatis实现多表查询

连表查询小口诀:
1、确定要查询的数据在哪张表中,哪张表是驱动表【核心表】
2、确定其他的关联数据在哪些表中,然后再选择使用左连接/右连接/全连接
3、确定表之间的关联关系,写到ON后边
4、确定要查的数据在哪张表中,确定SELECT子句中写什么,可以适当的取别名

步骤
●创建对应VO封装数据
●创建Mapper接口中的方法和xml文件中的sql
●测试一下

package com.zide.entity.vo;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.Version;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * @author 石添
 * @date 2024/7/30
 */
@Data
public class StudentVO implements Serializable {

    private Long id;

    private String username;

    private String studentNo;

    private String classesName;

    private String majorName;

    private Long majorId;

    private Long classId;

    private String creator;

    private String updater;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;

}
<select id="selectVoList" resultType="com.zide.entity.vo.StudentVO">
    SELECT
        t1.id,t1.username,t1.student_no as studentNo,t1.major_id as majorId ,t1.class_id as classId,t1.creator,t1.updater
         ,t1.create_time as createTime,t1.update_time as updateTime
         ,t2.classes_name as classesName,t3.major_name as majorName
    FROM
        student t1 LEFT JOIN classes t2 ON t1.class_id = t2.id
                   LEFT JOIN major t3 ON t1.major_id = t3.id
        LIMIT 10;
</select>
  1. 使用结果集映射 resultMap 时,属性名和查询到的字段名要一一映射

    • 简单的属性(只是属性名和字段名不一致),直接映射;

    • 复杂的属性

      (如自定义对象、集合)需要单独处理(嵌套处理):

      • 属性是自定义对象:用 association,其中 javaType 为属性中自定义的类
      • 属性是集合:用 collection,其中 ofType 为属性中集合的泛型约束类型。(老师对应多个学生,用集合来装学生对象)
<select id="getAllTeachers" resultMap="teacherResultMap">
    SELECT t.`id` AS '老师id', t.`name` AS '老师姓名', s.`id` AS '学生id', s.`name` AS '学生姓名'
    FROM `teacher` AS t
    INNER JOIN `student` AS s
    ON t.`id` = s.`tid`;
</select>

<resultMap id="teacherResultMap" type="Teacher">
    <result property="id" column="老师id"/>
    <result property="name" column="老师姓名"/>
    <collection property="studentList" ofType="Student">
        <result property="id" column="学生id"/>
        <result property="name" column="学生姓名"/>
    </collection>
</resultMap>
<select id="getAllStudents" resultMap="studentResultMap">
    SELECT s.`id` AS '学生id', s.`name` AS '学生姓名', s.`tid` AS '老师id', t.`name` AS '老师姓名'
    FROM `student` AS s
    INNER JOIN `teacher` AS t
    ON s.`tid` = t.`id`;
</select>

<resultMap id="studentResultMap" type="Student">
    <result property="id" column="学生id"/>
    <result property="name" column="学生姓名"/>
    <!-- 复杂类型是对象,用 association -->
    <association property="teacher" javaType="Teacher">
        <result property="id" column="老师id"/>
        <result property="name" column="老师姓名"/>
    </association>
</resultMap>

12.myBatisPlus实现多表查询

MyBatis-Plus本身不具备多表操作的,引入第三方启动器【依赖】,才可以实现通过Wrapper的方式做连表查询

引入依赖

<dependency>
  <groupId>com.github.yulichang</groupId>
  <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
  <version>1.4.10</version>
</dependency>

Mapper修改

继承MPJBaseMapper而不是BaseMapper,如果自己的Mapper需要集成这些功能的话就可以继承MPJBaseMapper来优化

@Mapper
public interface IStudentMapper extends MPJBaseMapper<Student> {

}

Service

package com.zide.service.impl;

import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.zide.entity.Classes;
import com.zide.entity.Student;
import com.zide.entity.vo.StudentVO;
import com.zide.mapper.IStudentMapper;
import com.zide.service.IStudentService;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author 石添
 * @date 2024/7/28
 */
@Service
public class StudentServiceImpl implements IStudentService {
    private final IStudentMapper studentMapper;

    public StudentServiceImpl(IStudentMapper studentMapper) {
        this.studentMapper = studentMapper;
    }

    /**
     * 实现多表连查
     * @return
     */
    @Override
    public List<StudentVO> selectVoList() {
        // 连表查询,通过MPJLambdaWrapper
        MPJLambdaWrapper<Student> wrapper = new MPJLambdaWrapper<>();
        // 构建查询哪些数据,相当于select t1.id as id
        wrapper.selectAs(Student::getId, StudentVO::getId)
                .selectAs(Student::getUsername, StudentVO::getUsername)
                .selectAs(Student::getStudentNo, StudentVO::getStudentNo)
                .selectAs(Student::getMajorId, StudentVO::getMajorId)
                .selectAs(Student::getClassId, StudentVO::getClassId)
                .selectAs(Student::getCreator, StudentVO::getCreator)
                .selectAs(Student::getUpdateTime, StudentVO::getUpdateTime)
                .selectAs(Student::getCreator, StudentVO::getCreator)
                .selectAs(Student::getUpdater, StudentVO::getUpdater)
                .selectAs(Student::getCreateTime, StudentVO::getCreateTime)
                // join的表是哪个
                .leftJoin(Classes.class,Classes::getId,Student::getClassId) // left join t1.classes_id = t2. id
                // 确定关联关系
                .selectAs(Classes::getClassesName, StudentVO::getClassesName);
        // 继续构建条件
        wrapper.last("limit 10");

        return studentMapper.selectJoinList(StudentVO.class,wrapper);
    }
}

13.集成Druid

依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-3-starter</artifactId>
    <version>1.2.21</version>
</dependency>

配置

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/stt-study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&useServerPrepStmts=true&allowMultiQueries=true
      username: root
      password: stt123456
      # 如果initialSize数量较多时,打开会加快应用启动时间
      async-init: true
      # 连接池初始化时创建的连接数
      initial-size: 5
      # 连接池中最大连接数
      max-active: 50
      # 连接池中最小空闲连接数
      min-idle: 5
      # 连接池中最大空闲连接数
      max-idle: 20
      # 配置获取连接等待超时的时间 毫秒
      max-wait: 6000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      # 增强timeBetweenEvictionRunsMillis的周期性连接检查,minIdle内的空闲连接,每次检查强制验证连接有效性
      keep-alive: true
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      max-evictable-idle-time-millis: 172800000
      # 用于检测连接是否有效的SQL语句
      validation-query: select 1
      validation-query-timeout: 1
      # 是否开启空闲连接的检测
      test-while-idle: true
      # 是否开启连接的检测功能,在获取连接时检测连接是否有效
      test-on-borrow: false
      # 是否开启连接的检测功能,在归还连接时检测连接是否有效
      test-on-return: false
      # 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
      pool-prepared-statements: false

      filter:
        stat:
          enabled: true
          db-type: mysql
          log-slow-sql: true
          slow-sql-millis: 3000
          connection-stack-trace-enable: true
        wall:
          enabled: true
          db-type: mysql
          config:
            insert-allow: true
            delete-allow: false
            update-allow: true
            drop-table-allow: false
      stat-view-servlet:
        # http://localhost:8080/druid/index.html
        # 启用StatViewServlet
        enabled: true
        # 访问内置监控页面的路径,内置监控页面的首页是/druid/index.html
        url-pattern: /druid/*
        # 不允许清空统计数据,重新计算
        reset-enable: false
        # 配置监控页面访问密码
        login-username: root
        login-password: 123456
        # 允许访问的地址,如果allow没有配置或者为空,则允许所有访问
        allow: 127.0.0.1
        # 拒绝访问的地址,deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

多数据源

img

思路
1项目中配置多数据源,并且区分开,一般主数据源称为master、从数据源称为slave_1、slave_2
a自己写
b找现成的框架【工具】
2数据库也要有多个,只是案例,手动去同步一下数据,项目中的话需要做主从复制的

实现多数据源

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
    <version>4.3.0</version>
</dependency>

配置

spring:
  autoconfigure:
    # 忽略Druid的默认配置
    exclude: com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure
  # 数据源配置
  datasource:
    # 动态数据源
    dynamic:
      # 主数据源
      primary: master
      # 是否开启严格模式,开启后,当所有的数据源都没有可用连接时,会抛出异常,阻止系统启动,否则默认使用第一个数据源【主数据源】
      strict: false
      datasource:
        # 数据源的名字,可以自定义
        master:
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/stt-study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&useServerPrepStmts=true&allowMultiQueries=true
          username: root
          password: stt123456
        slave_1:
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/stt-study-slave1?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
          username: root
          password: stt123456
    druid:
      # 如果initialSize数量较多时,打开会加快应用启动时间
      async-init: true
      # 连接池初始化时创建的连接数
      initial-size: 5
      # 连接池中最大连接数
      max-active: 50
      # 连接池中最小空闲连接数
      min-idle: 5
      # 连接池中最大空闲连接数
      max-idle: 20
      # 配置获取连接等待超时的时间 毫秒
      max-wait: 6000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      # 增强timeBetweenEvictionRunsMillis的周期性连接检查,minIdle内的空闲连接,每次检查强制验证连接有效性
      keep-alive: true
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      max-evictable-idle-time-millis: 172800000
      # 用于检测连接是否有效的SQL语句
      validation-query: select 1
      validation-query-timeout: 1
      # 是否开启空闲连接的检测
      test-while-idle: true
      # 是否开启连接的检测功能,在获取连接时检测连接是否有效
      test-on-borrow: false
      # 是否开启连接的检测功能,在归还连接时检测连接是否有效
      test-on-return: false
      # 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
      pool-prepared-statements: false

      filter:
        stat:
          enabled: true
          db-type: mysql
          log-slow-sql: true
          slow-sql-millis: 3000
          connection-stack-trace-enable: true
        wall:
          enabled: true
          db-type: mysql
          config:
            insert-allow: true
            delete-allow: false
            update-allow: true
            drop-table-allow: false
      stat-view-servlet:
        # http://localhost:8080/druid/index.html
        # 启用StatViewServlet
        enabled: true
        # 访问内置监控页面的路径,内置监控页面的首页是/druid/index.html
        url-pattern: /druid/*
        # 不允许清空统计数据,重新计算
        reset-enable: false
        # 配置监控页面访问密码
        login-username: root
        login-password: 123456
        # 允许访问的地址,如果allow没有配置或者为空,则允许所有访问
        allow: 127.0.0.1
        # 拒绝访问的地址,deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

代码应用

通过@DS注解实现

package com.zide.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.zide.entity.Classes;
import com.zide.entity.Student;
import com.zide.entity.vo.StudentVO;
import com.zide.mapper.IStudentMapper;
import com.zide.service.IStudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author 石添
 * @date 2024/7/28
 */
@Slf4j
@Service
public class StudentServiceImpl implements IStudentService {
    private final IStudentMapper studentMapper;

    public StudentServiceImpl(IStudentMapper studentMapper) {
        this.studentMapper = studentMapper;
    }

    /**
     * 查数据去从数据库
     * @return
     */
    @DS("slave_1")
    @Override
    public List<StudentVO> selectVoList() {
        // 连表查询,通过MPJLambdaWrapper
        MPJLambdaWrapper<Student> wrapper = new MPJLambdaWrapper<>();
        // 构建查询哪些数据,相当于select t1.id as id
        wrapper.selectAs(Student::getId, StudentVO::getId)
                .selectAs(Student::getUsername, StudentVO::getUsername)
                .selectAs(Student::getStudentNo, StudentVO::getStudentNo)
                .selectAs(Student::getMajorId, StudentVO::getMajorId)
                .selectAs(Student::getClassId, StudentVO::getClassId)
                .selectAs(Student::getCreator, StudentVO::getCreator)
                .selectAs(Student::getUpdateTime, StudentVO::getUpdateTime)
                .selectAs(Student::getCreator, StudentVO::getCreator)
                .selectAs(Student::getUpdater, StudentVO::getUpdater)
                .selectAs(Student::getCreateTime, StudentVO::getCreateTime)
                // join的表是哪个
                .leftJoin(Classes.class,Classes::getId,Student::getClassId) // left join t1.classes_id = t2. id
                // 确定关联关系
                .selectAs(Classes::getClassesName, StudentVO::getClassesName);
        // 继续构建条件
        wrapper.last("limit 10");

        return studentMapper.selectJoinList(StudentVO.class,wrapper);
    }

    /**
     * 写数据去主数据库
     */
    @Override
    public int saveStudent(StudentVO studentVO) {

        // 存储的时候需要转成Student
        Student insertBean = new Student();
        BeanUtils.copyProperties(studentVO,insertBean);
        log.info("insertBean===>{}",insertBean);
        return studentMapper.insert(insertBean);
    }
}

封装多数据源注解

可以通过自定义注解封装@DS注解实现简化注解参数的目的

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@DS("master")
public @interface Master {
}

r)
.selectAs(Student::getUpdater, StudentVO::getUpdater)
.selectAs(Student::getCreateTime, StudentVO::getCreateTime)
// join的表是哪个
.leftJoin(Classes.class,Classes::getId,Student::getClassId) // left join t1.classes_id = t2. id
// 确定关联关系
.selectAs(Classes::getClassesName, StudentVO::getClassesName);
// 继续构建条件
wrapper.last(“limit 10”);

    return studentMapper.selectJoinList(StudentVO.class,wrapper);
}

/**
 * 写数据去主数据库
 */
@Override
public int saveStudent(StudentVO studentVO) {

    // 存储的时候需要转成Student
    Student insertBean = new Student();
    BeanUtils.copyProperties(studentVO,insertBean);
    log.info("insertBean===>{}",insertBean);
    return studentMapper.insert(insertBean);
}

}


### 封装多数据源注解

可以通过自定义注解封装@DS注解实现简化注解参数的目的

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@DS(“master”)
public @interface Master {
}


  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值