1. 创建表
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; 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');
2. pom
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</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>
3. application.yml
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC&useSSL=false spring.datasource.username=root spring.datasource.password=123456
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl mybatis-plus.mapper-locations=classpath:mapper/*.xml
4. 实体类
@Data public class User { private Long id; private String name; private Integer age; private String email; }
5. 添加mapper
public interface UserMapperr extends BaseMapper<User> {
//自定义查询 Map<String,Object> selectMapById(Long id); }
6. 测试不同的方法
@SpringBootTest class ApplicationTests { @Autowired private UserMapperr userMapperr; @Test void contextLoads() { //通过条件构造器查询一个List集合,若没有条件,可以设置null为参数 List<User> users = userMapperr.selectList(null); users.forEach(System.out::println); } @Test void testInsert(){ User user = new User(); user.setName("张三"); user.setAge(23); user.setEmail("zhangsan@qq.com"); //插入方法,默认生成id使用雪花算法,生成分布式id //INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? ) int result = userMapperr.insert(user); System.out.println("result " + result); System.out.println("id " + user.getId()); } @Test void testDelete(){ //1. 通过id删除用户信息 DELETE FROM user WHERE id=? /* int result = userMapperr.deleteById(1516412322837491713L); System.out.println("result " + result);*/ //2. 根据map集合中所设置的条件删除用户信息 DELETE FROM user WHERE name = ? AND age = ? /* Map<String, Object> map = new HashMap<>(); map.put("name", "Billie"); map.put("age", 24); int result = userMapperr.deleteByMap(map); System.out.println("result " + result);*/ //3. 通过多个id实现批量删除 DELETE FROM user WHERE id IN ( ? , ? , ? ) List<Long> list = Arrays.asList(1L, 2L, 3L); int result = userMapperr.deleteBatchIds(list); System.out.println("result " + result); } @Test void testUpdate(){ //修改用户信息 UPDATE user SET name=?, email=? WHERE id=? User user = new User(); user.setId(4L); user.setName("李四"); user.setEmail("lisi@atguigu.com"); int result = userMapperr.updateById(user); System.out.println("result " + result); } @Test void testSelect(){ //1 .通过id查询用户信息 SELECT id,name,age,email FROM user WHERE id=? /* User user = userMapperr.selectById(1L); System.out.println("user " + user);*/ //2. 根据多个id查询多个用户信息 SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? ) /* List<Long> longs = Arrays.asList(1L, 2L, 3L); List<User> users = userMapperr.selectBatchIds(longs); users.forEach(System.out::println);*/ //3. 根据map集合中的条件查询用户信息 SELECT id,name,age,email FROM user WHERE name = ? AND age = ? /* HashMap<String, Object> map = new HashMap<>(); map.put("name","Jack"); map.put("age",20); List<User> users = userMapperr.selectByMap(map); users.forEach(System.out::println);*/ //4. 查询所有数据 SELECT id,name,age,email FROM user /* List<User> users = userMapperr.selectList(null); users.forEach(System.out::println);*/ //5. 使用自定义的查询 select * from user where id = ? Map<String, Object> map = userMapperr.selectMapById(1L); System.out.println("map " + map); } }
使用自定义查询需要写mapper文件
<mapper namespace="com.lewang.mapper.UserMapperr"> <select id="selectMapById" resultType="map"> select * 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
(1)IService
MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑详情查看源码IService和ServiceImpl
(2)创建Service接口和实现类
/** * UserService继承IService模板提供的基础功能 */ public interface UserService extends IService<User> { }
/** * ServiceImpl实现了IService,提供了IService中基础功能的实现 * 若ServiceImpl无法满足业务需求,则可以使用自定的UserService定义方法,并在实现类中实现 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }
(3)测试count和批量添加
@Autowired private UserService userService; @Test void testService(){ // SELECT COUNT( * ) FROM user long count = userService.count(); System.out.println("总数量:" + count); } @Test void testSaveBatch(){ // SQL长度有限制,海量数据插入单条SQL无法实行,因此MP将批量插入放在了通用Service中实现,而不是通用Mapper //INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? ) ArrayList<User> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { User user = new User(); user.setName("abc" + i); user.setAge(10 + i); list.add(user); } userService.saveBatch(list); }
8. 当表名和实体类名不对应时
就会发生查询异常
(1)可以在实体类上使用注解声明
@TableName("t_user") @Data public class User { private Long id; private String name; private Integer age; private String email; }
数据库中的表名为t_user,实体类名为User时,可以在实体类上声明对应的表名
(2)通过全局配置解决问题
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
9. @TableId
(1)经过以上的测试,MyBatis-Plus在实现CRUD时,会默认将id作为主键列,并在插入数据时,默认基于雪花算法的策略生成id
若实体类和表中表示主键的不是id,而是其他字段,例如uid,MyBatis-Plus不会自动识别uid为主键列
@TableName("uuser") @Data public class User { private Long uid; private String name; private Integer age; private String email; }
当表中和实体类中都是用uid时,插入时会发生异常。使用@TableId修饰uid属性将其标识为主键,即可成功执行SQL语句。
(2)@TableId的value属性
若实体类中主键对应的属性为id,而表中表示主键的字段为uid,此时若只在属性id上添加注解@TableId,则抛出异常Unknown column 'id' in 'field list',即MyBatis-Plus仍然会将id作为表的主键操作,而表中表示主键的是字段uid
此时需要通过@TableId注解的value属性,指定表中的主键字段,@TableId("uid")或@TableId(value="uid")
@TableName("uuser") @Data public class User { @TableId(value = "uid") private Long id; private String name; private Integer age; private String email; }
(3)@TableId的type属性
IdType.ASSIGN_ID(默认) 基于雪花算法的策略生成数据id,与数据库id是否设置自增无关
IdType.AUTO 使用数据库的自增策略,注意,该类型请确保数据库设置了id自增,否则无效
IdType.ASSIGN_UUID 基于UUID生成id
@TableName("uuser") @Data public class User {
//设置自增策略 @TableId(value = "uid",type = IdType.AUTO) private Long id; private String name; private Integer age; private String email; }
配置全局主键策略
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
10. @TableField
MyBatis-Plus在执行SQL语句时,要保证实体类中的属性名和表中的字段名一致,如果实体类中的属性名和字段名不一致的情况,就会插入异常
(1)若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格,例如实体类属性userName,表中字段user_name,此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格,相当于在MyBatis中配置。
(2)若实体类中的属性和表中的字段不满足情况1,例如实体类属性username,表中字段name,此时需要在实体类属性上使用@TableField("name")设置属性所对应的字段名
@TableName("uuser") @Data public class User { @TableId(value = "uid",type = IdType.AUTO) private Long id; @TableField("name") private String username; private Integer age; private String email; }
11. @TableLogic
(1)逻辑删除
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
使用场景:可以进行数据恢复
(2)实现逻辑删除
step1:数据库中创建逻辑删除状态列,设置默认值为0
step2:实体类中添加逻辑删除属性
@TableName("uuser") @Data public class User { @TableId(value = "uid",type = IdType.AUTO) private Long id; @TableField("name") private String username; private Integer age; private String email; @TableLogic private Integer isDeleted; }
step3:测试
测试删除功能,真正执行的是修改
UPDATE uuser SET is_deleted=1 WHERE uid IN ( ? , ? , ? ) AND is_deleted=0
测试查询功能,被逻辑删除的数据默认不会被查询
SELECT uid AS id,name AS username,age,email,is_deleted FROM uuser WHERE is_deleted=0
12. 条件构造器和常用接口
wapper介绍
Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : 查询条件封装
UpdateWrapper : Update 条件封装
AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper : Lambda 更新封装Wrapper
(1)QueryWrapper
例1:组装查询条件
@Test void test01(){ //查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息 //SELECT uid AS id,name AS username,age,email,is_deleted FROM uuser WHERE is_deleted=0 AND (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL) QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.like("name","a") .between("age",10,20) .isNotNull("email"); List<User> users = userMapperr.selectList(wrapper); users.forEach(System.out::println); }
例2:组装排序条件
@Test void test02(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); //按年龄降序查询用户,如果年龄相同则按id升序排列 //SELECT uid AS id,name AS username,age,email,is_deleted FROM uuser WHERE is_deleted=0 ORDER BY age DESC,id ASC wrapper.orderByDesc("age").orderByAsc("id"); List<User> users = userMapperr.selectList(wrapper); users.forEach(System.out::println); }
例3:组装删除条件
@Test void test03(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.isNull("email"); //使用逻辑删除 UPDATE uuser SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL) int delete = userMapperr.delete(wrapper); System.out.println(delete); }
例4:修改
@Test void test04(){ QueryWrapper<User> wrapper = new QueryWrapper<>();
////将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改 //UPDATE uuser SET name=?, email=? WHERE is_deleted=0 AND (age > ? AND name LIKE ? OR email IS NULL) wrapper.gt("age",20).like("name","a") .or() .isNull("email"); User user = new User(); user.setUsername("小木"); user.setEmail("ll@qq.com"); int update = userMapperr.update(user, wrapper); System.out.println("更新结果:" + update); }
条件优先级
@Test void test05(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); //将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改 //UPDATE uuser SET name=?, email=? WHERE is_deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL)) //lambda表达式内的逻辑优先运算 wrapper.like("name","a") .and(i->i.gt("age",20).or().isNull("email")); User user = new User(); user.setUsername("西斯"); user.setEmail("eee@qq.com"); int update = userMapperr.update(user, wrapper); System.out.println("update" + update); }
例5:组装select子句
@Test void test06(){ //查询用户信息的username和age字段 //SELECT name,age FROM uuser WHERE is_deleted=0 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.select("name","age"); //selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null List<Map<String, Object>> maps = userMapperr.selectMaps(wrapper); maps.forEach(System.out::println); }
例6:实现子查询
@Test void test07(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); //查询id小于等于3的用户信息 //SELECT uid AS id,name AS username,age,email,is_deleted FROM uuser WHERE is_deleted=0 AND (uid IN (select uid from uuser where uid <= 3)) wrapper.inSql("uid","select uid from uuser where uid <= 3"); List<User> users = userMapperr.selectList(wrapper); users.forEach(System.out::println); }
(2)UpdateWrapper
@Test void test08(){ UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); //将(年龄大于20或邮箱为null)并且用户名中包含有a的用户信息修改 //UPDATE uuser SET name=?,email=? WHERE is_deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL)) updateWrapper.like("name","a") .and(i->i.gt("age",10).or().isNull("email")); updateWrapper.set("name","heheee").set("email","faefa@qq.com"); int update = userMapperr.update(null, updateWrapper); System.out.println("update:" + update); }
13. condition
在真正开发的过程中,组装条件是常见的功能,而这些条件数据来源于用户输入,是可选的,因此我们在组装这些条件时,必须先判断用户是否选择了这些条件,若选择则需要组装该条件,若没有选择则一定不能组装,以免影响SQL执行的结果
例1
@Test void test09(){ String username = ""; Integer ageBegin = null; Integer ageEnd = 30; QueryWrapper<User> wrapper = new QueryWrapper<>(); if (StringUtils.isNotBlank(username)){ wrapper.like("name",username); } if (ageBegin != null){ wrapper.ge("age",ageBegin); } if (ageEnd !=null){ wrapper.le("age",ageEnd); } //SELECT uid AS id,name AS username,age,email,is_deleted FROM uuser WHERE is_deleted=0 AND (age <= ?) List<User> users = userMapperr.selectList(wrapper); users.forEach(System.out::println); }
(2)上面的实现方案没有问题,但是代码比较复杂,我们可以使用带condition参数的重载方法构建查询条件,简化代码的编写
@Test void test10(){ String username = "a"; Integer ageBegin = null; Integer ageEnd = 30; QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.like(StringUtils.isNotBlank(username),"name",username) .ge(ageBegin != null,"age",ageBegin) .le(ageEnd != null,"age",ageEnd); //SELECT uid AS id,name AS username,age,email,is_deleted FROM uuser WHERE is_deleted=0 AND (name LIKE ? AND age <= ?) List<User> users = userMapperr.selectList(wrapper); users.forEach(System.out::println); }
14. LambdaQueryWrapper
@Test void test11(){ String username = "a"; Integer ageBegin = null; Integer ageEnd = 30; LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); //避免使用字符串表示字段,可以直接使用User::getUsername的方式 queryWrapper.like(StringUtils.isNotBlank(username),User::getUsername,username) .ge(ageBegin != null,User::getAge,ageBegin) .le(ageEnd != null,User::getAge,ageEnd); //SELECT uid AS id,name AS username,age,email,is_deleted FROM uuser WHERE is_deleted=0 AND (name LIKE ? AND age <= ?) List<User> users = userMapperr.selectList(queryWrapper); users.forEach(System.out::println); }
15. LambdaUpdateWrapper
@Test void test12(){ LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>(); //为了防止直接写字符串出现问题,可以使用函数式写法 updateWrapper.like(User::getUsername,"a") .and(i->i.gt(User::getAge,20).or().isNull(User::getEmail)); updateWrapper.set(User::getUsername,"huihui").set(User::getEmail,"fdsa@qq.com"); //UPDATE uuser SET name=?,email=? WHERE is_deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL)) int update = userMapperr.update(null, updateWrapper); System.out.println("update++++" + update); }
16. 分页相关
a. 使用内置函数,分页相关数据获取
(1)分页插件配置
@Configuration @MapperScan("com.lewang.mapper") public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
(2)分页相关数据获取
@Test void testPage(){ Page<User> page = new Page<>(3,3); //SELECT uid AS id,name AS username,age,email,is_deleted FROM uuser WHERE is_deleted=0 LIMIT ? userMapperr.selectPage(page, null); List<User> records = page.getRecords(); records.forEach(System.out::println); System.out.println("当前页:"+page.getCurrent()); System.out.println("每页显示的条数:"+page.getSize()); System.out.println("总记录数:"+page.getTotal()); System.out.println("总页数:"+page.getPages()); System.out.println("是否有上一页:"+page.hasPrevious()); System.out.println("是否有下一页:"+page.hasNext()); }
b. 自定义分页函数
(1)UserMapper中定义接口方法
Page<User> selectPageVo(@Param("page") Page<User> page,@Param("age") Integer age);
(2)UserMapper.xml中编写SQL
<mapper namespace="com.lewang.mapper.UserMapperr"> <select id="selectPageVo" resultType="com.lewang.pojo.User"> select * from uuser where age > #{age} </select> </mapper>
(3)测试
@Test void testSelfPage(){ Page<User> page = new Page<>(1,5); userMapperr.selectPageVo(page,10); List<User> userList = page.getRecords(); userList.forEach(System.out::println); }
17. 通用枚举
表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现
(1)数据库表添加字段sex
(2)创建通用枚举类型
@Getter public enum SexEnum { MALE(1,"男"), FEMALE(2,"女"); @EnumValue private int sex; private String sexName; SexEnum(int sex, String sexName) { this.sex = sex; this.sexName = sexName; } }
(3)配置扫描通用枚举
# 配置扫描通用枚举 mybatis-plus.type-enums-package=com.lewang.enums
(4)在实体类上使用属性使用枚举类型
@TableName("uuser") @Data public class User { @TableId(value = "uid",type = IdType.AUTO) private Long id; @TableField("name") private String username; private Integer age; private String email; private SexEnum sex; @TableLogic private Integer isDeleted; }
(5)测试
@Test void testEnum(){ User user = new User(); user.setUsername("enum"); user.setAge(20); user.setSex(SexEnum.MALE); //INSERT INTO uuser ( name, age, sex ) VALUES ( ?, ?, ? ) int insert = userMapperr.insert(user); System.out.println("数据插入:" + insert); }
表中插入的数据性别是枚举类型