关键步骤:导入mybatis-plus-boot-starter依赖,dao继承BaseMapper<实体类名>
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user (
id bigint(20) primary key auto_increment,
name varchar(32) not null,
password varchar(32) not null,
age int(3) not null ,
tel varchar(32) not null
);
insert into user values(1,'Tom','tom',3,'18866668888');
insert into user values(2,'Jerry','jerry',4,'16688886666');
insert into user values(3,'Jock','123456',41,'18812345678');
insert into user values(4,'传智播客','itcast',15,'4006184000');
说明:由于MP并未被收录到idea的系统内置配置,无法直接选择加入,需要手动在pom.xml中配置添加
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
druid数据源可以加也可以不加,SpringBoot有内置的数据源,可以配置成使用Druid数据源
从MP的依赖关系可以看出,通过依赖传递已经将MyBatis与MyBatis整合Spring的jar包导入,我们不需要额外在添加MyBatis的相关jar包
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
#serverTimezone是用来设置时区,UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为Asia/Shanghai
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: root
public class User {
private Long id; //注意id类型是Long
private String name;
private String password;
private Integer age;
private String tel;
//setter...getter...toString
}
@Mapper
public interface UserDao extends BaseMapper<User>{
}
@SpringBootTest
class MybatisplusQuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll() {
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}
@SpringBootTest
class MybatisplusQuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave(){
User user = new User();
user.setName("zhangsan");
user.setPassword("123");
user.setAge(18);
user.setTel("123321");
userDao.insert(user);
}
@Test
void testDelete(){
userDao.deleteById(2L);
}
@Test
void testUpdate(){
User user = new User();
user.setId(1L);
user.setName("Tom666");
user.setPassword("tom666");
userDao.updateById(user);
}
@Test
void testSelectById(){
System.out.println(userDao.selectById(3L));
}
@Test
void testSelectAll(){
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}
新增insert:
删除deleteById:
修改uodateById:
根据id查询selectById:
查询全部selectList:
问题:新增的时候发现id是一个很长的数据
解决方案:修改id主键自增策略(两种方式)
方式一:这里主键ID是雪花算法ASSIGN_ID策略生成的,想改成自增在实体类id上注解
@TableId(type = IdType.AUTO)
方式二:配置类yml全局配置id策略ASSIGN_ID:
mybatis-plus:
global-config:
db-config:
id-type: assign_id
AUTO策略:数据库默认自增策略
NONE: 不设置id生成策略
INPUT:用户手工输入id,如果id为null会报错
ASSIGN_ID:雪花算法生成id(可兼容数值型与字符串型),mp默认id策略
ASSIGN_UUID:以UUID生成算法作为id生成策略
其他的几个策略均已过时,都将被ASSIGN_ID和ASSIGN_UUID代替
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
注意:版本可以不用写,因为SpringBoot中已经管理了lombok的版本。
//注解为配置类@Configuration,也可以在引导类@Import({MybatisPlusConfig.class})
@Configuration
public class MybatisPlusConfig {
//被Spring容器管理
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1 创建mp拦截器对象MybatisPlusInterceptor
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
//2 添加内置拦截器,参数为分页内置拦截器对象PaginationInnerInterceptor
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
//IPage:用来构建分页查询条件
//Wrapper:译为包装器,封装器。用来构建条件查询的条件,目前我们没有可直接传为Null
IPage是一个接口,我们需要找到它的实现类来构建它,具体的实现类,可以进入到IPage类中按ctrl+h,会找到其有一个实现类为Page。
@Test
void testSelectPage(){
//1 创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数
IPage<User> page=new Page<>(1,3);
//2 执行分页查询
userDao.selectPage(page,null);
//3 获取分页结果,不配置拦截器就只能获取到current和size的数据,pages,total都是0,records是全部数据
System.out.println("当前页码值:"+page.getCurrent());
System.out.println("每页显示数:"+page.getSize());
System.out.println("一共多少页:"+page.getPages());
System.out.println("一共多少条数据:"+page.getTotal());
System.out.println("数据:"+page.getRecords());
}
mybatis-plus:
configuration:
#打印SQL日志到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
两种方式各有优劣:
QueryWrapper存在属性名写错的危险,但是支持聚合、分组查询;
LambdaQueryWrapper没有属性名写错的危险,但不支持聚合、分组查询
基本比较操作
排除控制台日志打印、banner、log
解决控制台打印日志过多的相关操作可以不用去做,一般会被用来方便我们查看程序运行的结果
<configuration>
configuration>
#mybatis-plus日志控制台输出
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: off 关闭mybatisplus启动图标
spring:
main:
banner-mode: off #关闭SpringBoot启动图标(banner)
@Test
void testGetAll(){
QueryWrapper qw = new QueryWrapper();
//SELECT id,name,password,age,tel FROM user WHERE (age < ?)
qw.lt("age",18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
写条件的时候,容易出错,比如age写错,就会导致查询不成功
@Test
void testGetAll(){
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.lambda().lt(User::getAge,18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
注意:构建LambdaQueryWrapper的时候泛型不能省。
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge,18);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//SELECT id,name,password,age,tel FROM user WHERE (age < ? AND age > ?)
lqw.lt(User::getAge, 30).gt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
问题:区间条件查询,例如某宝筛选价格范围,用户只填最高价,最低价为null不算在查询条件里。
解决思路:查询数据库表中,根据输入年龄范围来查询符合条件的记录,用户在输入值的时候,如果只输入第一个框,说明要查询大于该年龄的用户;如果只输入第二个框,说明要查询小于该年龄的用户;如果两个框都输入了,说明要查询年龄在两个范围之间的用户。
解决方法:新建实体类继承原实体类,多出属性范围,使用lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());进行判定
问题:后台如果想接收前端的两个数据,我们可以使用两个简单数据类型,也可以使用一个模型类。那么使用一个age属性,如何去接收页面上的两个值呢?
方案一(不推荐):添加属性age2,这种做法可以但是会影响到原模型类的属性内容
方案二(推荐):在domain.query下新建一个模型类,让其继承User类,并在其中添加age2属性,UserQuery在拥有User属性后同时添加了age2属性
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
@Data
public class UserQuery extends User {
private Integer age2;
}
区间条件查询代码实现:
方案一:(不推荐使用)
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
if(null != uq.getAge2()){
lqw.lt(User::getAge, uq.getAge2());
}
if( null != uq.getAge()) {
lqw.gt(User::getAge, uq.getAge());
}
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
如果条件多的话,每个条件都需要判断,代码量就比较大
方案二:推荐使用
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//第一个参数为判断条件
lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());
lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
也可以使用链式编程
目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容
查询投影:不查询所有字段,只查询出指定字段的数据
//查询部分属性
//QueryWrapper qw = new QueryWrapper<>();
//qw.select("id","name","age","tel");
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.select(User::getAge,User::getId,User::getName);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
聚合查询不能用Lambda,只能用QueryWrapper
需求:聚合函数查询,完成count、max、min、avg、sum的使用
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.select("count(*) as count");
//qw.select("max(age) as maxAge");
//qw.select("min(age) as minAge");
//qw.select("sum(age) as sumAge");
//qw.select("avg(age) as avgAge");
List<Map<String, Object>> maps = userDao.selectMaps(qw);
System.out.println(maps);
分组查询一定要配合聚合函数使用
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.select("count(*) as count,tel");
qw.groupBy("tel");
List<Map<String, Object>> maps = userDao.selectMaps(qw);
System.out.println(maps);
注意:
- 聚合与分组查询,无法使用lambda表达式来完成
- MP只是对MyBatis的增强,如果MP实现不了,就在DAO接口中使用MyBatis的方式实现
//=(eq匹配)
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.eq(User::getName,"Jerry").eq(User::getPassword,"jerry");
User loginUser = userDao.selectOne(lqw);
System.out.println(loginUser);
//范围查询:lt le gt ge between
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.le(User::getAge,30).gt(User::getAge,10);
//lqw.between(User::getAge,10,30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
between需要注意两个数值的大小顺序
//模糊查询:like likeLeft likeRight
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.like(User::getName,"J"); //%J%
//lqw.likeRight(User::getName,"J"); //J%
//lqw.likeLeft(User::getName,"J"); //%J
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.select("count(*) as count,tel");
qw.groupBy("tel");
List<Map<String, Object>> maps = userDao.selectMaps(qw);
System.out.println(maps);
//排序 orderBy
LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>();
/**
* condition :条件,返回boolean,
当condition为true,进行排序,如果为false,则不排序
* isAsc:是否为升序,true为升序,false为降序
* columns:需要操作的列
*/
lwq.orderBy(true,false, User::getAge);
userDao.selectList(lwq);
orderByAsc/Desc(columns):按照指定字段进行升序/降序
更多查询条件参考官方API:https://mp.baomidou.com/guide/wrapper.html#abstractwrapper
问题一:表字段与编码属性设计不同步
解决方案:@TableField字段注解里的value属性起别名
问题二:编码中添加了数据库中未定义的属性
解决方案:@TableField注解里的exist属性设置其是否在数据库存在
问题三:采用默认查询开放了更多的字段查看权限
解决方案:@TableField注解里的select属性,设置属性是否参与查询
问题四:表名与编码开发设计不同步
解决方案:注解@TableName来设置表与模型类之间的对应关系
不同的表应用不同的id生成策略
5种id策略
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
mybatis-plus:
global-config:
db-config:
id-type: assign_id
物理删除:业务数据从数据库中丢弃,执行的是delete操作
逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中,执行的是update操作
实现逻辑删除:
@Data
//@TableName("tbl_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id; //注意id类型是Long
private String name;
@TableField(value = "pwd",select = false)
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Integer online;
@TableLogic(value="0",delval="1")
//value属性是默认值,delval是删除后修改的值
private Integer deleted;
}
也可以使用全局配置的方式,更加通用:
mybatis-plus:
global-config:
db-config:
#逻辑删除字段名
logic-delete-field: deleted
#逻辑删除字面值:未删除为0
logic-not-delete-value: 0
#逻辑删除字面值:删除为1
logic-delete-value: 1
@Test
void testDelete(){
userDao.deleteById(1L);
}
业务并发现象带来的问题:秒杀
假如有100个商品或者票在出售,为了能保证每个商品或者票只能被一个人购买,如何保证不会出现超买或者重复卖
对于这一类问题,其实有很多的解决方案可以使用
第一个最先想到的就是锁,锁在一台服务器中是可以解决的,但是如果在多台服务器下锁就没有办法控制,比如12306有两台服务器在进行卖票,在两台服务器上都添加锁的话,那也有可能会导致在同一时刻有两个线程在进行卖票,还是会出现并发问题
接下来介绍的这种方式是针对于小型企业的解决方案,因为数据库本身的性能就是个瓶颈,如果对其并发量超过2000以上的就需要考虑其他的解决方案了
简单来说:乐观锁主要解决的问题是当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁的实现:
@Data
//@TableName("tbl_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id; //注意id类型是Long
private String name;
@TableField(value = "pwd",select = false)
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Integer online;
@TableLogic(value="0",delval="1")
//value属性是默认值,delval是删除后修改的值
private Integer deleted;
@Version
private Integer version;
}
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1.定义Mp拦截器
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//分页拦截器
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//乐观锁拦截器
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}
@Test
void testUpdate(){
//1.先通过要修改的数据id将当前数据查询出来
User user = userDao.selectById(3L); //version=2
User user2 = userDao.selectById(3L); //version=2
user2.setName("Jock aaa");
userDao.updateById(user2); //version=>4
user.setName("Jock bbb");
userDao.updateById(user); //verion=3?条件还成立吗?
}
观察我们之前写的代码,会发现其中也会有很多重复内容,对于这段代码,基本上都是对红色部分的调整,所以我们把去掉红色内容的东西称之为模板,红色部分称之为参数,以后只需要传入不同的参数,就可以根据模板创建出不同模块的dao代码
除了Dao可以抽取模块,其实我们常见的类都可以进行抽取,只要他们有公共部分即可。如下:
分析完后,我们会发现,要想完成代码自动生成,我们需要有以下内容:
代码生成器实现:
创建好一个MyBatisPlus模块
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.14-SNAPSHOTversion>
<relativePath/>
parent>
<groupId>com.zsgroupId>
<artifactId>mybatisplus_generatorartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>mybatisplus_generatorname>
<description>mybatisplus_generatordescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.3.1version>
dependency>
<dependency>
<groupId>com.mysqlgroupId>
<artifactId>mysql-connector-jartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.apache.velocitygroupId>
<artifactId>velocity-engine-coreartifactId>
<version>2.3version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starter-testartifactId>
<version>2.3.1version>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
public class CodeGenerator {
public static void main(String[] args) {
//1.获取代码生成器的对象
AutoGenerator autoGenerator = new AutoGenerator();
//创建DataSourceConfig 对象设置数据库相关配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("root");
autoGenerator.setDataSource(dataSource);
//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java"); //设置代码生成位置
globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("黑马程序员"); //设置作者
globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件
globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称
globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);
//设置包名相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.aaa"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity("domain"); //设置实体类包名
packageInfo.setMapper("dao"); //设置数据层包名
autoGenerator.setPackageInfo(packageInfo);
//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tbl_user"); //设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);
//2.执行代码生成器
autoGenerator.execute();
}
}
参考文章