能够基于MyBatisPlus完成标准Dao开发
能够掌握MyBatisPlus的条件查询
能够掌握MyBatisPlus的字段映射与表名映射
能够掌握id生成策略控制
能够理解代码生成器的相关配置
MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率
开发方式
基于MyBatis使用MyBatisPlus
基于Spring使用MyBatisPlus
基于SpringBoot使用MyBatisPlus
org.projectlombok
lombok
com.baomidou
mybatis-plus-boot-starter
3.5.1
com.alibaba
druid
1.1.16
注意事项1:由于mp并未被收录到idea的系统内置配置,无法直接选择加入
注意事项2:如果使用Druid数据源,需要导入对应坐标
(类名与表名对应,属性名与字段名对应)
新建一个数据库mybatisplyus
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC
username: root
password: 123456
//使用mp,dao的写法,只需要继承一个父亲接口就可以BaseMapper,还要注意加上泛型
@Mapper
public interface UserDao extends BaseMapper {
}
查询所有
@SpringBootTest
public class TestUserDao {
@Autowired
private UserDao userDao;
//查询所有
@Test
void testFindAll() {
List list = userDao.selectList(null);
System.out.println("list=" +list);
}}
通过入门案例制作,MyBatisPlus的优点有哪些?
MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率
官网:https://mybatis.plus/ https://mp.baomidou.com/
无侵入:只做增强不做改变,不会对现有工程产生影响
强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD 操作
支持 Lambda:编写查询条件无需担心字段写错
支持主键自动生成
内置分页插件
……
下面用MP进行CURD
// 新增
@Test
void testAdd() {
User user = new User();
user.setName("黑马程序员");
user.setPassword("itheima");
user.setAge(12);
user.setTel("4006184000");
int row = userDao.insert(user);
System.out.println("row = " + row);
}
这里面因为刚才不小心多增加一个相同内容 所以这里面删除的话id就用雪花
//删除
@Test
void testDelete() {
// int row = userDao.deleteById(3);
int row = userDao.deleteById(1631599267871215618L);
System.out.println("row = " + row);
}
我们现在把另一个也删除了
//修改
@Test
void testUpdate() {
//1.先查询数据
User user = userDao.selectById(2);
user.setPassword("777");
//2.修改
int row = userDao.updateById(user);
System.out.println("row = " + row);
}
//查询单个
@Test
void testFindById() {
User user = userDao.selectById(2);
System.out.println("user = " + user);
}
有什么简单的办法可以自动生成实体类的GET、SET方法?
Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。
org.projectlombok
lombok
1.18.12
常用注解:==@Data==,为当前实体类在编译期设置对应的get/set方法,无参/无参构造方法,toString方法,hashCode方法,equals方法等
package com.itheima.domain;
import lombok.*;
/*
1 生成getter和setter方法:@Getter、@Setter
生成toString方法:@ToString
生成equals和hashcode方法:@EqualsAndHashCode
2 统一成以上所有:@Data
3 生成空参构造: @NoArgsConstructor
生成全参构造: @AllArgsConstructor
4 lombok还给我们提供了builder的方式创建对象,好处就是可以链式编程。 @Builder【扩展】
*/
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
思考一下Mybatis分页插件是如何用的?
Ipage 接口
用MP后 是有一个selectPage() 方法 然后这个方法传进去一个对象page 这里面就已经包装了查第几页 每页查第多少条
//分面查询
@Test
void testFindPage() {
/** @param current 当前页
* @param size 每页显示条数*/
//1 创建IPage分页对象,设置分页参数 当前页为第一页 每页显示条数为2条
//IPage page =new Page<>(1,2);
//1 创建IPage分页对象,设置分页参数 当前页为第二页 每页显示条数为2条
IPage page =new Page<>(2,2);
//2 执行分页查询
IPage iPage = userDao.selectPage(page, null);
//3 获取分页结果
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());
}
}
②:设置分页拦截器作为Spring管理的bean
@Configuration
public class MybatisPlusConfig {
/*配置分页拦截器,需要打上@Bean的注解,暴露给spring使用*/
@Bean
public MybatisPlusInterceptor mpi(){
//1 创建MybatisPlus拦截器对象
MybatisPlusInterceptor mpi =new MybatisPlusInterceptor();
//2 添加分页拦截器
mpi.addInnerInterceptor(new PaginationInnerInterceptor());
//返回MyBatisplus的拦截器对象
return mpi;
}
}
重新运行发现 这回正常了
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC
username: root
password: 123456
# 开启mp的日志(输出到控制台)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
做法:在resources下新建一个logback.xml文件,名称固定,内容如下:
关于logback参考播客:https://www.jianshu.com/p/75f9d11ae011
spring:
main:
banner-mode: off # 关闭SpringBoot启动图标(banner)
# mybatis-plus日志控制台输出
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: off # 关闭mybatisplus启动图标
环境准备
依赖
org.projectlombok
lombok
com.baomidou
mybatis-plus-boot-starter
3.5.1
com.alibaba
druid
1.1.16
从demo1复制依赖 yml dao bean
测试类test.TestUserDao
MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合
无条件全查
@SpringBootTest
public class TestUserDao {
@Autowired
private UserDao userDao;
@Test
public void findByCondition(){
//查询年龄大于等于18岁 小于65岁的用户
//1.构建条件对象
QueryWrapper qw =new QueryWrapper<>();
//1.1 添加条件
//2.执行查询
List list = userDao.selectList(qw);
System.out.println("list = " + list);
}
}
@SpringBootTest
public class TestUserDao {
@Autowired
private UserDao userDao;
@Test
public void findByCondition(){
//查询年龄大于等于18岁 小于65岁的用户
//1.构建条件对象
QueryWrapper qw =new QueryWrapper<>();
//1.1 添加条件
qw.ge("age",18);
qw.lt("age",65);
//2.执行查询
List list = userDao.selectList(qw);
System.out.println("list = " + list);
}
}
链式写法
`@SpringBootTest
public class TestUserDao {
@Autowired
private UserDao userDao;
@Test
public void findByCondition(){
//查询年龄大于等于18岁 小于65岁的用户
//1.构建条件对象
QueryWrapper qw =new QueryWrapper<>();
//1.1 添加条件
//常规
// qw.ge("age",18);
// qw.lt("age",65);
//链式写法
qw.ge("age",18).lt("age",65);
//2.执行查询
List list = userDao.selectList(qw);
System.out.println("list = " + list);
}
}
//使用条件查询[使得lambda格式按条件查询]
@Test
public void findByCondition02(){
//查询年龄大于等于18岁 小于65岁的用户
//1.构建条件对象
QueryWrapper qw =new QueryWrapper<>();
//1.1 添加条件
//常规
qw.lambda().ge(User::getAge,18).lt(User::getAge,65);
//2.执行查询
List list = userDao.selectList(qw);
System.out.println("list = " + list);
}
//使用条件查询[使得lambda格式按条件查询(推荐)]
@Test
public void findByCondition03(){
//查询年龄大于等于18岁 小于65岁的用户
//1.构建条件对象
LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
//1.1 添加条件
//使用方法引用定位列的名字,可以很好的防止手动写列名,引发的一些问题。
//常规
lqw.ge(User::getAge,18);
lqw.lt(User::getAge,65);
//2.执行查询
List list = userDao.selectList(lqw);
System.out.println("list = " + list);
}
@Test
public void findByCondition04(){
//查询年龄大于等于18岁 小于65岁的用户
//1.构建条件对象
LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
//1.1 添加条件
//使用方法引用定位列的名字,可以很好的防止手动写列名,引发的一些问题。
//常规
lqw.ge(User::getAge,18).lt(User::getAge,65);
//2.执行查询
List list = userDao.selectList(lqw);
System.out.println("list = " + list);
}
//查询年龄小于等于18岁 或者年龄大于65岁的用户
//如果使用或者,那么需要使用or() 来连接多个条件
@Test
public void findByCondition05(){
//1.构建条件对象
LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
//2.添加条件
lqw.le(User::getAge,18).or().gt(User::getAge,65);
//2.执行查询
List list = userDao.selectList(lqw);
System.out.println("list = " + list);
}
如下搜索场景,在多条件查询中,有条件的值为空应该怎么解决?
原始写法
` //============空值的处理(其实就是以前的动态sql)==========
@Test
public void findByCondition06(){
//1. 组装数据【理论上,这份数据应该是从浏览器的页面传递过来】
//1.1 浏览器传递过来的数据 有多有少 可有可无
//1.2 如果传递过来了所有的这三个条件数据 那么表示想按照条件来查询
//1.3 传递多少个数据,就有多少个条件被追加
User u = new User();
u.setName("admin");
u.setPassword("123456");
u.setTel("10086");
//2.组装条件
LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
//2。1 原始的写法
//a.判断数据身上是否有用户名 有 就追加条件
if (u.getName() != null) {
qw.eq(User::getName,u.getName());
}
//b.判断数据身上是否有密码 ,有,就追加条件
if (u.getPassword() !=null) {
qw.eq(User::getPassword,u.getPassword());
}
//c.判断数据身上是否有电话,有,就追加条件
if (u.getTel() !=null) {
qw.eq(User::getTel,u.getTel());
}
//3.执行查询
List list = userDao.selectList(qw);
System.out.println("list = " + list);
}
//平时在处理页面上进行搜索过滤的代码
@Test
public void findByCondition07(){
//1. 组装数据【理论上,这份数据应该是从浏览器的页面传递过来】
//1.1 浏览器传递过来的数据 有多有少 可有可无
//1.2 如果传递过来了所有的这三个条件数据 那么表示想按照条件来查询
//1.3 传递多少个数据,就有多少个条件被追加
User u = new User();
u.setName("admin");
u.setPassword("123456");
u.setTel("10086");
//2.组装条件
LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
//a.判断数据身上是否有用户名 有 就追加条件
//参数一;就是用来标记这个条件要不要添加进去,如果是true,即表示要添加条件
qw.eq(u.getName() !=null, User::getName,u.getName());
//b.判断数据身上是否有密码 ,有,就追加条件
qw.eq(u.getPassword() != null,User::getPassword,u.getPassword());
//c.判断数据身上是否有电话,有,就追加条件
qw.eq(u.getTel() !=null,User::getTel,u.getTel());
//3.执行查询
List list = userDao.selectList(qw);
System.out.println("list = " + list);
}
//===============投影查询 和统计查询==================
//投影查询:指的就是查询少量的列。不是面对所有的列
@Test
public void findByCondition08(){
//======第一种写法=====
//1.使用QueryWrap
QueryWrapper qw = new QueryWrapper<>();
//1.1 设置查询的具体列
qw.select("id","name","password");
//2.执行查询
List userList = userDao.selectList(qw);
System.out.println("userList = " + userList);
System.out.println("---------------------------------------");
//=================第二种写法==============
//2.使用 LambdaQueryWrapper来做
LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
//2.1 设置查询哪些类
lqw.select(User::getId ,User::getName,User::getPassword);
//3.执行查询
List list = userDao.selectList(lqw);
System.out.println("list = " + list);
}
补:因为我这里面分组用到的是男女分组 而我们数据库里面没有性别这个字段 所以我们在先加一下这个字段
@Test
public void findByCondition09(){
//统计查询:统计性别的数量
// 1.定义条件对象
QueryWrapper qw = new QueryWrapper();
//2.追加条件
//qw.select("gender,count(*) as number");
//一个列就是一个参数数字字符串
qw.select("gender","count(*) as number");
//分组
qw.groupBy("gender");
//3.执行查询
//执行查询返回的是List集合里面装Map集合
//每一条记录就是一个Map集合,map集合的KEY就是列的名字,map集合的value就是列的值。
List
多条件查询有哪些组合?
范围匹配(> 、 = 、between)
模糊匹配(like)
空判定(null)
包含性匹配(in)
分组(group)
排序(order)
……
模糊匹配(like)
//模糊查询
@Test
public void testFindByCondition10(){
//1. 创建条件对象
LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
//2. 追加条件
lqw.like(User::getName , "i");
//3. 执行查询
List list = userDao.selectList(lqw);
System.out.println("list = " + list);
}
用户登录(eq匹配)
//查询单条记录,使用selectOne() :: 登录的场景
@Test
public void testFindByCondition11(){
//1. 创建条件对象
LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
//2. 追加条件
lqw.eq(User::getName , "zhangsan");
lqw.eq(User::getPassword , "777");
//3. 执行查询
User user = userDao.selectOne(lqw);
System.out.println("user = " + user);
}
购物设定价格区间、户籍设定年龄区间(le ge匹配 或 between匹配)
//区间查询: 使用 between 来实现 , 查询年龄大于等于18 ,并且小于等于65岁
@Test
public void testFindByCondition12(){
//1. 创建条件对象
LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
//2. 追加条件
//lqw.ge(User::getAge , 18).le(User::getAge , 65);
//前后都包含
lqw.between(User::getAge , 18 , 65);
//3. 执行查询
List userList = userDao.selectList(lqw);
System.out.println("userList = " + userList);
}
更多查询条件设置参看 https://mybatis.plus/guide/wrapper.html#abstractwrapper
思考表的字段和实体类的属性不对应,查询会怎么样?
在模型类属性上方,使用@TableField属性注解,通过==value==属性,设置当前属性对应的数据库表中的字段关系。
环境准备
建立表
CREATE TABLE `user02` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号' ,
`name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名' ,
`pwd` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码' ,
`age` int(3) NOT NULL COMMENT '年龄' ,
`tel` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '电话' ,
PRIMARY KEY (`id`)
)
依赖
org.projectlombok
lombok
com.baomidou
mybatis-plus-boot-starter
3.4.3
com.alibaba
druid
1.1.16
复制 bean yml
创建dao
@Mapper
public interface UserDao extends BaseMapper {
}
创建测试类
@SpringBootTest
public class TestUserDao {
@Autowired
private UserDao userDao;
@Test
public void testFindAll(){
//查询所有
List list = userDao.selectList(null);
System.out.println("list = " + list);
}
}
跑起来发现报错
### Cause: java.sql.SQLSyntaxErrorException: Unknown column 'password' in 'field list'
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Unknown column 'password' in 'field list'
错误原因解释
/*
1. mybatisplus 执行查询的时候,它并不是直接使用 select * 来做的查询
2. 它是通过解读dao 身上的 泛型,找到具体的类
public interface UserDao extends BaseMapper
3. 所以后续的UserDao的操作,它都是根据User02类来指导。
3.1 把类名的名字变成小写,那么就是表的名字。【默认】
3.2 查询表的所有的数据不是采用 * 来执行,怎么知道user02 有哪些列呢?
它是这么认为的,一般类当中的属性名一定和表里面的列名一样。!
3.3 写: select 列1, 列2, 列3 ... from user02 等价于
select 属性1, 属性2, 属性3, .... from user02.
4. 如果属性名和列的名字对不上,那么查询就报错了!
4.1 在不一样的属性名上面加上一个注解 @TableField, 可以认为给属性起一个别名。
*/
解决办法:
在不一样的属性名上面加上一个注解 @TableField, 可以认为给属性起一个别名
@Data
public class User02 {
private Long id;
private String name;
/*
1. mybatisplus 执行查询的时候,它并不是直接使用 select * 来做的查询
2. 它是通过解读dao 身上的 泛型,找到具体的类
public interface UserDao extends BaseMapper
3. 所以后续的UserDao的操作,它都是根据User02类来指导。
3.1 把类名的名字变成小写,那么就是表的名字。【默认】
3.2 查询表的所有的数据不是采用 * 来执行,怎么知道user02 有哪些列呢?
它是这么认为的,一般类当中的属性名一定和表里面的列名一样。!
3.3 写: select 列1, 列2, 列3 ... from user02 等价于
select 属性1, 属性2, 属性3, .... from user02.
4. 如果属性名和列的名字对不上,那么查询就报错了!
4.1 在不一样的属性名上面加上一个注解 @TableField, 可以认为给属性起一个别名。
*/
@TableField("pwd")
private String password;
private Integer age;
private String tel;
}
补:
这个不是报错 idea发现你要注入时并没有任何对象被管理起来 如果不想让有这条线 就在UserDao里面添加@Repository注解就可以了 当然这个不会影响我们的使用 所以我们可以不用去管他
在模型类属性上方,使用@TableField注解,通过==exist==属性,设置属性在数据库表字段中是否存在,默认为true。此属性无法与value合并使用。
问题分析:
/*
1. 当类里面出现了一个属性,这个属性并没有在表里面有任何一个列与之对应
2. 要告诉mybatisplus 这个属性并不是要和表里面哪个列进行对应,
2.1 通俗一点就是告诉MP。 这个属性不是表里面列,查询的时候不要带上它。
2.2 只需要在属性上加上注解@TableField ,设置里面的exist属性的值为 false
2.3 那么在执行表的查询工作的时候,就不会带上这个属性了。
3. 既然表里面没有这个列,那么为什么类当中要有这样的属性存在呢? 什么场景下会有这种情况出现!
3.1 JavaBean 是用来封装数据的,这份数据姑且认为,就是从数据库得到的数据。
3.2 封装好数据之后,要返回给前端【浏览器】,在浏览器上展示数据。
3.3 有一种极端的情况:
3.3.1 从数据库里面得到的数据,在页面上并不足以支撑它的数据展示。
3.3.2 比如:以前品牌的,状态值。
数据库里面存储的是 1 和 0
页面要展示的是: 启用 和 禁用。
3.3.3 可以在类当中追加一个属性: private String statusStr; 用来处理字符串状态值的。
*/
报错情况如下
### SQL: SELECT id,name,pwd AS password,age,tel,online FROM user02
### Cause: java.sql.SQLSyntaxErrorException: Unknown column 'online' in 'field list'
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Unknown column 'online' in 'field list'
解决办法:
package com.itheima.bean;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
@Data
public class User02 {
private Long id;
private String name;
/*
1. mybatisplus 执行查询的时候,它并不是直接使用 select * 来做的查询
2. 它是通过解读dao 身上的 泛型,找到具体的类
public interface UserDao extends BaseMapper
3. 所以后续的UserDao的操作,它都是根据User02类来指导。
3.1 把类名的名字变成小写,那么就是表的名字。【默认】
3.2 查询表的所有的数据不是采用 * 来执行,怎么知道user02 有哪些列呢?
它是这么认为的,一般类当中的属性名一定和表里面的列名一样。!
3.3 写: select 列1, 列2, 列3 ... from user02 等价于
select 属性1, 属性2, 属性3, .... from user02.
4. 如果属性名和列的名字对不上,那么查询就报错了!
4.1 在不一样的属性名上面加上一个注解 @TableField, 可以认为给属性起一个别名。
*/
@TableField("pwd")
private String password;
private Integer age;
private String tel;
/*
1. 当类里面出现了一个属性,这个属性并没有在表里面有任何一个列与之对应
2. 要告诉mybatisplus 这个属性并不是要和表里面哪个列进行对应,
2.1 通俗一点就是告诉MP。 这个属性不是表里面列,查询的时候不要带上它。
2.2 只需要在属性上加上注解@TableField ,设置里面的exist属性的值为 false
2.3 那么在执行表的查询工作的时候,就不会带上这个属性了。
3. 既然表里面没有这个列,那么为什么类当中要有这样的属性存在呢? 什么场景下会有这种情况出现!
3.1 JavaBean 是用来封装数据的,这份数据姑且认为,就是从数据库得到的数据。
3.2 封装好数据之后,要返回给前端【浏览器】,在浏览器上展示数据。
3.3 有一种极端的情况:
3.3.1 从数据库里面得到的数据,在页面上并不足以支撑它的数据展示。
3.3.2 比如:以前品牌的,状态值。
数据库里面存储的是 1 和 0
页面要展示的是: 启用 和 禁用。
3.3.3 可以在类当中追加一个属性: private String statusStr; 用来处理字符串状态值的。
*/
@TableField(exist = false)
private String online;
}
问题解决之后控制台显示:
在模型类属性上方,使用@TableField注解,通过==select==属性:设置该属性是否参与查询。此属性与select()映射配置不冲突。
问题分析:设置不让查询后 我们就查询不到 在生活场景中比较经常用到的是不查密码 不要密码查询出来
/*
1. 如果在执行查询操作的时候,不希望把这个类的数据给查询出来,那么可以加上@TableField注解
2. 通过设置里面的select属性的值为 false, 那么它将不参与查询!
3. 如果从结果来看的话,select和exist并没有什么区别,结果一样:
3.1 exist讲究的是这个属性【列】在表里面有还是没有
3.2 select讲究的是这个属性【列】到底要不要查询出来,这个列在表里一定是有的。
3.3 在写入操作上也有区别:
select标记的属性,在执行增删改的时候还是会参与的,但是 exist 标记的属性就不会参与,因为没有这个列
*/
解决办法:
package com.itheima.bean;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
@Data
public class User02 {
private Long id;
private String name;
/*
1. mybatisplus 执行查询的时候,它并不是直接使用 select * 来做的查询
2. 它是通过解读dao 身上的 泛型,找到具体的类
public interface UserDao extends BaseMapper
3. 所以后续的UserDao的操作,它都是根据User02类来指导。
3.1 把类名的名字变成小写,那么就是表的名字。【默认】
3.2 查询表的所有的数据不是采用 * 来执行,怎么知道user02 有哪些列呢?
它是这么认为的,一般类当中的属性名一定和表里面的列名一样。!
3.3 写: select 列1, 列2, 列3 ... from user02 等价于
select 属性1, 属性2, 属性3, .... from user02.
4. 如果属性名和列的名字对不上,那么查询就报错了!
4.1 在不一样的属性名上面加上一个注解 @TableField, 可以认为给属性起一个别名。
*/
@TableField("pwd")
private String password;
/*
1. 如果在执行查询操作的时候,不希望把这个类的数据给查询出来,那么可以加上@TableField注解
2. 通过设置里面的select属性的值为 false, 那么它将不参与查询!
3. 如果从结果来看的话,select和exist并没有什么区别,结果一样:
3.1 exist讲究的是这个属性【列】在表里面有还是没有
3.2 select讲究的是这个属性【列】到底要不要查询出来,这个列在表里一定是有的。
3.3 在写入操作上也有区别:
select标记的属性,在执行增删改的时候还是会参与的,但是 exist 标记的属性就不会参与,因为没有这个列
*/
@TableField(select = false)
private Integer age;
private String tel;
/*
1. 当类里面出现了一个属性,这个属性并没有在表里面有任何一个列与之对应
2. 要告诉mybatisplus 这个属性并不是要和表里面哪个列进行对应,
2.1 通俗一点就是告诉MP。 这个属性不是表里面列,查询的时候不要带上它。
2.2 只需要在属性上加上注解@TableField ,设置里面的exist属性的值为 false
2.3 那么在执行表的查询工作的时候,就不会带上这个属性了。
3. 既然表里面没有这个列,那么为什么类当中要有这样的属性存在呢? 什么场景下会有这种情况出现!
3.1 JavaBean 是用来封装数据的,这份数据姑且认为,就是从数据库得到的数据。
3.2 封装好数据之后,要返回给前端【浏览器】,在浏览器上展示数据。
3.3 有一种极端的情况:
3.3.1 从数据库里面得到的数据,在页面上并不足以支撑它的数据展示。
3.3.2 比如:以前品牌的,状态值。
数据库里面存储的是 1 和 0
页面要展示的是: 启用 和 禁用。
3.3.3 可以在类当中追加一个属性: private String statusStr; 用来处理字符串状态值的。
*/
@TableField(exist = false)
private String online;
}
解决完成问题之后 的控制台显示
在模型类上方,使用@TableName注解,通过==value==属性,设置当前类对应的数据库表名称。
为了演示 我们把表名修改成t_user
我们把数据库中的表名修改之后 回来看Dao 发现还是写的是user02 因为他都是按照javabean来写的
发现我们数据库表中并没有类名小写后的名字 所以这个时候如何启动 就会报错
org.springframework.jdbc.BadSqlGrammarException:
### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Table 'mybatisplus.user02' doesn't exist
解决办法
package com.itheima.bean;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/*
1. 当类名和表的名字不一致的时候,那么执行操作就会失败。
2. 因为MP, 操作的时候,默认就会把类的名字变成小写,然后充当表名去操作。
3. 可以在类上打注解:
@TableName("具体的表名")
*/
@Data
@TableName("t_user")
public class User02 {
private Long id;
private String name;
/*
1. mybatisplus 执行查询的时候,它并不是直接使用 select * 来做的查询
2. 它是通过解读dao 身上的 泛型,找到具体的类
public interface UserDao extends BaseMapper
3. 所以后续的UserDao的操作,它都是根据User02类来指导。
3.1 把类名的名字变成小写,那么就是表的名字。【默认】
3.2 查询表的所有的数据不是采用 * 来执行,怎么知道user02 有哪些列呢?
它是这么认为的,一般类当中的属性名一定和表里面的列名一样。!
3.3 写: select 列1, 列2, 列3 ... from user02 等价于
select 属性1, 属性2, 属性3, .... from user02.
4. 如果属性名和列的名字对不上,那么查询就报错了!
4.1 在不一样的属性名上面加上一个注解 @TableField, 可以认为给属性起一个别名。
*/
@TableField("pwd")
private String password;
/*
1. 如果在执行查询操作的时候,不希望把这个类的数据给查询出来,那么可以加上@TableField注解
2. 通过设置里面的select属性的值为 false, 那么它将不参与查询!
3. 如果从结果来看的话,select和exist并没有什么区别,结果一样:
3.1 exist讲究的是这个属性【列】在表里面有还是没有
3.2 select讲究的是这个属性【列】到底要不要查询出来,这个列在表里一定是有的。
3.3 在写入操作上也有区别:
select标记的属性,在执行增删改的时候还是会参与的,但是 exist 标记的属性就不会参与,因为没有这个列
*/
@TableField(select = false)
private Integer age;
private String tel;
/*
1. 当类里面出现了一个属性,这个属性并没有在表里面有任何一个列与之对应
2. 要告诉mybatisplus 这个属性并不是要和表里面哪个列进行对应,
2.1 通俗一点就是告诉MP。 这个属性不是表里面列,查询的时候不要带上它。
2.2 只需要在属性上加上注解@TableField ,设置里面的exist属性的值为 false
2.3 那么在执行表的查询工作的时候,就不会带上这个属性了。
3. 既然表里面没有这个列,那么为什么类当中要有这样的属性存在呢? 什么场景下会有这种情况出现!
3.1 JavaBean 是用来封装数据的,这份数据姑且认为,就是从数据库得到的数据。
3.2 封装好数据之后,要返回给前端【浏览器】,在浏览器上展示数据。
3.3 有一种极端的情况:
3.3.1 从数据库里面得到的数据,在页面上并不足以支撑它的数据展示。
3.3.2 比如:以前品牌的,状态值。
数据库里面存储的是 1 和 0
页面要展示的是: 启用 和 禁用。
3.3.3 可以在类当中追加一个属性: private String statusStr; 用来处理字符串状态值的。
*/
@TableField(exist = false)
private String online;
}
映射之后执行结果
环境准备
依赖
org.projectlombok
lombok
com.baomidou
mybatis-plus-boot-starter
3.4.3
com.alibaba
druid
1.1.16
复制bean dao
追加一个属性
环境准备完成
主键生成的策略有哪几种方式?
不同的表应用不同的id生成策略
日志:自增(1,2,3,4,……)
购物订单:特殊规则(FQ23948AK3843)
外卖单:关联地区日期等信息(10 04 20200314 34 91)
关系表:可省略id
……
名称:@TableId
类型:属性注解
位置:模型类中用于表示主键的属性定义上方
作用:设置当前类中主键属性的生成策略
相关属性
type:设置主键属性的生成策略,值参照IdType枚举值
添加测试类
package com.itheima.test;
import com.itheima.bean.User;
import com.itheima.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class TestUserDao {
@Autowired
private UserDao userDao;
@Test
public void testAdd(){
User u = new User(null, "张三", "123", 18, "100", "男");
//userDao.insert(u);
//userDao.deleteById(1632424817103765505L);
}
}
这里面要注意的是main启动类里面启动没有多环境配置并不影响测试类的输出 所以这里面如果看到没有激活springboot时 如下图所示 先不用去管他 这里面先不用去激活
修改包装类的原因是因为有时我们的Id 如果不想给的话 我们可以直接给一个null
@TableId(type = IdType.AUTO) AUTO: 表示使用数据库自增的id
@TableId(type = IdType.INPUT) INPUT: 以用户输入的为准,如果有输入的话。
mybatis-plus:
global-config:
db-config:
id-type: assign_id
table-prefix: tbl_
MyBatisPlus是否支持批量操作?
package com.itheima.test;
import com.itheima.bean.User;
import com.itheima.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
//演示多记录操作: 批量删除和批量查询
@SpringBootTest
public class TestUserDao02 {
@Autowired
private UserDao userDao;
@Test
public void testDeleteBatch(){
List ids = Arrays.asList(new Long[]{1632433992487747586L, 1632433992487747587L});
//批量删除
int row = userDao.deleteBatchIds(ids);
System.out.println("row = " + row);
}
}
这里面要注意一下 因为我们前面表名前缀全局配置设置了一个表tb_ 会影响到 所以把user里面tb_表先注释掉 不然会报错 说找不到这张表 报错如下
解决办法
//演示多记录操作: 批量删除和批量查询
@SpringBootTest
public class TestUserDao02 {
@Autowired
private UserDao userDao;
@Test
public void testQueryBatch(){
List ids = Arrays.asList(new Long[]{1L, 2L,3L,4L ,333L,1632431756852400130L});
//批量查询
List list = userDao.selectBatchIds(ids);
System.out.println("list = " + list);
}
}
在实际环境中,如果想删除一条数据,是否会真的从数据库中删除该条数据?
删除操作业务问题:业务数据从数据库中丢弃
逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中
`package com.itheima.bean;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.awt.*;
//@TableName("tb_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
/*
ID生成策略:
1. 默认情况下,mybatisplus 使用的是雪花算法的策略
2. id策略:
AUTO: 表示使用数据库自增的id
NONE: 没有设置,约等于INPUT
INPUT: 以用户输入的为准,如果有输入的话。
ASSIGN_ID : 雪花算法生成的id,默认使用的是这个。
ASSIGN_UUID : 使用uuid 来生成id
*/
@TableId(type = IdType.INPUT)
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
private String gender;
/*
这是一个逻辑删除字段
1. 首先先在表里面增加一个列,名字: deleted
2. 自己心里衡量删除的值和未删除的值,删除的字段的值是 1, 没有删除的时候,字段的值是 0.
3. 在JavaBean 里面定义已给属性: deleted
4. 打上注解,标记这个属性是一个逻辑删除属性。
4.1 配置属性 value : 未删除的值
4.2 配置属性 deval : 删除的值
5. 可以在代码里面配置,也可以在application.yml里面全局配置
*/
@TableLogic(delval = "1" , value = "0")
private int deleted;
}
//演示逻辑删除
@SpringBootTest
public class TestUserDao03 {
@Autowired
private UserDao userDao;
@Test
public void testDelete(){
userDao.deleteById(1);
}
}
逻辑删除是做的更新操作 那么我们查询时会查询到逻辑删除后的数据吗?
答:不会的
例子:
package com.itheima.test;
import com.itheima.bean.User;
import com.itheima.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.List;
//演示逻辑删除
@SpringBootTest
public class TestUserDao03 {
@Autowired
private UserDao userDao;
@Test
public void testFindAll(){
List list = userDao.selectList(null);
System.out.println("list = " + list);
}
}
逻辑删除本质:逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
`spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC
username: root
password: 123456
# 配置输出sql语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# table-prefix: tb_ # 全局设置表名的前缀
id-type: auto # 设置主键策略,对全部的表都有影响
logic-delete-field: deleted # 设置删除的逻辑字段
logic-delete-value: 1 # 设置删除的值
logic-not-delete-value: 0 # 设置未删除的值
# mybatisplus的全局配置
增加一个字段
复制config配置类
乐观锁主张的思想是什么?
业务并发现象带来的问题:秒杀
package com.itheima.bean;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.awt.*;
//@TableName("tb_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
/*
ID生成策略:
1. 默认情况下,mybatisplus 使用的是雪花算法的策略
2. id策略:
AUTO: 表示使用数据库自增的id
NONE: 没有设置,约等于INPUT
INPUT: 以用户输入的为准,如果有输入的话。
ASSIGN_ID : 雪花算法生成的id,默认使用的是这个。
ASSIGN_UUID : 使用uuid 来生成id
*/
@TableId(type = IdType.INPUT)
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
private String gender;
/*
这是一个逻辑删除字段
1. 首先先在表里面增加一个列,名字: deleted
2. 自己心里衡量删除的值和未删除的值,删除的字段的值是 1, 没有删除的时候,字段的值是 0.
3. 在JavaBean 里面定义已给属性: deleted
4. 打上注解,标记这个属性是一个逻辑删除属性。
4.1 配置属性 value : 未删除的值
4.2 配置属性 deval : 删除的值
5. 可以在代码里面配置,也可以在application.yml里面全局配置
*/
//@TableLogic(delval = "1" , value = "0")
private int deleted;
// 表示这个属性是一个乐观锁的属性
@Version
private int version;
}
package com.itheima.config;
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.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
/**
* 配置分页拦截器,需要打上@Bean的注解,暴露给spring使用
* @return
*/
@Bean
public MybatisPlusInterceptor mpi(){
//1. 创建MyBatisplus的拦截器对象
MybatisPlusInterceptor mpi = new MybatisPlusInterceptor();
//2. 往它身上添加分页拦截器
mpi.addInnerInterceptor(new PaginationInnerInterceptor());
//3. 追加乐观锁的拦截器
mpi.addInnerInterceptor(new OptimisticLockerInnerInterceptor() );
//4. 返回MyBatisplus的拦截器对象
return mpi;
}
}
package com.itheima.test;
import com.itheima.bean.User;
import com.itheima.dao.UserDao;
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 TestUserDao04 {
@Autowired
private UserDao userDao;
@Test
public void testUpdate(){
//1. 先查询数据
User user = userDao.selectById(3L);
//1.1修改数据
user.setPassword("6789");
//2.执行更新
userDao.updateById(user);
}
}
解释(注:以下的例子是视频例子以我的操作不同)
package com.itheima.test;
import com.itheima.bean.User;
import com.itheima.dao.UserDao;
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 TestUserDao04 {
@Autowired
private UserDao userDao;
@Test
public void testUpdate(){
//1. 先查询数据
User user = userDao.selectById(3);
//1. 先查询数据
User user02 = userDao.selectById(3);
//1.1 修改数据
user.setPassword("888");
//1.2 执行更新
userDao.updateById(user);
user02.setPassword("0000");
userDao.updateById(user02);
}
}
如果只给一张表的字段信息,能够推演出Domain、Dao层的代码?
Mapper接口模板
实体对象类模板
第一步:创建SpringBoot工程,添加代码生成器相关依赖,其他依赖自行添加
com.baomidou
mybatis-plus-boot-starter
3.5.1
com.baomidou
mybatis-plus-generator
3.4.1
org.apache.velocity
velocity-engine-core
2.3
第二步:编写代码生成器类
@SpringBootTest
class Demo5CodeApplicationTests {
@Test
void contextLoads() {
//D:\review_MyBatisPlus\review_MyBatisPlus_itheima\mybatisplus\demo5_code
System.out.println(System.getProperty("user.dir"));
}
}
黑马笔记内容
package com.itheima;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
public class Generator {
public static void main(String[] args) {
//1. 创建代码生成器对象,执行生成代码操作
AutoGenerator autoGenerator = new AutoGenerator();
//2. 数据源相关配置:读取数据库中的信息,根据数据库表结构生成代码
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);
//3. 执行生成操作
autoGenerator.execute();
}
}
`package com.itheima;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Demo5CodeApplicationTests {
@Test
void contextLoads() {
//D:\review_MyBatisPlus\review_MyBatisPlus_itheima\mybatisplus\demo5_code
System.out.println(System.getProperty("user.dir"));
//创建代码生成器对象,执行生成代码操作
AutoGenerator autoGenerator = new AutoGenerator();
//数据源相关配置:读取数据库中的信息,根据数据库表结构生成代码
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/reggie?serverTimezone=UTC");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("123456");
autoGenerator.setDataSource(dataSourceConfig);
//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java");
globalConfig.setOpen(false);
globalConfig.setAuthor("黑马程序员");
globalConfig.setFileOverride(true);
globalConfig.setMapperName("%sDao");
globalConfig.setIdType(IdType.ASSIGN_ID);
// autoGenerator.setGlobalConfig(globalConfig);
//包相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.itheima");
packageInfo.setEntity("bean");
packageInfo.setMapper("dao");
autoGenerator.setPackageInfo(packageInfo);
//策略配置
StrategyConfig strategy = new StrategyConfig();
//strategy.setInclude("tbl_user");
strategy.setTablePrefix("tbl_");
strategy.setRestControllerStyle(true);
strategy.setEntityLombokModel(true);
//strategy.setLogicDeleteFieldName("deleted");
//strategy.setVersionFieldName("version");
autoGenerator.setStrategy(strategy);
autoGenerator.execute();
}
}
说明:在资料中也提供了CodeGenerator代码生成器类,根据实际情况修改后可以直接使用。
设置全局配置
//设置全局配置
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);
说明:在资料中也提供了CodeGenerator代码生成器类,根据实际情况修改后可以直接使用。