Day1
了解Mybatis Plus
1创建数据库表
CREATE TABLE `tbl_product` (
`id` bigint NOT NULL AUTO_INCREMENT,
`pname` varchar(50) COMMENT '商品名称',
`brand` varchar(100) COMMENT '品牌',
`price` double COMMENT '单价',
`num` int COMMENT '库存数量',
`online` char(1) COMMENT '是否上架',
`create_time` date COMMENT '添加日期',
`last_update_time` datetime COMMENT '最后修改日期',
`version` tinyint DEFAULT '0' COMMENT '版本号',
`deleted` tinyint DEFAULT '0' COMMENT '是否删除 0 否 1 是',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*Data for the table `tbl_user` */
INSERT INTO tbl_product (pname,brand,price,num,online,create_time,last_update_time,version,deleted) VALUES
('HUAWEI P50','华为',8988.0,20,'1','1999-10-26','2021-12-24 14:53:22',1,0),
('小米10S','小米',2699.0,21,'1','2021-06-12','2021-10-06 08:12:16',1,0),
('OPPO K9','OPPO',1999.0,23,'1','1998-03-27','2021-12-06 12:56:36',1,0),
('HUAWEI nova 8','华为',2099.0,20,'0','2021-06-22','2021-12-10 17:26:21',1,0),
('华为 Mate 40','华为',6799.0,25,'1','1996-07-16','2021-10-07 09:32:16',1,0),
('小米 红米Note9','小米',935.0,23,'1','1998-05-01','2021-10-10 22:17:17',1,0),
('OPPO A11s','OPPO',1199.0,23,'0','1998-07-12','2021-11-02 23:22:36',1,0),
('OPPO A93','OPPO',1599.0,24,'1','1997-11-02','2021-08-03 10:36:27',1,0),
('华为畅享 20e ','华为',1199.0,26,'1','1995-12-06','2021-08-03 11:36:27',1,0),
('小米 MIX4','小米',4499.0,22,'1','1996-08-06','2021-10-07 09:32:16',1,0),
('vivo S12','vivo',3699.0,28,'1','1999-12-06','2021-11-02 18:23:22',1,0),
('vivo iQOO 8','vivo',3899.0,30,'0','2000-08-01','2021-12-25 09:56:31',1,0);
2 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
<dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<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>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
3 编写配置文件
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/user?useUnicode=true&IntegerEncoding=utf8&useSSL=false&servertimeZone=Asia/Shanghai
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: 123456
mybatis-plus:
# 别名
type-aliases-package: com.itheima.mp.domain
# mapper文件位置
mapper-locations: classpath*:/mapper/**/*.xml
configuration:
# 控制台日志打印,便于查看SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4 实体类
@Data
@TableName("tbl_product")
public class Product {
private Long id;
private String pname;
private String brand;
private Double price;
private Integer num;
private Integer online;
private Date createTime;
private Date lastUpdateTime;
private Integer version;
private Integer deleted;
}
5 mapper extends BaseMapper
@Repository
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
}
注解
@TableName
- 描述:表名注解,标识实体类对应的表,如果表名和类名一致可以省略
- 使用位置:实体类
@TableName("tbl_product") //绑定表关系
public class Product {
如果每个表都是以固定前缀开头,可以全局配置表前缀
属性设置 > 全局设置: 如果使用了 @TableName 指定表名,则会忽略全局的表前缀
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_ #表前缀
@TableId
- 描述:主键注解
- 使用位置:实体类主键字段
//IdType 指定主键类型
@TableId(value = "id",type = IdType.AUTO)
private Long id;
如果大部分表主键都是自增,可以进行全局设置
属性上的优先级 > 全局设置
mybatis-plus:
global-config:
db-config:
id-type: assign_id #主键策略
table-prefix: tbl_ #表前缀
IdType
值 | 描述 |
---|---|
AUTO | 数据库 ID 自增,手动设置ID无效 |
NONE | 默认值 无状态,和未添加@TableId注解效果一样,当手动设置主键id值,会按设置的值插入,若未手动设置值,会使用雪花算法成一个Long值作为主键。 |
INPUT | insert 前自行 set 主键值,(如果没有手动设置ID值,则ID为null,使用数据库自增策略) |
ASSIGN_ID | 分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator 的方法nextId (默认实现类为DefaultIdentifierGenerator 雪花算法)如果手动设置了id值,则使用手动设置的id,否则使用雪花算法生成一个Long类型值作为id |
ASSIGN_UUID | 分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator 的方法nextUUID (默认 default 方法)注意注解类型要是varchar类型,长度最少32位 |
//IdType 指定主键类型
@TableId(value = "id",type = IdType.AUTO)
private Long id;
@Test
public void testIdType(){
Product product = new Product();
//product.setId(16L); //不手动设置id
product.setPname("华为P50");
product.setPrice(8999D);
productMapper.insert(product);
}
@TableField
- 描述:字段注解
- 使用位置:实体类普通字段
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
value | String | “” | 数据库字段名,如果同名可以省略 |
exist | boolean | true | 是否为数据库表字段,如果表中没有该字段必须设置为false,CRUD都不会包含该字段 |
select | boolean | true | 查询时是否查询该字段,如果设置为false,查询时不包含,但是insert、update、delete包含 |
fill | Enum | FieldFill.DEFAULT | 字段自动填充策略,默认不会自动填充值 |
@TableField(value = "pname",select = false)
private String name;
fill 自动填充
当对应的属性没有值
,执行 insert 或 update 操作自动给属性字段填充指定的值
应用场景:设置默认值,例如每次创建用户,后台程序中都需要设置创建时间、是否启用、用户等级…,这些字段就可以使用默认填充,不必每次都手动设置值
- FieldFill 填充策略
值 | 描述 |
---|---|
DEFAULT | 默认不处理 |
INSERT | 插入时填充字段 |
UPDATE | 更新时填充字段 |
INSERT_UPDATE | 插入和更新时填充字段 |
实现步骤
第一步:指定填充策略
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date lastUpdateTime;
第二步:设置填充值
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
//给对象中的哪个属性赋值,这里的类型必须和属性类型一致
this.strictInsertFill(metaObject, "createTime", () -> new Date(), Date.class); // 起始版本 3.3.3(推荐)
this.strictInsertFill(metaObject, "lastUpdateTime", () -> new Date(), Date.class); // 起始版本 3.3.3(推荐)
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "createTime", () -> new Date(), Date.class); // 起始版本 3.3.3(推荐)
this.strictUpdateFill(metaObject, "lastUpdateTime", () -> new Date(), Date.class); // 起始版本 3.3.3(推荐)
}
}
第三步:测试
@Test
public void testFill(){
Product product = new Product();
product.setPname("华为P60");
product.setPrice(9999D);
//没有设置时间,让MyBatisPlus自动填充值
productMapper.insert(product);
}
Mapper CRUD接口
BaseMapper
通用 CRUD 封装 BaseMapper
接口, 泛型 T
为任意实体对象
public interface ProductMapper extends BaseMapper<Product> {
}
MyBatisPlus 提供了 Wrapper 条件构造器
接口,用于设置条件
- QueryWrapper LambdaQueryWrapper 设置查询、删除条件
- UpdateWrapper LambdaUpdateWrapper 设置修改条件
总结:
- dao层实现 BaseMapper 接口,可以省略大量单表CRUD代码编写
- 如果CRUD需要条件,使用 Wrapper 接口的实现类来设置条件
查询
前面使用BaseMapper接口中的查询所有和根据id查询,开发中很多时候需要设置查询条件和一些复杂查询
设置查询条件
QueryWrapper 实现类提供了大量设置条件的方法
查询方法 | 说明 |
---|---|
eq (equals) | 等于= |
ne (not equals) | 不等与<> |
gt (greater than) | 大于> |
ge (greater equals) | 大于等于>= |
lt (less than) | 小于< |
le (less equals) | 小于等于<= |
like | 模糊查询 LIKE |
notLike | 模糊查询 NOT LIKE |
in | IN 查询 |
notIn | NOT IN 查询 |
isNull | NULL 值查询 |
isNotNull | IS NOT NULL |
需求:查询 库存num > 25 并且创建时间 1997-01-01 <= create_time < 2000-01-01
或者
pname以华为开头的产品信息,结果按 create_time 升序排序
select * from tbl_product
where num > 25
and create_time between '1997-01-01' and '2000-01-01'
or pname like '华为%'
order by create_time asc
- 对应程序如下
@Test
public void testQueryWrapper(){
/**
select * from tbl_product
where num > 25
and create_time between '1997-01-01' and '2000-01-01'
or pname like '华为%'
order by create_time asc
*/
//设置查询条件 (where 后面部分内容)
QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
// num > 25
queryWrapper.gt("num",25);
//and create_time between '1997-01-01' and '2000-01-01'
queryWrapper.between("create_time","1997-01-01","2000-01-01");
//or
queryWrapper.or();
//pname like '华为%'
queryWrapper.likeRight("pname","华为");
//order by create_time asc
queryWrapper.orderByAsc("create_time");
List<Product> list = productMapper.selectList(queryWrapper);
for (Product p : list) {
System.out.println(p);
}
}
查询优化-动态判断条件
问题:前面查询条件都是写死的,没有判断用户是否输入了查询条件
需求: 前台用户可以查询 price > xx 或 pname liek “%xx%” 的数据,此时查询该如何写 ?
查询优化
@Test
public void testParamQuery2(){
//查询 price > xx 并且 pname like "%xx%"
//设置查询条件 (where 后面部分内容)
QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
//price > xx condition:满足条件再设置查询条件
queryWrapper.gt(productVO.getPrice() != null,"price", productVO.getPrice());
//pname like '%xx%' condition:满足条件再设置查询条件
queryWrapper.like(StringUtils.isNotBlank(productVO.getPname()),"pname", productVO.getPname());
List<Product> list = productMapper.selectList(queryWrapper);
for (Product p : list) {
System.out.println(p);
}
}
查询优化-lambda查询
字段名都是写死了,万一字段名称变更,例如 pname 改为 product_name ,每个带有 pname 条件的代码都需要修改
如果做到一改全改?
@Test
public void testLambdaQuery(){
//查询 price > xx 并且 pname like "%xx%"
// 使用lambda()方法进行字段名获取
//QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
通过lambda方式获取字段名称
//queryWrapper.lambda().gt(productVO.getPrice() != null, Product::getPrice, productVO.getPrice());
通过lambda方式获取字段名称
//queryWrapper.lambda().like(StringUtils.isNotBlank(productVO.getPname()),Product::getPname, productVO.getPname());
// 使用 LambdaQueryWrapper方式查询
LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
// 通过lambda方式获取字段名称
queryWrapper.gt(productVO.getPrice() != null, Product::getPrice, productVO.getPrice());
// 通过lambda方式获取字段名称
queryWrapper.like(StringUtils.isNotBlank(productVO.getPname()),Product::getPname, productVO.getPname());
List<Product> list = productMapper.selectList(queryWrapper);
for (Product p : list) {
System.out.println(p);
}
}
原理:
LambdaQueryWrapper 获取指定方法对应属性上的 @TableField 注解的 value 属性值,即为 字段名称
如果 @TableFile(value=“username”) 修改,所有对应的lambda查询字段都会改变
查询优化-链式编程
queryWrapper对象的方法返回的还是父类AbstractWrapper类型对象,因此可以直接继续调用方法
@Test
public void testChainQuery(){
// 使用 LambdaQueryWrapper方式查询
LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
// 链式调用,因为方法返回的还是调用对象
queryWrapper.gt(productVO.getPrice() != null, Product::getPrice, productVO.getPrice())
.like(StringUtils.isNotBlank(productVO.getPname()),Product::getPname, productVO.getPname());
List<Product> list = productMapper.selectList(queryWrapper);
for (Product p : list) {
System.out.println(p);
}
}
设置查询字段
QueryWrapper.select() 可以对select 字段 部分进行设置
需求: 查询库存 num > 25 或者 price > 3000 的商品信息,只展示商品 id、pname、price、num 字段
# SQL语句
select id,pname,price,num
from tbl_product
where num > 25
or
price > 3000
@Test
public void testSelect(){
/*
需求: 查询库存 num > 25 或者 price > 3000 的商品信息,只展示商品 id、pname、price、num 字段
select id,pname,price,num
from tbl_product
where num > 25
or
price > 3000
*/
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
//设置查询的字段 select
wrapper.select(Product::getId, Product::getPname, Product::getPrice,Product::getNum);
//设置查询条件 where
wrapper.gt(Product::getNum,25)
.or()
.gt(Product::getPrice,3000);
List<Product> list = productMapper.selectList(wrapper);
list.forEach(System.out::println);
}
设置别名
QueryWrapper.select() 设置查询字段的别名
需求:查询每个品牌价格 > 3000 的商品各有多少库存
# SQL
select brand, sum(num) as num
from tbl_product tp
where price > 3000
group by brand
@Test
public void testSelectAlias(){
/*
需求:查询每个品牌价格 > 3000 的商品各有多少库存
select brand, sum(num) as count
from tbl_product tp
where price > 3000
group by brand
*/
//设置查询条件
QueryWrapper<Product> wrapper = new QueryWrapper<>();
//设置select字段和别名
wrapper.select("brand","sum(num) as count")
//where条件
.gt("price",3000)
.groupBy("brand");
List<Map<String, Object>> list = productMapper.selectMaps(wrapper);
//遍历list
list.forEach(entry -> {
Set<String> keySet = entry.keySet();
//遍历map中的键值对
keySet.forEach(key -> {
System.out.print(key+":"+entry.get(key)+", ");
});
System.out.println();
});
}
需要在product类中添加 count 属性接收返回结果,并标注该属性在表中不存在
@TableField(exist = false)
private Integer count;
练习
提示: 从 BaseMpper 提供的方法名称 + 返回值类型,判断是否能实现需求
练习1: 查询商品名称以 OPPO开头,价格 > 1500 ,并且是上架状态的商品,结果按价格降序排序 BaseMapper.selectList(wrapper)
Product product = new Product();
product.setPname("OPPO");
product.setPrice(1500.0);
QueryWrapper<Product> wrapper = new QueryWrapper<>();
wrapper.likeRight(product.getPname()!=null,"pname",product.getPname());
wrapper.gt("price",1500);
wrapper.eq("online",1);
wrapper.orderByDesc("price");
List<Product> products = productMapper.selectList(wrapper);
for (Product p : products){
System.out.println(p);
}
练习2: 统计价格 > 5000 的商品数量 BaseMapper.selectCount(wrapper)
QueryWrapper<Product> wrapper = new QueryWrapper<>();
wrapper.gt("price",5000);
Long aLong = productMapper.selectCount(wrapper);
System.out.println(aLong);
练习2: 统计每个品牌各有多少件商品,结果按数量升序排序 BaseMapper.selectMaps(wrapper)
QueryWrapper<Product> wrapper = new QueryWrapper<>();
wrapper.select("brand","SUM(num) num");
wrapper.groupBy("brand");
wrapper.orderByAsc("num");
List<Map<String, Object>> maps1 = productMapper.selectMaps(wrapper);
maps1.forEach(System.out::println);
练习3: 查询每个品牌的最低价、最高价 BaseMapper.selectMaps(wrapper)
QueryWrapper<Product> wrapper = new QueryWrapper<>();
wrapper.select("brand","MIN(price) min","MAX(price) max");
wrapper.groupBy("brand");
List<Map<String, Object>> maps = productMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
练习4: 查询id包含 1,2,6,8 的商品信息 selectBatchIds(Collection)
练习5: 查询id包含 1,2,6,8 并且 pname 不为null, 且是上架状态 的商品信息 (和练习5的区别是这次有条件) BaseMapper.selectList(wrapper)