今天依然很困 11.27
还有这么多(;д;) 我太难了
狗屎,我觉得今天也搞不完11.28
今晚摸鱼(大草)11.29
哦哦,好几天没看这个了,今天完成它12.2
打扰了,今天继续12.4
文章目录
初识
Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
它已经封装好了一些crud方法,我们不需要再写xml了,直接调用这些方法就行,就类似于JPA。(话说JPA是啥(等把手头上的看完再去了解好了)
集成MyBatis-Plus非常的简单,只需要引入 starter 工程,并配置 mapper 扫描路径即可。
Mapper 继承BaseMapper接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
特性
https://blog.csdn.net/magician_Code/article/details/102950173
- 无侵入:Mybatis-Plus 在 Mybatis 的基础上进行扩展,只做增强不做改变,引入 Mybatis-Plus 不会对您现有的Mybatis 构架产生任何影响,而且 MP 支持所有 Mybatis 原生的特性
- 依赖少:仅仅依赖 Mybatis 以及 Mybatis-Spring
- 损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作
- 预防Sql注入:内置Sql注入剥离器,有效预防Sql注入攻击
- 通用CRUD操作:内置通用 Mapper、通用Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 多种主键策略:支持多达4种主键策略(内含分布式唯一ID生成器),可自由配置,完美解决主键问题 (?
- 支持ActiveRecord:支持ActiveRecord 形式调用,实体类只需继承 Model 类即可实现基本 CRUD 操作 (?
- 支持代码生成:采用代码或者 Maven插件可快速生成 Mapper 、 Model 、 Service 、 Controller层代码,支持模板引擎,更有超多自定义配置等您来使用(比 Mybatis 官方的 Generator 更加强大)
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 支持关键词自动转义:支持数据库关键词(order、key……)自动转义,还可自定义关键词
- 内置分页插件:基于Mybatis物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询
- 内置性能分析插件:可输出Sql语句以及其执行时间,建议开发测试时启用该功能,能有效解决慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,预防误操作
Spring Boot
导入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
注:引入 MyBatis-Plus 之后请不要再次引入 MyBatis 以及 MyBatis-Spring,以避免因版本差异导致的问题。
通用Mapper - CRUD接口
前排提醒:为了方便看到MP操作数据库时使用的SQL语句,把mapper包或者项目日志级别logging.level配置为TRACE
。
通用 CRUD 封装
BaseMapper
接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器
泛型T
为任意实体对象
参数Serializable
为任意类型主键, Mybatis-Plus 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键
对象Wrapper
为 条件构造器
————————————————
版权声明:本文为CSDN博主「magic__Coder」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/magician_Code/article/details/102950173
DAO层的数据库操作接口通过继承通用Mappercom.baomidou.mybatisplus.core.mapper.BaseMapper
来使用MP提供的CRUD能力,使得我们可以不用写SQL语句,更简便的操作数据库。
创建之前提到的两个DO类对应的DAO操作类:
public interface TeamMapper extends BaseMapper<Team> {
}
public interface PlayerMapper extends BaseMapper<Player> {
}
在Spring Boot启动类中使用@MapperScan
配置Mapper包扫描。
MP的默认主键策略
值 | 描述 |
---|---|
AUTO | 数据库自增 |
INPUT | 数据库自增 |
ID_WORKER | 分布式全局唯一ID 长整型类型 |
UUID | 32位UUID字符串 |
NONE | 无状态 |
ID_WORKER_STR | 分布式全局唯一ID 字符串类型 |
MP默认的主键策略是ID_WORKER,所以我们Insert
操作生成的数据id
为一串数字。要修改MP的主键策略,可以通过全局和局部的配置。
全局配置主键是MP配置项(MP配置大全)的一部分,通过在Spring Boot的配置文件中配置mybatis-plus.global-config.db-config.id-type
即可。
局部配置则是通过一个成员变量注解@TableId
来实现,以下是该注解的描述:
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | " " | 主键字段名 |
type | Enum | 否 | IdType.NONE | 主键类型 |
TableId注解除了声明Id策略的type外,还有一个名为value的属性,这个属性定义的是当我们的数据表实体的主键名。
在MP中,默认的数据库主键名为id,如果我们表实体主键不是起名为id,那么我们就需要配置这个属性。
我们可以看到,声明的DO类默认的Id策略是NONE,这个时候就受全局配置的影响。我们可以尝试把MP全局主键策略配置为AUTO,即自增长策略,注意,这时也需要数据表启用自增长策略。
Select操作
Select
相关方法:
T selectById(Serializable id)
;// 根据 ID 查询List<T>selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 查询List<T>selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object>columnMap);
// 查询(根据 columnMap 条件)T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据entity 条件,查询一条记录Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据entity 条件,查询全部记录List<Map<String, Object>>selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据Wrapper 条件,查询全部记录List<Object> selectObjs(@Param(Constants.WRAPPER)Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录(并翻页)List item IPage<Map<String, Object>> selectMapsPage(IPage<T> page @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据Wrapper条件,查询全部记录(并翻页)
MP中的Select和Delete都提供了多种操作,总的来说可以分成根据Id操作、根据columnMap操作(Map设置表字段和值)和根据Wrapper操作(Wrapper是MP提供的条件构造器,可以实现很强大的功能)。
上面最后两个是关于分页的查询,这部分将在本文后面进行讲解。
根据Id和根据columnMap操作相对比较简单,实现的功能也相对有限,这里先看几个例子:
@Test
public void testSelectById() {
// 查找Id为1的球队
Team team = teamMapper.selectById(1L);
// MP执行的SQL:
// Preparing: SELECT id,city,name FROM team WHERE id=?
// Parameters: 1(Long)
Assertions.assertNotNull(team);
// 查找Id为1、2、3的球队
List<Team> teamList = teamMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
// MP执行的SQL:
// Preparing: SELECT id,city,name FROM team WHERE id IN ( ? , ? , ? )
// Parameters: 1(Long), 2(Long), 3(Long)
Assertions.assertNotEquals(0, teamList.size());
}
@Test
public void testSelectByMap() {
// 查找洛杉矶的球队
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("city", "Los Angeles"); // Key值为数据库列名
List<Team> teamList = teamMapper.selectByMap(columnMap);
// MP执行的SQL:
// Preparing: SELECT id,city,name FROM team WHERE city = ?
// Parameters: Los Angeles(String)
Assertions.assertNotEquals(0, teamList.size());
}
条件构造器
MP关于条件构造器主要涉及到AbstractWrapper
、QueryWrapper(LambdaQueryWrapper)
和UpdateWrapper(LambdaUpdateWrapper)
。
AbstractWrapper
QueryWrapper(LambdaQueryWrapper)
和UpdateWrapper(LambdaUpdateWrapper)
的父类,用于生成sql
的where
条件,entity
属性也用于生成sql的where条件。- 注意:
entity
生成的where
条件与使用各个api生成的where条件没有任何关联行为。
QueryWrapper
- 继承自
AbstractWrapper
,自身的内部属性entity
也用于生成where
条件,LambdaQueryWrapper
可以通过new QueryWrapper().lambda()
方法获取。
- 继承自
UpdateWrapper
- 继承自
AbstractWrapper
,自身的内部属性entity
也用于生成where
条件,LambdaUpdateWrapper
可以通过new UpdateWrapper().lambda()
方法获取。
- 继承自
条件构造器包含了基本上所有的SQL操作,光看概念会有点抽象,接下来我们用一些实际的例子来近距离感受下条件构造器的强大功能。
@SpringBootTest
class PlayerMapperTest {
@Autowired
private PlayerMapper playerMapper;
/*
1.球员姓名包含Kyle且号码小于40的球员
name LIKE '%Kyle%' AND no < 40
*/
@Test
public void testQuery1() {
QueryWrapper<Player> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "Kyle").lt("no", 40);
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (name LIKE ? AND no < ?)
// Parameters: %Kyle%(String), 40(Integer)
Assertions.assertNotEquals(0, playerList.size());
}
/*
2.球员姓名包含Kyle且号码大于等于0且小于等于20并且team_id不为空
name LIKE '%Kyle%' AND no BETWEEN 20 AND 40 AND team_id IS NOT NULL
*/
@Test
public void testQuery2() {
QueryWrapper<Player> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "Kyle")
.between("no", 0, 20)
.isNotNull("team_id");
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (name LIKE ? AND no BETWEEN ? AND ? AND team_id IS NOT NULL)
// Parameters: %Kyle%(String), 0(Integer), 20(Integer)
Assertions.assertNotEquals(0, playerList.size());
}
/*
3.球员姓名以Anthony开头或者球员号码大于等于25,按照号码降序排序,号码相同按照team_id升序排序
name LIKE 'Anthony%' OR no >= 25 ORDER BY no DESC, team_id ASC
*/
@Test
public void testQuery3() {
QueryWrapper<Player> queryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("name", "Anthony") // 以 LIKE 'X%' 形式
.or().gt("no", 25) // 调用 .or() 即可以 or 形式连接
.orderByDesc("no")
.orderByAsc("team_id");
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (name LIKE ? OR no > ?) ORDER BY no DESC , team_id ASC
// Parameters: Anthony%(String), 25(Integer)
Assertions.assertNotEquals(0, playerList.size());
}
/*
4.球员姓名包含Anthony并且所属球队为Lakers
name LIKE '%Anthony%' AND team_id IN (SELECT id FROM team WHERE name = 'Lakers')
*/
@Test
public void testQuery4() {
QueryWrapper<Player> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "Anthony")
.inSql("team_id", "SELECT id FROM team WHERE name = 'Lakers'"); // in 操作
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (name LIKE ? AND team_id IN (SELECT id FROM team WHERE name = 'Lakers'))
// Parameters: %Anthony%(String)
Assertions.assertNotEquals(0, playerList.size());
}
/*
5.球员姓名包含Kyle并且(球员号码小于40或者team_id不为空)
name LIKE '%Kyle%' AND (no < 40 OR team_id IS NOT NULL)
*/
@Test
public void testQuery5() {
QueryWrapper<Player> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "Kyle")
// .and(qw -> qw.lt("no", 40).or().isNotNull("team_id")) // AND 嵌套
.nested(qw -> qw.lt("no", 40).or().isNotNull("team_id")); // 正常嵌套 不带 AND 或者 OR
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (name LIKE ? AND ( (no < ? OR team_id IS NOT NULL) ))
// Parameters: %Kyle%(String), 40(Integer)
Assertions.assertNotEquals(0, playerList.size());
}
/*
6.球员姓名以Kyle开头或者(球员号码小于40并且号码大于0并且team_id不为空)
name LIKE 'Kyle%' AND (no < 40 AND no > 0 AND team_id IS NOT NULL)
*/
@Test
public void testQuery6() {
QueryWrapper<Player> queryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("name", "Kyle")
// .or(qw -> qw.lt("no", 40).gt("no", 0).isNotNull("team_id")) // OR 嵌套
.or().nested(qw -> qw.lt("no", 40).gt("no", 0).isNotNull("team_id")); // 正常嵌套 不带 AND 或者 OR
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (name LIKE ? OR ( (no < ? AND no > ? AND team_id IS NOT NULL) ))
// Parameters: Kyle%(String), 40(Integer), 0(Integer)
Assertions.assertNotEquals(0, playerList.size());
}
/*
7.(球衣号码小于40或team_id不为空)并且姓名以LeBron开头
(no < 40 OR team_id IS NOT NULL) AND name LIKE 'LeBron%'
*/
@Test
public void testQuery7() {
QueryWrapper<Player> queryWrapper = new QueryWrapper<>();
queryWrapper.nested(qw -> qw.lt("no", 40).or().isNotNull("team_id"))
.likeRight("name", "LeBron");
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (( (no < ? OR team_id IS NOT NULL) ) AND name LIKE ?)
// Parameters: 40(Integer), LeBron%(String)
Assertions.assertNotEquals(0, playerList.size());
}
/*
8.球员号码为3, 13, 23, 33
no in (3, 13, 23, 33)
*/
@Test
public void testQuery8() {
QueryWrapper<Player> queryWrapper = new QueryWrapper<>();
queryWrapper.in("no", Arrays.asList(3, 13, 23, 33));
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (no IN (?,?,?,?))
// Parameters: 3(Integer), 13(Integer), 23(Integer), 33(Integer)
Assertions.assertNotEquals(0, playerList.size());
}
/*
9.limit
LIMIT 1
*/
@Test
public void testQuery9() {
QueryWrapper<Player> queryWrapper = new QueryWrapper<>();
queryWrapper.in("no", Arrays.asList(3, 13, 23, 33))
.last("limit 1"); // 无视优化规则直接拼接到 sql 的最后;只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (no IN (?,?,?,?)) limit 1
// Parameters: 3(Integer), 13(Integer), 23(Integer), 33(Integer)
Assertions.assertNotEquals(0, playerList.size());
}
/*
10.select
SELECT id, name
FROM player
*/
@Test
public void testQuery10() {
QueryWrapper<Player> queryWrapper = new QueryWrapper<>();
queryWrapper.select(Player.class, i -> i.getColumn().equals("id")||i.getColumn().equals("name")) // 投影操作,指定返回的列
//.select("id", "name") // 投影操作,指定返回的列
.in("no", Arrays.asList(3, 13, 23, 33));
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,name FROM player WHERE (no IN (?,?,?,?))
// Parameters: 3(Integer), 13(Integer), 23(Integer), 33(Integer)
Assertions.assertNotEquals(0, playerList.size());
}
/*
11.使用统计函数
SELECT team_id, AVG(no) avg_no, MAX(no) max_no
FROM player
GROUP BY team_id
HAVING SUM(no) < 500
*/
@Test
public void testQuery11() {
QueryWrapper<Player> queryWrapper = new QueryWrapper<>();
queryWrapper.select("team_id", "AVG(no) avg_no", "MAX(no) max_no") // select 可以直接使用聚合函数并且起别名
.groupBy("team_id") // group by
.having("max_no < 500"); // 通过 sql 指定 having
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT team_id,AVG(no) avg_no,MAX(no) max_no FROM player GROUP BY team_id HAVING SUM(no) < 500
// Parameters:
Assertions.assertNotEquals(0, playerList.size());
}
}
通用Mapper
还提供了selectOne
、selectMaps
等方法,大家可以自行设计应用场景来熟悉我们没有使用到的方法,调用方法都类似。
另外,这里再介绍一个更优雅的查询方式,使用QueryChainWrapper
进行操作,一步到位:
@Test
public void testQueryChain() {
QueryChainWrapper<Player> queryChainWrapper = new QueryChainWrapper<>(playerMapper);
List<Player> playerList = queryChainWrapper.eq("team_id", 1)
.list(); // 其底层是调用了 BaseMapper.selectList
Assertions.assertNotEquals(0, playerList.size());
// MP执行的SQL:
// Preparing: SELECT team_id,AVG(no) avg_no,MAX(no) max_no FROM player GROUP BY team_id HAVING max_no < 500
// Parameters:
}
之前还提到了LambdaQueryWrapper
,使用方法跟QueryWrapper
类似,而且LambdaQueryWrapper
可以定制查询的列:
@Test
public void testQueryLambda() {
// 三种构建 LambdaQueryWrapper 的方法:
//LambdaQueryWrapper<Player> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//LambdaQueryWrapper<Player> lambdaQueryWrapper = new QueryWrapper<Player>().lambda();
LambdaQueryWrapper<Player> lambdaQueryWrapper = Wrappers.lambdaQuery();
lambdaQueryWrapper.eq(Player::getTeamId, 1L);
List<Player> playerList = playerMapper.selectList(lambdaQueryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (team_id = ?)
// Parameters: 1(Long)
Assertions.assertNotEquals(0, playerList.size());
LambdaQueryChainWrapper<Player> lambdaQueryChainWrapper = new LambdaQueryChainWrapper<>(playerMapper);
playerList = lambdaQueryChainWrapper.eq(Player::getTeamId, 1L).list();
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (team_id = ?)
// Parameters: 1(Long)
Assertions.assertNotEquals(0, playerList.size());
}
不管是QueryWrapper
还是LambdaQueryWrapper
,除了上面提到的构造方法外,都有对应的传入一个entity
的构造方法,这时候MP在构造WHERE时会把entity的非空属性以AND的形式直接添加,并且与条件构造器本身设置的条件不冲突。但是以entity的形式只是简单的用=条件,例如:
@Test
public void testQueryEntity() {
Player player = new Player();
player.setName("James");
QueryWrapper<Player> queryWrapper = new QueryWrapper<>(player);
queryWrapper.eq("team_id", 1L);
List<Player> playerList = playerMapper.selectList(queryWrapper);
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE name=? AND (team_id = ?)
// Parameters: James(String), 1(Long)
Assertions.assertNotEquals(0, playerList.size());
}
这段单元测试会失败,因为playerList.size()
为0,这是由于查询条件name
使用了=
,如果我们希望name
以LIKE
的形式来构建查询,那么我们可以在name属性上设置@TableField(condition = SqlCondition.LIKE)
,这时单元测试就跟我们预想的一样了。
UPDATE&DELETE
通用Service
MP的另一大特色是框架为我们提供了通用Service,使得我们只需关注系统业务。
- 通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用
get 查询单行
、remove 删除
、list 查询集合
、page 分页
前缀命名方式区分 Mapper 层避免混淆。- 泛型
T
为任意实体对象 建议如果存在自定义通用 Service方法的可能,请创建自己的IBaseService
继承Mybatis-Plus
提供的基类
对象Wrapper
为 条件构造器
通用Service的操作跟通用Mapper类似,区别是按照Service层规范
把DAO
层的insert
、select
、delete
改成了save
、get\list
和remove
。
现在我们也创建Service层
的接口和实现类:
public interface TeamService extends IService<Team> {
}
@Service
@Transactional
public class TeamServiceImpl extends ServiceImpl<TeamMapper, Team> implements TeamService {
}
public interface PlayerService extends IService<Player> {
}
@Service
@Transactional
public class PlayerServiceImpl extends ServiceImpl<PlayerMapper, Player> implements PlayerService {
}
使用通用Service非常简单,声明接口时继承IService
就可以拥有MP提供的通用Service
的能力;实现类除了实现接口外还需要继承MP的ServiceImpl
类,并传入通用Mapper和对应的DO对象。
下面我们直接通过几个例子来使用MP的通用Service:
@SpringBootTest
class TeamServiceTest {
@Autowired
TeamService teamService;
@Test
public void testSave() {
Team team = new Team();
team.setName("Nuggets");
team.setCity("Denver");
boolean result = teamService.save(team);
Assertions.assertTrue(result);
Team team1 = new Team();
team1.setName("Grizzlies");
team1.setCity("Memphis");
Team team2 = new Team();
team2.setName("NETS");
team2.setCity("Brooklyn");
Team team3 = new Team();
team3.setName("Thunder");
team3.setCity("Oklahoma");
result = teamService.saveBatch(Arrays.asList(team1, team2, team3));
Assertions.assertTrue(result);
}
@Test
public void testSelect() {
Team team = teamService.getOne(Wrappers.<Team>query().eq("id", 1L), false);
Assertions.assertNotNull(team);
// MP执行的SQL:
// Preparing: SELECT id,city,name FROM team WHERE (id = ?)
// Parameters: 1(Long)
List<Team> teamList = teamService.list(Wrappers.<Team>query().le("id", 5L));
Assertions.assertNotEquals(0, teamList.size());
// MP执行的SQL:
// Preparing: SELECT id,city,name FROM team WHERE (id <= ?)
// Parameters: 5(Long)
}
@Test
public void testUpdate() {
Team team = new Team();
team.setId(1L);
team.setName("Laker");
boolean result = teamService.saveOrUpdate(team); // saveOrUpdate 会先执行 SELECT BY Id 操作,如果找到有记录则进行 UPDATE 操作,否则进行 INSERT 操作
Assertions.assertTrue(result);
// MP执行的SQL:
// Preparing: SELECT id,city,name FROM team WHERE id=?
// Parameters: 1(Long)
// Preparing: UPDATE team SET name=? WHERE id=?
// Parameters: Laker(String), 1(Long)
team = new Team();
team.setId(100L);
team.setName("Blazers");
team.setCity("Portland");
result = teamService.saveOrUpdate(team);
Assertions.assertTrue(result);
// MP执行的SQL:
// Preparing: SELECT id,city,name FROM team WHERE id=?
// Parameters: 100(Long)
// Preparing: INSERT INTO team ( id, city, name ) VALUES ( ?, ?, ? )
// Parameters: 100(Long), Portland(String), Blazers(String)
teamService.update(Wrappers.<Team>update().eq("id", 1L).set("name", "Lakers"));
// MP执行的SQL:
// Preparing: UPDATE team SET name=? WHERE (id = ?)
// Parameters: Lakers(String), 1(Long)
}
}
我们可以看到,通用Service和通用Mapper操作方式基本保持一致,需要注意的是,通用Service的save和update操作返回值是boolean类型。另外,我们还可以用链式操作以更优雅的方式进行操作:
@Test
public void testChain() {
List<Team> teamList = teamService.query().select("id", "name").le("id", 5L).list();
Assertions.assertNotEquals(0, teamList.size());
// MP执行的SQL:
// Preparing: SELECT id,name FROM team WHERE (id <= ?)
// Parameters: 5(Long)
}
分页查询
在实际项目中用得最多也比较重要的业务应该就是分页操作了,MP对分页操作也做了插件支持。
在Spring Boot项目中使用MP的分页功能也非常简单,只需要在容器中注入PaginationInterceptor对象
即可。这里以@Configuration
配置:
@EnableTransactionManagement
@Configuration
public class MyBatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
return paginationInterceptor;
}
}
有了这个配置,就可以在项目中使用MP的分页插件了。其中通用Mapper和通用Service都提供了分页查询的相关功能,我们以通用Service为例使用MP的分页功能。
首先我们来看一下分页功能最重要的一个接口IPage
,这里直接上源码:
public interface IPage<T> extends Serializable {
/**
* 降序字段数组
*
* @return order by desc 的字段数组
* @see #orders()
*/
@Deprecated
default String[] descs() {
return null;
}
/**
* 升序字段数组
*
* @return order by asc 的字段数组
* @see #orders()
*/
@Deprecated
default String[] ascs() {
return null;
}
/**
* 获取排序信息,排序的字段和正反序
*
* @return 排序信息
*/
List<OrderItem> orders();
/**
* KEY/VALUE 条件
*
* @return ignore
*/
default Map<Object, Object> condition() {
return null;
}
/**
* 自动优化 COUNT SQL【 默认:true 】
*
* @return true 是 / false 否
*/
default boolean optimizeCountSql() {
return true;
}
/**
* 进行 count 查询 【 默认: true 】
*
* @return true 是 / false 否
*/
default boolean isSearchCount() {
return true;
}
/**
* 计算当前分页偏移量
*/
default long offset() {
return getCurrent() > 0 ? (getCurrent() - 1) * getSize() : 0;
}
/**
* 当前分页总页数
*/
default long getPages() {
if (getSize() == 0) {
return 0L;
}
long pages = getTotal() / getSize();
if (getTotal() % getSize() != 0) {
pages++;
}
return pages;
}
/**
* 内部什么也不干
* <p>只是为了 json 反序列化时不报错</p>
*/
default IPage<T> setPages(long pages) {
// to do nothing
return this;
}
/**
* 分页记录列表
*
* @return 分页对象记录列表
*/
List<T> getRecords();
/**
* 设置分页记录列表
*/
IPage<T> setRecords(List<T> records);
/**
* 当前满足条件总行数
*
* @return 总条数
*/
long getTotal();
/**
* 设置当前满足条件总行数
*/
IPage<T> setTotal(long total);
/**
* 当前分页总页数
*
* @return 总页数
*/
long getSize();
/**
* 设置当前分页总页数
*/
IPage<T> setSize(long size);
/**
* 当前页,默认 1
*
* @return 当前页
*/
long getCurrent();
/**
* 设置当前页
*/
IPage<T> setCurrent(long current);
/**
* IPage 的泛型转换
*
* @param mapper 转换函数
* @param <R> 转换后的泛型
* @return 转换泛型后的 IPage
*/
@SuppressWarnings("unchecked")
default <R> IPage<R> convert(Function<? super T, ? extends R> mapper) {
List<R> collect = this.getRecords().stream().map(mapper).collect(toList());
return ((IPage<R>) this).setRecords(collect);
}
}
其中对我们比较重要的属性有current(当前页数)
、size(每页记录数)
、pages(总页数)
、total(总记录数)
和records(分页数据)
。
MP为我们提供了一个简单IPage实现:Page类。下面是一个简单的分页查询例子:
@Test
public void testPage() {
// 设置分页条件
Page<Player> page = new Page<>();
page.setCurrent(1);
page.setSize(5);
playerService.page(page, Wrappers.<Player>query().eq("team_id", 1L));
// MP执行的SQL:
// Preparing: SELECT COUNT(1) FROM player WHERE (team_id = ?)
// Parameters: 1(Long)
// Preparing: SELECT id,no,team_id,name FROM player WHERE (team_id = ?) LIMIT ?,?
// Parameters: 1(Long), 0(Long), 5(Long)
//page.getPages(); // 总页数
//page.getTotal(); // 总记录数
// 分页结果保存在 page 的 records 属性中
Assertions.assertNotEquals(0, page.getRecords().size());
page.setSearchCount(false); // 是否需要查询总记录数,这里设置不需要
playerService.page(page, Wrappers.<Player>query().eq("team_id", 1L));
// MP执行的SQL:
// Preparing: SELECT id,no,team_id,name FROM player WHERE (team_id = ?) LIMIT ?,?
// Parameters: 1(Long), 0(Long), 5(Long)
Assertions.assertNotEquals(0, page.getRecords().size());
}
这里有一点需要注意,就是当业务对表的总记录数不关心的时候,可以通过Page.setSearchCount(false)
方法来避免执行SELECT COUNT(1)
操作。
很多时候我们的分页查询的业务会非常复杂,通过条件构造器也可能不能实现我们的需求,这时候就需要我们自己写SQL了,这不正是MyBatis的强项吗。好在MP对MyBatis原有的特性完全支持,使得我们可以自定义SQL。接下来我们重点讲解MP自定义SQL。
使用自定义SQL
普通操作(CURD)自定义SQL的MP打开方式:
在Mapper中添加一个名为"ew
"(MP规定,可以用Constants.WRAPPER
这个值为"ew"的常量)的Wrapper类型参数:
@Select("SELECT * FROM team ${ew.customSqlSegment}") // Wrapper.customSqlSegment为自定义SQL的内容
List<Team> findMP(@Param(Constants.WRAPPER) Wrapper<Team> wrapper);
调用时传入一个条件构造器即可,但需要注意的是,MP的这种自定义SQL的方式只能添加到WHERE条件,但WHERE中的判断逻辑可以省略(如AND或OR的拼接,都交给了Wrapper实现),而且WHERE关键字也不需要我们特地指定。然后通过以下方式调用:
@Test
public void testCustomFindMP() {
List<Team> teamList = teamMapper.findMP(Wrappers.<Team>query().le("id", 5L));
// MP执行的SQL:
// Preparing: SELECT * FROM team WHERE (id <= ?)
// Parameters: 5(Long)
Assertions.assertNotEquals(0, teamList.size());
}
分页查询的自定义SQL也非常简单,在Mapper中定义分页查询(Page参数必须是第一个参数):
@Select("SELECT * FROM player ${ew.customSqlSegment}")
IPage<Player> pageCustomMP(Page page, @Param(Constants.WRAPPER) Wrapper<Player> wrapper);
调用方式也是我们非常熟悉的方式:
@Test
public void testPageCustomMP() {
IPage<Player> playerIPage = playerMapper.pageCustomMP(new Page(1, 5), Wrappers.<Player>query().le("id", 5L));
// MP执行的SQL:
// Preparing: SELECT COUNT(1) FROM player WHERE (id <= ?)
// Parameters: 5(Long)
// Preparing: SELECT * FROM player WHERE (id <= ?) LIMIT ?,?
// Parameters: 5(Long), 0(Long), 5(Long)
Assertions.assertNotEquals(0, playerIPage.getRecords().size());
}
参考实战二(已修改部分代码)
https://blog.csdn.net/weixin_42752859/article/details/86703947
导入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>1.0.5</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>2.3</version>
</dependency>
application.properties
server.port=8080
#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/wssll?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#mybatis-plus
mybatis-plus.mapper-locations=classpath:mybatis/mapper/*Dao.xml #在该xml中只写自定义的sql,如果没有特殊sql这行可以注了
mybatis-plus.type-aliases-package=io.ssss.wsssall.dal.dao
Config配置(注解)
@Configuration
public class MybatisConfig {
@Bean
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1000);
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
// 配置数据源
@Bean(name="dataSource")
@ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource(){
return new DruidDataSource();
}
// 配置事物管理器
@Bean(name="transactionManager")
public DataSourceTransactionManager transactionManager(){
return new DataSourceTransactionManager(dataSource());
}
}
启动类MapperScan
@SpringBootApplication
@MapperScan("io.mssnk.wsssall.dal.dao") //具体到dao的包
public class We1Application {
public static void main(String[] args) {
SpringApplication.run(We1Application.class, args);
}
}
dao 接口
@Repository
public interface CartDao extends BaseMapper<Cart> {//BaseMapper是插件提供的一个通用接口,有很多方法
Integer getMax();//这个是我自定义的一个方法,BaseMapper中没有的
}
``
7. Service调用并测试
}
@Override
public Cart getById(Long id){
return cartDao.selectById(id); //这个是BaseMapper通用方法,id可以是Long,也可以是Integer
}
@Override
public List<Cart> findAll(Cart cart) {
EntityWrapper<Cart> tWrapper = new EntityWrapper<>() ;//
//条件查询1,直接entity
tWrapper.setEntity(cart);//可以接受一个domian对象作为查询条件
//条件查询2,指定属性
//tWrapper.eq("count",1).eq("status",0);//也可以自定义查询属性
return cartDao.selectList(tWrapper);//这也是BaseMapper的通用方法,
}
@Override
public Integer getMax() {
return cartDao.getMax();//这是我自定义的方法,需要在*Dao.xml中写具体sql实现
如果没有复杂sql,直接用mybatis plus就可以省去xml的配置,免得各种报错,而且数据库表字段修改了,不再修改xml中的数据了!
另外,另外plus支持的条件查询,不仅有eq,还有gt,lt,这是很多自动生成sql插件不具备的