MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。如果你想对自己的项目进行技术升级,不妨尝试将mybatis换成Mybatis-Plus,你将从此在无比强大的mp大法中沉迷而无法自拔日渐精神!mp 润物无声,只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。并且mp 遵循效率至上,你只需简单配置,即可快速进行 CRUD 操作,从而节省大量时间。同时mp拥有 丰富的功能,热加载、代码生成、分页、性能分析等功能一应俱全。让我们从本篇出发,开启真正的mp开发之旅吧!
为了更加清晰直观地进行讲解,这里我们使用Navicat,创建了mp_practice数据库,同时在数据库中创建了User表,并将初始数据添加
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
使用 Spring Initializr 快速初始化一个 Spring Boot 工程
settings中将group,artifact,type,Java Version,name换成自己需要的
在pom.xml文件中引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.3.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
注意:引入 MyBatis-Plus 之后请不要再次引入 MyBatis,以避免因版本差异导致的问题。
不同版本安装lombok插件方式不同,可以根据自己idea的版本在csdn中搜索自己对应版本的lombok插件安装方法(博主自己的2019版本当初在安装的时候出现过应用商店没有对应版本的lombok的问题,大体解决思路是去官网下载lombok包然后导入本地,可参照此博客)
springboot默认建立的就是application.properties,直接在 application.properties 配置文件中添加 MySQL 数据库的相关配置:
#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mp_practice?characterEncoding=utf-8&useSSL=false
spring.datasource.username=你的username
spring.datasource.password=你的password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mp_practice?serverTimezone=GMT%2B8
spring.datasource.username=你的username
spring.datasource.password=你的password
注意:
1、这里的 url 使用了 ?serverTimezone=GMT%2B8 后缀,因为8.0版本的jdbc驱动需要添加这个后缀,否则运行测试用例报告如下错误:java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more
2、这里的 driver-class-name 使用了 com.mysql.cj.jdbc.Driver ,在 jdbc 8 中 建议使用这个驱动,否则运行测试用例的时候会有 WARN 信息
在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹
@SpringBootApplication
@MapperScan("com.xuhao.mp.mapper")
public class MybatisPlusApplication {
......
}
创建报entity编写实体类User.java(此处使用lombok的@Data)
@Data//自动生成对应的实体类如get、set、equals、hashCode、toString等方法
public class User {
private Long id;//Long切勿写成long
private String name;
private Integer age;
private String email;
}
创建包mapper编写Mapper接口:UserMapper.java
package com.xuhao.mp.mapper;/*
@author: 徐昊
@since: 2023/3/5
*/
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xuhao.mp.entity.User;
public interface UserMapper extends BaseMapper<User> {
}
此时红色波浪线并不代表出现错误,在UserMapper中使用@Repository注解即可
此时红色波浪线消失(强迫症狂喜)
编写测试方法:
@Test
void testSelectList(){
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
@Test
public void testInsert(){
User user = new User();
user.setName("xubanxian");
user.setEmail("[email protected]");
user.setAge(8848);
int result = userMapper.insert(user);//返回值为影响的行数
System.out.println("影响的行数为:"+result);
System.out.println("user id:"+user.getId());
}
这里我们并未对id进行赋值但是却由于mp的主键策略,自动生成了一个全局唯一id
那么,生成了这么长的一个id到底有什么意义呢?
随着业务规模的不断扩大,需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。数据库的扩展方式主要包括:业务分库、主从复制,数据库分表。
业务分库指的是按照业务模块将数据分散到不同的数据库服务器。例如,一个简单的电商网站,包括用户、商品、订单三个业务模块,我们可以将用户数据、商品数据、订单数据分开放到三台不同的数据库服务器上,而不是将所有数据都放在一台数据库服务器上。这样的就变成了3个数据库同时承担压力,系统的吞吐量自然就提高了。
虽然业务分库能够分散存储和访问压力,但同时也带来了新的问题:
(1)join 操作问题
业务分库后,原本在同一个数据库中的表分散到不同数据库中,导致无法使用 SQL 的 join 查询。
(2)事务问题
原本在同一个数据库中不同的表可以在同一个事务中修改,业务分库后,表分散到不同的数据库中,无法通过事务统一修改。
(3)成本问题
业务分库同时也带来了成本的代价,本来 1 台服务器搞定的事情,现在要 3 台,如果考虑备份,那就是 2 台变成了 6 台
读写分离的基本原理是将数据库读写操作分散到不同的节点上。读写分离的基本实现是:
数据库服务器搭建主从集群,一主一从、一主多从都可以。 数据库主机负责读写操作,从机只负责读操作。
数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据。 业务服务器将写操作发给数据库主机,将读操作发给数据库从机。
注意:
这里用的是“主从集群”,而不是“主备集群”。
“从机”的“从”可以理解为“仆从”,仆从是要帮主人干活的,“从机”是需要提供读数据的功能的;
而“备机”一般被认为仅仅提供备份功能,不提供访问功能。
所以使用“主从”还是“主备”,是要看场景的,这两个词并不是完全等同。
将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。
垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。
例如,前面示意图中的 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 万来进行分表,有可能某个分段实际存储的数据量只有 1000 条,而另外一个分段实际存储的数据量有 900 万条。
同样以用户 ID 为例:
假如我们一开始就规划了 10 个数据库表,路由算法可以简单地用 user_id % 10 的值来表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 5 的子表中,ID 为 10086 的用户放到编号为 6 的字表中。
复杂点: 初始表数量的选取。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题。
优点: 表分布比较均匀。
缺点: 扩充新的表很麻烦,所有数据都要重分布。
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性
核心思想:
a、长度共64bit(一个long型)。
b、首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
c、41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。
d、10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
e、12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。
MyBatis-Plus默认的主键策略是:ASSIGN_ID (使用了雪花算法)
@TableId(type = IdType.ASSIGN_ID)
private String id;
需要在创建数据表的时候设置主键自增
实体字段中配置 @TableId(type = IdType.AUTO)
@TableId(type = IdType.AUTO)
private Long id;
#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto
@Test
public void testUpdateById(){
User user = new User();
user.setId(1L);
user.setAge(28);
int result = userMapper.updateById(user);
System.out.println("影响行数:"+result);
}
注意:update时生成的sql自动是动态sql:UPDATE user SET age=? WHERE id=?
在未使用自动填充之前,相应的insert和update方法应该如下:
@Test
public void testInsert(){
User user = new User();
user.setName("xubanxian");
user.setEmail("[email protected]");
user.setAge(8867);
user.setCreateTime(new Date());
user.setUpdateTime(new Date());
int result = userMapper.insert(user);//返回值为影响的行数
System.out.println("影响的行数为:"+result);
System.out.println("user id:"+user.getId());
}
@Test
public void testUpdateById(){
User user = new User();
user.setId(1L);
user.setAge(28);
user.setUpdateTime(new Date());
int result = userMapper.updateById(user);
System.out.println("影响行数:"+result);
}
}
每一次更新我们都要对相应的时间进行代码编写,这实在是太麻烦了。所幸,mp给我们提供了非常方便的代码填充插件,mp真的太懂程序员了!
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。
我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作
在User表中添加datetime类型的新的字段create_time、update_time
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
//mp会自动将驼峰转化成数据库对应的下划线的形式
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
package com.atguigu.mybatisplus.handler;
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1多万。
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。
如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。
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);
package com.atguigu.mybatis_plus.entity;
@Data
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
package com.atguigu.mybatis_plus.mapper;
@Repository
public interface ProductMapper extends BaseMapper<Product> {
}
@Autowired
private ProductMapper productMapper;
/**
* 并发编程一般叫concurrent
*/
@Test
public void testConcurrentUpdate() {
//1、小李
Product p1 = productMapper.selectById(1L);
System.out.println("小李取出的价格:" + p1.getPrice());
//2、小王
Product p2 = productMapper.selectById(1L);
System.out.println("小王取出的价格:" + p2.getPrice());
//3、小李将价格加了50元,存入了数据库
p1.setPrice(p1.getPrice() + 50);
productMapper.updateById(p1);
//4、小王将商品减了30元,存入了数据库
p2.setPrice(p2.getPrice() - 30);
int result = productMapper.updateById(p2);
if(result == 0){//更新失败,重试
//重新获取数据
p2 = productMapper.selectById(1L);
//更新
p2.setPrice(p2.getPrice() - 30);
productMapper.updateById(p2);
}
//最后的结果
Product p3 = productMapper.selectById(1L);
System.out.println("最后的结果:" + p3.getPrice());
}
数据库中添加version字段
取出记录时,获取当前version SELECT id,name,version FROM product WHERE id=1
更新时,version + 1,如果where语句中的version版本不对,则更新失败
UPDATE product SET price=price+50, version=version+ 1 WHERE id=1 AND version=1
接下来介绍如何在Mybatis-Plus项目中,使用乐观锁:
添加 @Version 注解
@Data
public class Product {
private Long id;
private String name;
private int price;
@Version
private int version;
}
创建包config,创建文件MybatisPlusConfig.java,我们从此之后将对mp的配置全部集中在这个类中实现,此时可以删除主类中的 @MapperScan 扫描注解
@EnableTransactionManagement//事务处理
@Configuration
@MapperScan("com.xuhao.mp.mapper")
public class MyBatisPlusConfig {
}
在 MybatisPlusConfig 中注册 Bean
/**
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
/**
* 并发编程一般叫concurrent
*/
@Test
public void testConcurrentUpdate(){
// 1、小李获取数据
Product product1 = productMapper.selectById(1L);
System.out.println("小李去除的价格"+product1.getPrice());
// 2、小王获取数据
Product product2 = productMapper.selectById(1L);
System.out.println("小王获取的数据"+product2.getPrice());
// 3、小李加了50存入数据库
product1.setPrice(product1.getPrice()+50);
productMapper.updateById(product1);
// 4、小王减了30存入数据库
product2.setPrice(product2.getPrice()-30);
int result = productMapper.updateById(product2);//若更新失败,result为0
if (result == 0){
System.out.println("小王更新失败");
//发起重试
product2 = productMapper.selectById(1L);
product2.setPrice(product2.getPrice() - 30);
productMapper.updateById(product2);
}
// 5、输出结果
Product product3 = productMapper.selectById(1L);
System.out.println("最后的结果:"+product3.getPrice());
}
@Test
public void testSelectBatchIds(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
通过map封装查询条件
/**
* 最基本的条件查询
*/
@Test
public void testSelectByMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","xubanxian");
map.put("age",18);
List<User> users = userMapper.selectByMap(map);
for (User user : users) {
System.out.println(user);
}
}
对应的sql语句:
SELECT id,name,age,email,create_time,update_time FROM user WHERE name = ? AND age = ?
注意:map中的key对应数据库中的列名。如:数据库user_id,实体类是userId,这时map的key需要填写user_id
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
配置类中添加@Bean配置
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
测试: 最终通过 page 对象获取相关数据
@Test
public void testSelectPage(){
//第一页,每页五条记录
Page<User> page = new Page<>(1, 5);
//queryWrapper,条件构造器
Page<User> pageParam = userMapper.selectPage(page, null);
List<User> records = pageParam.getRecords();
records.forEach(System.out::println);
System.out.println(pageParam.getCurrent());//当前页码
System.out.println(pageParam.getPages());//总页数
System.out.println(pageParam.getSize());//每页记录数
System.out.println(pageParam.getTotal());//总记录数
System.out.println(pageParam.hasNext());//是否有下一页
System.out.println(pageParam.hasPrevious());//是否上一页
}
对应的sql语句:SELECT id,name,age,email,create_time,update_time FROM user LIMIT 0,5
当指定了特定的查询列时,希望分页结果列表只返回被查询的列,而不是很多null值
测试selectMapsPage分页:结果集是Map
@Test
public void testSelectMapsPage() {
//返回很多null列
//Page page = new Page<>(1, 5);
//QueryWrapper queryWrapper = new QueryWrapper<>();
//queryWrapper.select("name", "age");
//Page pageParam = userMapper.selectPage(page, queryWrapper);
//
//pageParam.getRecords().forEach(System.out::println);
//条件构造器
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.select("id","name");//只查id和name,
//SELECT id,name FROM user LIMIT ?,?
//第一页,每页五条记录
Page<Map<String,Object>> page = new Page<>(1, 5);
//queryWrapper,条件构造器
Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, userQueryWrapper);
List<Map<String, Object>> records = page.getRecords();
records.forEach(System.out::println);
System.out.println(pageParam.getCurrent());
System.out.println(pageParam.getPages());
System.out.println(pageParam.getSize());
System.out.println(pageParam.getTotal());
System.out.println(pageParam.hasNext());
System.out.println(pageParam.hasPrevious());
}
@Test
public void testDeleteById(){
int result = userMapper.deleteById(5L);
System.out.println(result);
}
@Test
public void testDeleteBatchById(){
int result = userMapper.deleteBatchIds(Arrays.asList(7, 8, 9, 10));
System.out.println("删除了:"+result+"行");
}
@Test
public void testDeleteByMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","xubanxian");
map.put("age",18);
int result = userMapper.deleteByMap(map);
System.out.println("删除了:"+result+"行");
}
逻辑删除的使用场景:
添加 deleted字段:ALTER TABLE user ADD COLUMN deleted boolean DEFAULT false
添加deleted 字段,并加上 @TableLogic 注解
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
//mp会自动将驼峰转化成数据库对应的下划线的形式
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@TableLogic //逻辑删除标记
private Integer deleted;
}
application.properties 加入以下配置,此为默认值,如果你的默认值和mp默认的一样,该配置可无
#被删除时为1
mybatis-plus.global-config.db-config.logic-delete-value=1
#未被删除时为0
mybatis-plus.global-config.db-config.logic-not-delete-value=0
逻辑删除的本质是对delete进行修改,语句为:
UPDATE user SET deleted=1 WHERE id=? AND deleted=0
//logic为逻辑的意思
@Test
public void testLogicDelete() {
int result = userMapper.deleteById(1L);
System.out.println(result);
}
Navicat内观察结果:
MyBatis Plus中查询操作也会自动添加逻辑删除字段的判断:
SELECT id,name,age,email,create_time,update_time,deleted FROM user WHERE deleted=0
@Test
public void testLogicDeleteSelect() {
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
控制台运行结果如下:
偶然发现,mp在进行逻辑删除的时候不会触发mp的自动填充(目前还不知道是因为啥),问题如下:
在最刚开始时候id为11的xubanxian的update_time是2023-03-07 21:37:19,此时这个xubanxian的deleted为0,处于非逻辑删除状态。
在上图这个时间的时候博主运行了逻辑删除测试方法,
测试方法运行成功后,deleted状态变为1,此时的11号xubanxian为逻辑删除状态,但是原本应该被自动填充的update_time为实现自动填充,着实怪哉!
目前查到的一个原因好像是低版本的mp会出现这种bug,换成新版本的好像会解决这个bug。
真正的原因目前还没查明白QAQ(不知道屏幕前的你遇没遇到过这种问题,如果有合理的解决方案请发到评论区,万分感谢!)
Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : 查询条件封装(用的最多)
UpdateWrapper : Update 条件封装
AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper(复杂的会用到)
LambdaUpdateWrapper : Lambda 更新封装Wrapper
为了更加清晰地演示,这里博主创建了一个新的测试类
@SpringBootTest
public class QueryWrapperTests {
@Autowired
private UserMapper userMapper;
}
@Test
public void testDelete(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 链式编程
queryWrapper
.eq("age",8858)
.isNotNull("email")
.isNotNull("name");
// queryWrapper.ge("age",18);//大于等于18
// queryWrapper.gt("age",18);//大于18
// queryWrapper.le("age",18);//小于等于18
// queryWrapper.lt("age",18);//下于18
int delete = userMapper.delete(queryWrapper);
System.out.println("删除了"+delete+"行");
}
@Test
public void testSelectOne(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name","Tom");
User user = userMapper.selectOne(queryWrapper);//只能返回一条记录,多余一条则抛出异常
System.out.println(user);
}
sql:SELECT id,name,age,email,create_time,update_time,deleted FROM user WHERE deleted=0 AND (name = ?)
结果:
注意:selectOne()返回的是一条实体记录,当出现多条时会报错
selectOne()可以用于账号的是否唯一的检查中
包含大小边界
@Test
public void testSelectCount(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.between("age",10,30);
List<User> users = userMapper.selectList(queryWrapper);
Integer count = userMapper.selectCount(queryWrapper);
System.out.println("年纪在10-30的有"+count+"人");
System.out.println("分别是:");
users.forEach(System.out::println);
}
sql:SELECT COUNT( 1 ) FROM user WHERE deleted=0 AND (age BETWEEN ? AND ?)
selectMaps()返回Map集合列表,通常配合select()使用
@Test
public void testSelectMaps(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.select("name","age")
.like("name","e")
.likeRight("email","t");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
sql: SELECT name,age FROM user WHERE deleted=0 AND (name LIKE ? AND email LIKE ?)
Parameters: %e%(String), t%(String)
待更新
待更新
待更新
待更新
待更新
更新未完待续……
未来博主会继续将项目中遇到的一些比较好的mp实战代码进行讲解分析,并更新在这一篇博客中,诸君共勉!