一、MyBatis-Plus简介
1、知识架构
2、简介
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
3、特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
4、支持数据库
- MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
- 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库
5、框架结构
6、代码及文档地址
官方地址: http://mp.baomidou.com
代码发布地址:
Github: https://github.com/baomidou/mybatis-plus
Gitee: https://gitee.com/baomidou/mybatis-plus
文档发布地址: https://baomidou.com/pages/24112f
二、入门案例
1、开发环境
- IDE:idea 2019.2
- JDK:JDK8+
- 构建工具:maven 3.5.4
- MySQL版本:MySQL 5.7
- Spring Boot:2.6.3
- MyBatis-Plus:3.5.1
2、创建数据库及表
a>创建表
CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; use `mybatis_plus`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
b>添加数据
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');
3、创建Spring Boot工程
a>初始化工程
使用 Spring Initializr 快速初始化一个 Spring Boot 工程
b>引入依赖
<dependencies>
<!--mybatis-plus场景启动器依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--lomback依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--数据库驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--默认添加的依赖:spring-boot场景启动器依赖,使用插件创建springboot项目自动添加的依赖。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--默认添加的依赖:spring-boot测试功能的场景启动器依赖,使用插件创建springboot项目自动添加的依赖。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
c>idea中安装lombok插件
4、编写代码
a>方式一:配置application.properties
# 配置数据源的类型,springboot默认的数据源就是HikariDataSource
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
# 配置连接数据库信息:
# 1.配置数据库驱动类:和mysql的启动类版本有关
# 1.mysql驱动类是5版本的(com.mysql.jdbc.Driver),spring boot 2.0(内置jdbc5驱动)
# 2.mysql驱动类是8版本的(com.mysql.cj.jdbc.Driver),spring boot 2.1及以上(内置jdbc8驱动)
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 2.配置连接数据库的url地址:和当前mysql的版本有关
# 1.MySQL5.7版本的url (需要添加参数:操作数据库的编码格式、显式禁用SSL):
# jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
# 2.MySQL8.0版本的url:(需要添加的参数:操作数据库的编码格式、显式禁用SSL、时区)
# jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
# 配置用户名
spring.datasource.username=root
# 配置密码
spring.datasource.password=root
b>方式二:配置application.yml
spring:
# 配置数据源信息
datasource:
# 配置数据源类型
type: com.zaxxer.hikari.HikariDataSource
# 配置连接数据库信息
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
username: root
password: root
注意:
c>启动类
package com.atguigu.mybatisplus;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.atguigu.mybatisplus.mapper") //扫描mapper接口所在的包
public class MybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisplusApplication.class, args);
}
}
d>添加实体
package com.atguigu.mybatisplus.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data //有:无参构造,get,set,equals,hashcode,toString。 没有:有参构造。
@AllArgsConstructor //有参构造
@NoArgsConstructor //添加了有参构造后,默认的无参构造不在提供,需要手动添加
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
User类编译之后的结果:
e>添加mapper
package com.atguigu.mybatisplus.mapper;
import com.atguigu.mybatisplus.pojo.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
//1.mapper接口继承BaseMapper接口
//2.添加泛型,泛型为操作的实体类类型
public interface UserMapper extends BaseMapper<User>{
}
f>测试
package com.atguigu.mybatisplus;
import com.atguigu.mybatisplus.mapper.UserMapper;
import com.atguigu.mybatisplus.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest //添加完此注解后,就可以对springIoc所管理的一些组件进行自动装配了。
public class MyBatisPlusTest {
//说明:因为添加的有@MapperScan注解,所以会把mapper接口所动态生成的代理类交给ioc容器来管理,此时就可以直接注入了。
@Autowired
UserMapper userMapper;
@Test
public void testSelectList(){
//通过条件构造器查询一个list集合,若没有条件,则可以设置null为参数,表示查询所有数据
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
}
结果:
注意:
g>添加日志
在application.yml中配置日志输出
# 配置MyBatis自带的日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
三、基本CRUD
1、BaseMapper
MyBatis-Plus中的基本CRUD在内置的BaseMapper中都已得到了实现,我们可以直接使用,接口如下:
alt+7:查看当前类的结构。
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)
</p>
*
根据 entity 条件,查询一条记录
*<p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常
*@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、插入(1个)
@Test
public void testInsert(){
//实现新增用户信息
//INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
User user = new User();
user.setName("张三");
user.setAge(23);
user.setEmail("zhangsan@atguigu.com");
int result = userMapper.insert(user);
System.out.println("result:"+result);//result:1
//说明:1.mybatis-plus可以自动获取插入数据的主键自增的id
// 2.mybatis-plus默认是用的是雪花算法生成的id,所以创建表时的id为bigint类型,实体类为long类型
System.out.println("id:"+user.getId());//id:1513145144151375874
}
3、删除(5个)
说明:暂时先讲3个,剩下的到后面讲解。
a>通过id删除记录
@Test
public void testDeleteById(){
//通过id删除用户信息
//DELETE FROM user WHERE id=?
int result = userMapper.deleteById(1513145144151375874L);
System.out.println("受影响行数:"+result);
}
b>通过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);
}
c>通过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、修改(2个)
说明:条件构造器到后面讲解。
4.1、根据id进行修改
@Test
public void testUpdateById(){
//根据id进行修改
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、查询
a>根据id查询用户信息
@Test
public void testSelectById(){
//根据id查询用户信息
//SELECT id,name,age,email FROM user WHERE id=?
User user = userMapper.selectById(4L);
System.out.println(user);
}
b>根据多个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);
}
c>通过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);
}
d>查询所有数据
@Test
public void testSelectList02(){
//查询所有用户信息
//SELECT id,name,age,email FROM user
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
6、自定义mapper接口
说明:
- 如果MyBatis-Plus中的BaseMapper接口提供的方法不满足sql的使用,也可以使用之前mybatis中自己写sql的方式。
- MyBatis-Plus的默认的映射文件位置:
classpath*:/mapper/**/*.xml
6.1、mapper接口
/**
* 根据id查询用户信息为map集合
* @param id
* @return
*/
Map<String, Object> selectMapById(Long id);
6.2、映射文件
<?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.atguigu.mybatisplus.mapper.UserMapper">
<!--Map<String, Object> selectMapById(Long id);-->
<select id="selectMapById" resultType="map">
select id,name,age,email from user where id = #{id}
</select>
</mapper>
7、通用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
a>IService
MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑详情查看源码IService和ServiceImpl
说明:
- 类似于使用BaseMapper接口中的方法不能满足所有的SQL语句,同样ServiceImpl类里面提供的方法同样不能满足所有的业务需求。
- 在使用上类似于mapper接口,我们自定义的mappper接口实现BaseMapper接口,此时既可以使用BaseMapper接口提供的方法也可以使用自定义的方法。
b>创建Service接口和实现类
package com.atguigu.mybatisplus.service;
import com.atguigu.mybatisplus.pojo.User;
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {
}
------------------------------------
package com.atguigu.mybatisplus.service.impl;
import com.atguigu.mybatisplus.mapper.UserMapper;
import com.atguigu.mybatisplus.pojo.User;
import com.atguigu.mybatisplus.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
//说明:如果单单只是implements UserService接口,因为UserService extends IService<User>接口,
// 所以此时所有的IService接口中的方法都要在实现类中重写,而我们有用不到那么多的方法,此时就可以使用
// IService的自己的实现类ServiceImpl即可,泛型:操作当前mapper接口的类型、操作当前实体类的类型
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
c>测试查询记录数
package com.atguigu.mybatisplus;
import com.atguigu.mybatisplus.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MyBatisPlusServiceTest {
@Autowired
UserServiceImpl userService;
@Test
public void testGetCount(){
//查询总记录数
//SELECT COUNT( * ) FROM user
long count = userService.count();
System.out.println("总记录数:"+count);
}
}
d>测试批量插入
@Test
public void testInsertMore(){
//批量添加
//注意:执行的sql是单个sql语句进行循环添加,来完成批量添加的操作。
//INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? )
List<User> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
User user = new User();
user.setName("ybc"+i);
user.setAge(20+i);
list.add(user);
}
boolean b = userService.saveBatch(list);
System.out.println(b);
}
四、常用注解
1、@TableName
- 经过以上的测试,在使用MyBatis-Plus实现基本的CRUD时,我们并没有指定要操作的表,只是在Mapper接口继承BaseMapper时,设置了泛型User,而操作的表为user表
- 由此得出结论,MyBatis-Plus在确定操作的表时,由BaseMapper的泛型决定,即实体类型决定,且默认操作的表名和实体类型的类名一致
a>问题
b>方式一:通过@TableName解决问题
c>方式二:通过全局配置解决问题
# 配置MyBatis自带的日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 设置MyBatis-Plus的全局配置
global-config:
db-config:
# 设置实体类所对应的表的统一前缀
table-prefix: t_
2、@TableId
a>问题
b>通过@TableId解决问题
在实体类中uid属性上通过@TableId将其标识为主键,即可成功执行SQL语句
c>@TableId的value属性
解决:
d>@TableId的type属性
值 | 描述 |
---|---|
IdType.ASSIGN_ID(默认) | 基于雪花算法的策略生成数据id,与数据库id是否设置自增无关 |
IdType.AUTO | 使用数据库的自增策略,注意,该类型请确保数据库设置了id自增, 否则无效 |
方式一:在 @TableId注解上添加type属性
注意:如果手动设置了主键id,则雪花算法不会生效。
方式二:配置全局主键策略:即 如果有很多实体类都需要配置主键生成策略,一个个的配置太麻烦,可以进行全局配置。
# 配置MyBatis自带的日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 设置MyBatis-Plus的全局配置
global-config:
db-config:
# 设置实体类所对应的表的统一前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
e>雪花算法
-
背景
需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。
数据库的扩展方式主要包括:业务分库、主从复制,数据库分表。 -
数据库分表
将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务 继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据, 如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进 行拆分。
单表数据拆分有两种方式:垂直分表和水平分表。示意图如下:
-
垂直分表
垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。
例如,前面示意图中的 nickname 和 description 字段,假设我们是一个婚恋网站,用户在筛选其他用户的时候,主要是用 age 和 sex 两个字段进行查询,而 nickname 和 description 两个字段主要用于展示,一般不会在业务查询中用到。description 本身又比较长,因此我们可以将这两个字段独立到另外一张表中,这样在查询 age 和 sex 时,就能带来一定的性能提升。 -
水平分表
水平分表适合表行数特别大的表,有的公司要求单表行数超过 5000 万就必须进行分表,这个数字可以
作为参考,但并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过 1000
万就要分表了;而对于一些简单的表,即使存储数据超过 1 亿行,也可以不分表。
但不管怎样,当看到表的数据量达到千万级别时,作为架构师就要警觉起来,因为这很可能是架构的性 能瓶颈或者隐患。
水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理
主键自增
① 以最常见的用户 ID 为例,可以按照 1000000 的范围大小进行分段,1 ~ 999999 放到表 1中, 1000000 ~ 1999999 放到表2中,以此类推。
② 复杂点:分段大小的选取。分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会 导致单表依然存在性能问题,一般建议分段大小在 100 万至 2000 万之间,具体需要根据业务选取合适的分段大小。
③ 优点:可以随着数据的增加平滑地扩充新的表。例如,现在的用户是 100 万,如果增加到 1000 万, 只需要增加新的表就可以了,原有的数据不需要动。
④ 缺点:分布不均匀。假如按照 1000 万来进行分表,有可能某个分段实际存储的数据量只有 1 条,而
另外一个分段实际存储的数据量有 1000 万条。
取模
① 同样以用户 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)。
②优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。
3、@TableField
a>情况1
说明:nybatis-plus默认配置了驼峰命名规则。
b>情况2
4、@TableLogic
说明:逻辑删除指的是假删除,在表中设置一个表示删除状态的字段,比如设置默认值为0表示未删除状态,当我们进行逻辑删除时就会把这个字段改为1,那么从此之后我们从这张表中查询出来的数据应该都是未删除状态的数据。
a>逻辑删除
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
- 使用场景:可以进行数据恢复
b>实现逻辑删除
说明1:删除功能会变成修改操作。
说明2:此时查询出的数据都是未删除状态的数据。
五、条件构造器和常用接口(重听)
说明:条件构造器是用来封装当前sql的条件的。
1、wapper介绍
- Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询条件封装(查询,删除)
- UpdateWrapper : Update 条件封装 (修改)
- AbstractLambdaWrapper : 使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper(查询,删除)
- LambdaUpdateWrapper : Lambda 更新封装Wrapper(修改)
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
2、QueryWrapper
a>例1:组装查询条件
package com.atguigu.mybatisplus;
import com.atguigu.mybatisplus.mapper.UserMapper;
import com.atguigu.mybatisplus.pojo.User;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class MyBatisPlusWrapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void test01(){
//查询用户名包含a,年龄在20到30之间,邮箱信息不为null的用户信息
//SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//注意:参数1是数据库表的字段名
queryWrapper.like("user_name", "a")
.between("age", 20, 30)
.isNotNull("email");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
}
b>例2:组装排序条件
@Test
public void test02(){
//查询用户信息,按照年龄的降序排序,若年龄相同,则按照id升序排序
//SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,uid ASC
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("uid");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
c>例3:组装删除条件
@Test
public void test03(){
//删除邮箱地址为null的用户信息
//UPDATE t_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("result:"+result);
}
d>例4:使用queryWrapper实现修改功能
@Test
public void test04(){
//将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改
//UPDATE t_user SET user_name=?, email=? WHERE is_deleted=0 AND (age > ? AND user_name LIKE ? OR email IS NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age", 20)
.like("user_name", "a")
.or()
.isNull("email");
User user = new User();
user.setName("小明");
user.setEmail("test@atguigu.com");
int result = userMapper.update(user, queryWrapper);
System.out.println("result:"+result);
}
d>例4:条件的优先级
@Test
public void test05(){
//将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
//lambda中的条件优先执行
//UPDATE t_user SET user_name=?, email=? WHERE is_deleted=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL))
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("user_name", "a")
.and(i->i.gt("age",20).or().isNull("email"));
User user = new User();
user.setName("小红");
user.setEmail("test@atguigu.com");
int result = userMapper.update(user, queryWrapper);
System.out.println("result:"+result);
}
e>例5:组装select子句
@Test
public void test06(){
//查询用户的用户名、年龄、邮箱信息
//SELECT user_name,age,email FROM t_user WHERE is_deleted=0
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("user_name", "age", "email");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
f>例6:组装查询
@Test
public void test07(){
//查询id小于等于100的用户信息
//SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (uid IN (select uid from t_user where uid <= 100))
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("uid", "select uid from t_user where uid <= 100");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
3、通过UpdateWrapper实现修改功能
@Test
public void test08(){
//将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.like("user_name", "a")
.and(i -> i.gt("age", 20).or().isNull("email"));
updateWrapper.set("user_name", "小黑").set("email","abc@atguigu.com");
int result = userMapper.update(null, updateWrapper);
System.out.println("result:"+result);
}
4、condition
思路一:
@Test
public void test09(){
//SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND age <= ?)
String username = "a";
Integer ageBegin = null;
Integer ageEnd = 30;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if(StringUtils.isNotBlank(username)){
//isNotBlank判断某个字符创是否不为空字符串、不为null、不为空白符
queryWrapper.like("user_name", username);
}
if(ageBegin != null){
queryWrapper.ge("age", ageBegin);
}
if(ageEnd != null){
queryWrapper.le("age", ageEnd);
}
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
思路二:
@Test
public void test10(){
String username = "a";
Integer ageBegin = null;
Integer ageEnd = 30;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username), "user_name", username)
.ge(ageBegin != null, "age", ageBegin)
.le(ageEnd != null, "age", ageEnd);
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
5、LambdaQueryWrapper
@Test
public void test11(){
//SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND age <= ?)
String username = "a";
Integer ageBegin = null;
Integer ageEnd = 30;
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username), User::getName, username)
.ge(ageBegin != null, User::getAge, ageBegin)
.le(ageEnd != null, User::getAge, ageEnd);
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
6、LambdaUpdateWrapper
@Test
public void test12(){
//将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.like(User::getName, "a")
.and(i -> i.gt(User::getAge, 20).or().isNull(User::getEmail));
updateWrapper.set(User::getName, "小黑").set(User::getEmail,"abc@atguigu.com");
int result = userMapper.update(null, updateWrapper);
System.out.println("result:"+result);
}
六、插件
1、分页插件
a>添加配置类
package com.atguigu.mybatisplus.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.atguigu.mybatisplus.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
b>测试
package com.atguigu.mybatisplus;
import com.atguigu.mybatisplus.mapper.UserMapper;
import com.atguigu.mybatisplus.pojo.User;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MyBatisPlusPluginsTest {
@Autowired
private UserMapper userMapper;
@Test
public void testPage(){
//泛型为实体类类型 参数:当前页的页码,每页显示的条数
Page<User> page = new Page<>(1, 3);
//参数:page对象,条件构造器。 查询的是所有数据进行的分页,条件构造器为空。
userMapper.selectPage(page, null);
System.out.println(page.getRecords());//获取当前分页数据
System.out.println(page.getPages());//获取总页数
System.out.println(page.getTotal());//获取总记录数
System.out.println(page.hasNext());//判断是否有下一页
System.out.println(page.hasPrevious());//判断是否有上一页
}
}
2、xml自定义分页
说明:当前写的查询sql是自定义的,现在需要在自己写的sql语句中通过分页插件实现分页功能,这个时候该如何实现呢???
a>UserMapper中定义接口方法
/**
* 通过年龄查询用户信息并分页
* @param page MyBatis-Plus所提供的分页对象,必须位于第一个参数的位置
* @param age
* @return
*/
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
b>配置返回值resultType的别名
# 配置MyBatis自带的日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 设置MyBatis-Plus的全局配置
global-config:
db-config:
# 设置实体类所对应的表的统一前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
# 配置类型别名所对应的包
type-aliases-package: com.atguigu.mybatisplus.pojo
c>UserMapper.xml中编写SQL
<!--Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);-->
<!--说明:现在仍然是使用分页插件来实现分页功能,所以说我们自己的写的sql语句不需要写分页功能-->
<!--resultType:写的是user的全称限定类名,这里写的是类型别名原因是在配置文件中配置了类型别名。-->
<select id="selectPageVo" resultType="User">
select uid,user_name,age,email from t_user where age > #{age}
</select>
d>测试
@Test
public void testPageVo(){
//泛型为实体类类型 参数:当前页的页码,每页显示的条数
Page<User> page = new Page<>(1, 3);
//参数:page对象,条件构造器。 查询的是所有数据进行的分页,条件构造器为空。
userMapper.selectPage(page, null);
System.out.println(page.getRecords());//获取当前分页数据
System.out.println(page.getPages());//获取总页数
System.out.println(page.getTotal());//获取总记录数
System.out.println(page.hasNext());//判断是否有下一页
System.out.println(page.hasPrevious());//判断是否有上一页
}
3、乐观锁
a>场景
b>乐观锁与悲观锁
- 乐观锁解决:表现的比较乐观,通过表中设置一个字段表示版本号来实现,每次用户更新版本号也会随之改变, 比如每次更新版本号都加上1。如:小李和小王同时操作获取的数据都是100,最初这个商品数据的版本号为1 ,此时小李获取最初的数据:100 ,版本号:1,更新后的数据变为数据:150,版本号:2。
那么小王在开始操作数据的时候获取的最初值是:数据:100,版本号:1,小王在进行更新时除了要更新数据之外还要把最初获得的版本号:1,作为条件进行匹配,由于小李操作完数据后版本号变为了2,所以此时就不能匹配到数据进行修改了。 - 悲观锁解决:表现得比较悲观,把数据加上锁后,小李在操作这个数据的时候小王一直处于阻塞状态,直到小李把数据更新完成后小王才能获取这个数据并进行操作。
c>模拟修改冲突
数据库中增加商品表
CREATE TABLE t_product (
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
添加数据
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
添加实体
package com.atguigu.mybatisplus.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
添加mapper
public interface ProductMapper extends BaseMapper<Product> {
}
测试
@Autowired
private ProductMapper productMapper;
@Test
public void testProduct01(){
//小李查询商品价格
Product productLi = productMapper.selectById(1);
System.out.println("小李查询的商品价格:"+productLi.getPrice());
//小王查询商品价格
Product productWang = productMapper.selectById(1);
System.out.println("小王查询的商品价格:"+productWang.getPrice());
//小李将商品价格+50
productLi.setPrice(productLi.getPrice()+50);
productMapper.updateById(productLi);//参数为Product实体类对象
//小王将商品价格-30
productWang.setPrice(productWang.getPrice()-30);
int result = productMapper.updateById(productWang);
//老板查询商品价格
Product productLaoban = productMapper.selectById(1);
//价格覆盖,最后的结果:70
System.out.println("老板查询的商品价格:"+productLaoban.getPrice());
}
d>乐观锁实现流程
SELECT id,`name`,price,`version` FROM product WHERE id=1
UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1
e>Mybatis-Plus实现乐观锁
修改实体类
package com.atguigu.mybatisplus.pojo;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
private Long id;
private String name;
private Integer price;
@Version //标识乐观锁版本号字段
private Integer version;
}
添加乐观锁插件配置
package com.atguigu.mybatisplus.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
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.atguigu.mybatisplus.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
测试修改冲突
优化流程
@Autowired
private ProductMapper productMapper;
@Test
public void testProduct01(){
//小李查询商品价格
Product productLi = productMapper.selectById(1);
System.out.println("小李查询的商品价格:"+productLi.getPrice());
//小王查询商品价格
Product productWang = productMapper.selectById(1);
System.out.println("小王查询的商品价格:"+productWang.getPrice());
//小李将商品价格+50
productLi.setPrice(productLi.getPrice()+50);
productMapper.updateById(productLi);//参数为Product实体类对象
//小王将商品价格-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);
//价格覆盖,最后的结果:70
System.out.println("老板查询的商品价格:"+productLaoban.getPrice());
}
七、通用枚举
a>数据库表添加字段sex
说明:之所以把性别sex设置为int类型,是为了把表示性别的1和2存储到数据库中。一般情况下性别来保存的时候,保存的是一个数值,比如:1和2表示男和女。
b>创建通用枚举类型
package com.atguigu.mybatisplus.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
@Getter //因为枚举类里面存的是常量,所以只需要设置get方法不需要设置set方法。
public enum SexEnum {
//jdk1.5使用enum关键字创建枚举类,提供枚举类对象的方式
MALE(1, "男"),
FEMALE(2, "女");
@EnumValue //将注解所标识的属性的值存储到数据库中
private Integer sex;
private String sexName;
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
c>配置扫描通用枚举
# 配置MyBatis自带的日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 设置MyBatis-Plus的全局配置
global-config:
db-config:
# 设置实体类所对应的表的统一前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
# 配置类型别名所对应的包
type-aliases-package: com.atguigu.mybatisplus.pojo
# 扫描通用枚举的包
type-enums-package: com.atguigu.mybatisplus.enums
d>测试
package com.atguigu.mybatisplus;
import com.atguigu.mybatisplus.enums.SexEnum;
import com.atguigu.mybatisplus.mapper.UserMapper;
import com.atguigu.mybatisplus.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 MyBatisPlusEnumTest {
@Autowired
private UserMapper userMapper;
@Test
public void test(){
User user = new User();
user.setName("admin");
user.setAge(33);
//此时:数据库的保存性别的类型是int,而现在存入的是枚举类型,所以会报错
// 解决:mybatis-plus提供的有直接实现功能的方法:在枚举类的属性上添加@EnumValue注解,在配置文件中添加扫描通用枚举的包
user.setSex(SexEnum.MALE);
int result = userMapper.insert(user);
System.out.println("result:"+result);
}
}
八、代码生成器
说明:mybatis的逆行工程是通过数据库中的表逆向生成:实体类、对应的mapper接口、对应的映射文件。而mybatis-plus的代码生成器同样是通过数据库中的表来生成,只不过生成的更多:控制层、业务层、持久层、实体类、对应的mapper接口、对应的映射文件。
1、引入依赖
<!--mybatis-plus逆向工程的代码生成器的核心依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<!--因为当前实现的功能会生成freemarker引擎模板,所以需要一个freemarker相关的依赖-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
2、快速生成
package com.atguigu.mybatisplus;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;
public class FastAutoGeneratorTest {
public static void main(String[] args) {
//连接数据库的信息
FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus?characterEncoding=utf-8&userSSL=false", "root", "root")
.globalConfig(builder -> { //进行全局配置
builder.author("atguigu") // 设置作者
//.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件,重新生成代码
.outputDir("D://mybatis_plus"); // 指定输出目录
})
.packageConfig(builder -> { //设置当前的包
builder.parent("com.atguigu") // 设置父包名
.moduleName("mybatisplus") // 设置父包模块名,也就是说当前生成的所有内容都在com.atguigu.mybatisplus包下。
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> { //策略配置
builder.addInclude("t_user") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
之后会自动打开生成的项目:
九、多数据源
1、创建数据库及表
CREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `mybatis_plus_1`;
CREATE TABLE product (
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
use mybatis_plus;
DROP TABLE IF EXISTS product;
2、引入依赖
创建一个新的工程:
除了之前的依赖,此外需要添加多数据源的依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
3、配置多数据源
spring:
# 配置数据源信息
datasource:
dynamic:
# 设置默认的数据源或者数据源组,默认值即为master
primary: master
# 严格匹配数据源:
# false(默认):如果当前匹配不到数据源的时候它会使用当前的默认数据源
# true:未匹配到指定数据源时抛异常
strict: false
datasource:
#主数据源的信息,名字和primary的值保持一致。
master:
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
#从数据源的信息
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
4、创建实体类
User:
package com.atguigu.mybatisplus.pojo;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* Date:2022/2/15
* Author:ybc
* Description:
*/
@Data
@TableName("t_user")
public class User {
@TableId
private Integer uid;
private String userName;
private Integer age;
private Integer sex;
private String email;
private Integer isDeleted;
}
Product:
package com.atguigu.mybatisplus.pojo;
import lombok.Data;
/**
* Date:2022/2/15
* Author:ybc
* Description:
*/
@Data
public class Product {
private Integer id;
private String name;
private Integer price;
private Integer version;
}
5、创建mapper接口
UserMapper:
package com.atguigu.mybatisplus.mapper;
import com.atguigu.mybatisplus.pojo.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface UserMapper extends BaseMapper<User> {
}
ProductMapper:
package com.atguigu.mybatisplus.mapper;
import com.atguigu.mybatisplus.pojo.Product;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface ProductMapper extends BaseMapper<Product> {
}
6、添加包扫描
7、创建service
UserService接口
package com.atguigu.mybatisplus.service;
import com.atguigu.mybatisplus.pojo.User;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* Date:2022/2/15
* Author:ybc
* Description:
*/
public interface UserService extends IService<User> {
}
UserServiceImpl实现类:
package com.atguigu.mybatisplus.service.impl;
import com.atguigu.mybatisplus.mapper.UserMapper;
import com.atguigu.mybatisplus.pojo.User;
import com.atguigu.mybatisplus.service.UserService;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* Date:2022/2/15
* Author:ybc
* Description:
*/
@Service
@DS("master")//指定所操作的数据源
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
ProductService接口
package com.atguigu.mybatisplus.service;
import com.atguigu.mybatisplus.pojo.Product;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* Date:2022/2/15
* Author:ybc
* Description:
*/
public interface ProductService extends IService<Product> {
}
ProductServiceImpl实现类:
package com.atguigu.mybatisplus.service.impl;
import com.atguigu.mybatisplus.mapper.ProductMapper;
import com.atguigu.mybatisplus.pojo.Product;
import com.atguigu.mybatisplus.service.ProductService;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* Date:2022/2/15
* Author:ybc
* Description:
*/
@Service
@DS("slave_1")//指定所操作的数据源
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
}
8、测试
package com.atguigu.mybatisplus;
import com.atguigu.mybatisplus.service.ProductService;
import com.atguigu.mybatisplus.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MybatisPlusDatasourceApplicationTests {
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Test
public void test(){
System.out.println(userService.getById(1));
System.out.println(productService.getById(1));
}
}
十、MyBatisX插件
1、安装MyBatisX插件
功能1:快速定位到mapper接口所对应的映射文件以及映射文件所对应的接口。
2、MyBatisX代码快速生成
创建一个新的工程:
添加依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
配置数据源信息:
spring:
# 配置数据源信息
datasource:
# 配置数据源类型
type: com.zaxxer.hikari.HikariDataSource
# 配置连接数据库信息
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
username: root
password: root
高版本的驱动可能会报错,可以选择一个低版本的驱动。
3、MyBatisX快速生成CRUD(1)
package com.atguigu.mybatisx.mapper;
import com.atguigu.mybatisx.pojo.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @Entity com.atguigu.mybatisx.pojo.User
*/
public interface UserMapper extends BaseMapper<User> {
//只需要写方法名,MyBatisX就可以帮助我们快速的生成对应的sql语句
// 注意:方法要见名之意,比如:插入就要以insert开头。
// 带小鸟图标的就是MyBatisX为我们提供的模板
int insertSelective(User user);
}
4、MyBatisX快速生成CRUD(2)
同样的步骤生成其它的方法:
package com.atguigu.mybatisx.mapper;
import com.atguigu.mybatisx.pojo.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @Entity com.atguigu.mybatisx.pojo.User
*/
public interface UserMapper extends BaseMapper<User> {
//只需要写方法名,MyBatisX就可以帮助我们快速的生成对应的sql语句
// 注意:方法要见名之意,比如:插入就要以insert开头。
// 带小鸟图标的就是MyBatisX为我们提供的模板
int insertSelective(User user);
int deleteByUidAndUserName(@Param("uid") Long uid, @Param("userName") String userName);
int updateAgeAndSexByUid(@Param("age") Integer age, @Param("sex") Integer sex, @Param("uid") Long uid);
List<User> selectAgeAndSexByAgeBetween(@Param("beginAge") Integer beginAge, @Param("endAge") Integer endAge);
List<User> selectAllOrderByAgeDesc();
}
接口对应的映射文件:
<?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.atguigu.mybatisx.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.atguigu.mybatisx.pojo.User">
<id property="uid" column="uid" jdbcType="BIGINT"/>
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="email" column="email" jdbcType="VARCHAR"/>
<result property="sex" column="sex" jdbcType="INTEGER"/>
<result property="isDeleted" column="is_deleted" jdbcType="INTEGER"/>
</resultMap>
<sql id="Base_Column_List">
uid,user_name,age,
email,sex,is_deleted
</sql>
<insert id="insertSelective">
insert into t_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="uid != null">uid,</if>
<if test="userName != null">user_name,</if>
<if test="age != null">age,</if>
<if test="email != null">email,</if>
<if test="sex != null">sex,</if>
<if test="isDeleted != null">is_deleted,</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="uid != null">#{uid,jdbcType=BIGINT},</if>
<if test="userName != null">#{userName,jdbcType=VARCHAR},</if>
<if test="age != null">#{age,jdbcType=INTEGER},</if>
<if test="email != null">#{email,jdbcType=VARCHAR},</if>
<if test="sex != null">#{sex,jdbcType=INTEGER},</if>
<if test="isDeleted != null">#{isDeleted,jdbcType=INTEGER},</if>
</trim>
</insert>
<delete id="deleteByUidAndUserName">
delete from t_user
where
uid = #{uid,jdbcType=NUMERIC}
AND user_name = #{userName,jdbcType=VARCHAR}
</delete>
<update id="updateAgeAndSexByUid">
update t_user
set age = #{age,jdbcType=NUMERIC},
sex = #{sex,jdbcType=NUMERIC}
where
uid = #{uid,jdbcType=NUMERIC}
</update>
<select id="selectAgeAndSexByAgeBetween" resultMap="BaseResultMap">
select age, sex
from t_user
where
age between #{beginAge,jdbcType=INTEGER} and #{endAge,jdbcType=INTEGER}
</select>
<select id="selectAllOrderByAgeDesc" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from t_user
order by age desc
</select>
</mapper>