小编目前大一,刚开始着手学习SSM,小编会把每个框架都整理成知识点发布出来。如果你也同时在学习SSM的话,不妨看看我做的这个笔记。我认为同为初学者,我把我对知识点的理解以这种代码加观点的方式分享出来不仅加深了我的理解,或许在某个时候对你也有所帮助,同时也欢迎大家在评论区分享你们的观点。
带着决心起床,带着满意入睡。
目录
概述
通过JavaWeb的学习,我们知道Mybatis是一个非常流行的持久层框架,主要是做数据库的增删改查的,在日常生活中我们也知道plus表示加强和升级,那今天我们来看一下MybatisPlus在Mybatis上做了哪些升级和优化。下面是Mybatis-Plus的官网。
MyBatis-Plus 🚀 为简化开发而生 (baomidou.com)
下面这就是Mybatis-plus的官网给出的Mybatis-plus给出的特性,第一点我们知道其实我们使用Mybatis-plus来做持久层开发,是和之前使用Mybatis是没有太大区别,直接引入对项目工程都不会产生影响。我们可以看到第二个特点效率至上,其实我们之前所写的项目大部分都是单表的crud操作,对此Mybatis-plus就做了升级,说只要简单配置就可以快速实现单表crud,以及第三个特点丰富功能都是很吸引我们的,迫不及待想看看Mybatis-plus怎么使用。
快速入门
入门案例
通过前面的概述我们知道Mybatis-plus是在Mybatis上的升级,所以先准备一个普通的Mybatis操作数据库的案例,然后把它改成Mybatis-plus,我们就更能体会到Mybatis-plus带来的方便与效率了。我们先来看一下项目结构,表现层和业务层我都没有实现。
直接写了UserMapper接口和用了xml映射的方式来操作user这张表,接口中也是定义了几个常规的crud方法,妥妥属于单表操作。xml配置文件的代码就不看了,业就是那么一回事,代码量属实不少。
public interface UserMapper {
void saveUser(User user);
void deleteUser(Long id);
void updateUser(User user);
User queryUserById(@Param("id") Long id);
List<User> queryUserByIds(@Param("ids") List<Long> ids);
}
接下来我们要使用Mybatis-plus来爆改当前项目,我们就先要引入Mybatis-plus的依赖,所以第一步引入MybatisPlus的起步依赖。起步依赖官网也有给出,我这里引入的是SpringBoot3的,SpringBoot2的官网也有给出。另外声明一句,这个依赖集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果,也就是可以用MybatisPlus的起步依赖代替Mybatis的。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
第二步,我们需要将我们自定义的Mapper(案例中的UserMapper)继承MybatisPlus提供的BaseMapper接口,同时指明泛型为所操作的表的实体类类型。
public interface UserMapper extends BaseMapper<User> {
}
然后就没有然后了,我们已经完成了,接下来我们只需要使用BaseMapper给我们提供的方法就好了,下面让我们测试一下,就是测试和刚刚一样的crud业务。
五个方法都显示测试通过,建议大家可以看一下控制台,看它的sql语句是不是和我们之前写的是大差不差的。入门案例也完成了,通过入门案例我们知道只需要两步就可以使用MybatisPluss首先第一步引入依赖,第二步让自定义的Mapper接口继承BaseMapper接口,同时指明泛型为所操作的表的实体类类型就行了。
常见注解
其实MyBatisPlus是通过扫描实体类,并基于反射获取实体类信息作为数据库信息。但是这之间需要遵守驼峰转下划线的命名规则,不然就会导致与数据库字段名不符合。
简单来说就是约定大于配置,只要我们遵守了上面的约定,MybatisPlus就能自动帮我们实现crud。接下来我们来看一下,如果我们实体类不符合这些约定,我们又改怎么办呢,但是我还是建议遵守约定,这样使用起来多省事啊,下面的知识仅供了解。
MybatisPlus给我们提供了几个比较常用的注解。如果不符合以上三个约定,我们就需要使用下面的注解来规定,同样每个注解代表每个约定。
在我们规定主键时,Mybatis-Plus给我们提供了三个设置id的枚举。 第一个和第二个都好理解,第三个就是Mybatis-Plus帮我们自动生成id,但是这个id没有规律而言,通过雪花算法随机生成的。
@TableId(value = "id",type = IdType.AUTO)
private Long id;
对于第三个@TableField注解,我们需要注意几个特殊情况的使用。
以上就是常用的三个注解,对于其它一些注解,Mybatis-Plus官网都有介绍,有兴趣可以去看一下。
常见配置
前面我们知道使用Mybatis-Plus是不需要配置的,但是我们还是需要知道Mybatis-Plus一些基本的配置项。Mybatis-Plus的配置项继承了Mybatis原生配置和一些自己特有的配置。
我们会发现对于上面几项都是继承的Mybatis的原生配置,后面几项才是Mybatis-Plus特有配置。 另外对于一些配置的默认值我们不知道,我们可以去官网查询。
快速入门到这里就结束了,下面是MyBatisPlus使用的基本流程总结
核心功能
条件构造器
MybatisPlus支持各种复杂的where条件,可以满足日常开发的所有需求,在BaseMapper提供的方法中有一些的参数是Wrapper类型,这类参数就是条件构造器,用来构造复杂SQL语句的。
下面就是Wrapper的继承结构,见名知意,加了Lambda的就是可以用Lambada这种特殊语法来构建条件,UpdateWrapper就是更新条件构造器,QueryWrapper就是查询条件构造器。
接下来我们就来演示一下条件构造器怎么使用,首先明确我们是要查询还是更新,选择合适的条件构造器,然后调用合适的方法就可以使用条件的构造。 第一个就是查询username中带o的且薪资大于1000的用户,对于一些方法名不清楚,可以去官网查,大部分和我们之前写sql语句时的条件是一样的。第二个就是更新username为jack的员工薪资为2000。
@Test
void testQueryWrapper() {
// 1.构造查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username", "info", "balance")
.like("username", "o")
.ge("balance", 1000);
// 2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
@Test
void testUpdateWrapper() {
// 1.要更新的数据
User user = new User();
user.setBalance(2000);
// 2.更新的条件
QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");
// 3.执行更新
userMapper.update(user, wrapper);
}
有了上面的基础,下面让我们来实现将员工id为1,2,4的员工薪资减200。
@Test
void testUpdateWrapper() {
List<Long> ids = List.of(1L, 2L, 4L);
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", ids);
userMapper.update(null, wrapper);
}
我们会发现在上面的查询和更新中我们都是将字段名以字符串的形式写死了,这属于硬编码,因此可以使用LambdaWrapper来解决这种问题,下面就演示一下LambdaQueryWrapper,UpdateWrapper同理。我们这里是传函数式接口,传了get字段名的函数,其实底层他会用反射的方式来获取到字段名。
@Test
void testLambdaQueryWrapper() {
// 1.构造查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
// 2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
到这里,条件构造器就学习完了,更多条件构造的方法可以去官网查看,还有就是刚开始可能这样使用不是特别习惯,多用就好了。
自定义SQL
我们可以利用MybatisPlus的Wrapper来构建复杂的where条件,然后自己定义SQL语句中剩下的部分。对于一些特殊的逻辑,例如上面的将id在指定范围你的用户的薪资扣减指定值,我们会发现我们将SQL语句写在了业务层,这是不符合我们开发的规范的。因此有了自定义SQL,也就是让MP去构建它所擅长的where条件,然后自己定义SQL语句中剩下的部分。
首先我们要基于Wrapper构建where条件。
@Test
void testCustomSqlUpdateWrapper() {
List<Long> ids = List.of(1L, 2L, 4L);
int amount = 200;
// 1.构建条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, ids);
// 2.自定义SQL方法调用
userMapper.updateBalanceByIds(wrapper,amount);
}
接着我们在mapper接口中对应的方法参数用Parem注解声明wrapper变量名称,必须是ew。
void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);
最后自定义SQL,使用Wrapper条件,在最后调用ew.customSqlSegment实现自动拼接。
<update id="updateBalanceByIds">
update user
set balance = balance - #{amount} ${ew.customSqlSegment}
</update>
Service接口
在之前我们mapper层接口只需要继承BaseMapper,那一些基本的crud就不需要我们自己写了,可谓是一时继承一时爽,那Mybatis-Plus为了让我们能够一直继承一直爽下去,还提供了Service接口,只要继承了它,那一些基础的Service层方法也不需要我们写了。下面我们就来学习一下Service接口。Service中提供了许多基本的增删改查的方法。
下面我们来看一下如何继承Service层接口,比Mapper层接口稍微复杂了一丢丢。
因为倘若我们的Service接口继承了IServcie接口,那我们的实现类也就需要实现IService中的所有方法,这不是我们想要的,我们只想白嫖,MybatisPlus呢,当然知道我们的小心思,所以给我们提供了IService的实现类,因此我们只需要让我们Service接口继承IService接口,Service实现类不仅需要实现Service接口,同时还要继承IService实现类就好了。
首先我们要让UserService接口继承IService接口。
public interface IUserService extends IService<User> {
}
接着我们要让UserService的实现类去实现UserService接口同时继承IService的实现类,并声明两个泛型,一个是同实现类的自定义Mapper接口,第二个就是实现类。
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
接着我们就可以直接使用Service接口中提供的方法了,测试代码如下:
@SpringBootTest
public class IUserServiceTest {
@Autowired
private IUserService userService;
@Test
void TestSaveUser() {
User user = new User();
// user.setId(5L);
user.setUsername("ShuTao");
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userService.save(user);
}
@Test
void testQuery(){
List<User> users = userService.listByIds(List.of(1L, 2L, 4L));
users.forEach(System.out::println);
}
}
这样Service接口的简单使用我们就学习完了。
那么现在对于一些简单的只涉及单表的crud的业务,我们在controller层就能轻松解决,只需要调用已经提供好的方法就好了,例如下面的新增用户。
@ApiOperation("新增用户接口")
@PostMapping
public void saveUser(@RequestBody UserFormDTO userDTO) {
// 1.把DTO拷贝到PO
User user = BeanUtil.copyProperties(userDTO, User.class);
// 2.新增
userService.save(user);
}
删除,查询单个,多个这些都是可以直接调用的,这些就属于Service开发简单业务接口,可谓是非常的方便,那接下来我们来看一下稍微复杂的业务,不能直接调用Service中的接口实现的,就需要我们自己去手写方法,然后利用前面的自定义SQL来拼接实现。接下来我们就来实现一个根据id扣减员工工资。还是像之前一样,我们首先要调用Service层中自定义实现的方法。
@ApiOperation("扣减用户余额接口")
@PutMapping("/{id}/deduction/{money}")
public void deductMoneyById(
@ApiParam("用户id") @PathVariable("id") Long id,
@ApiParam("扣减的金额") @PathVariable("money") Integer money) {
userService.deductBalance(id, money);
}
接着去处理其中的业务逻辑,判断是否查询到用户,用户的状态是否正常,用户的余额是否充足。
@Override
public void deductBalance(Long id, Integer money) {
// 1.查询用户
User user = getById(id);
// 2.校验用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
// 3.校验余额是否充足
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足!");
}
// 4.扣减余额
baseMapper.deductBalance(id,money);
}
由于这里比较简单,就直接用注解的方式编写了SQL语句。
@Update("update user set balance = balance - #{money} where id = #{id}")
void deductBalance(@Param("id") Long id, @Param("money") Integer money);
接下来我们来学习一下IService中的Lambda查询。
Service中的Lambda查询通常用于复杂条件的查询,使用起来非常简单,只要熟悉那些条件函数的用法就好了。
@Override
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
List<User> users = lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.gt(minBalance != null, User::getUsername, minBalance)
.lt(maxBalance != null, User::getUsername, maxBalance)
.list();
return users;
}
IService中的Lambda更新其实差不多,只是最后要调用update方法才能实现更新,这里就是拿上面那个根据id扣减余额作为例子演示一下。
@Override
@Transactional
public void deductBalance(Long id, Integer money) {
// 1.查询用户
User user = getById(id);
// 2.校验用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
// 3.校验余额是否充足
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足!");
}
// 4.扣减余额
int remainBalance = user.getBalance() - money;
lambdaUpdate()
.set(User::getBalance, remainBalance)
.set(remainBalance == 0, User::getStatus, 2)
.eq(User::getId, id)
.eq(User::getBalance,user.getBalance()) //乐观锁
.update();
baseMapper.deductBalance(id, money);
}
到此Mybatis-Plus的核心功能就学完了。
扩展功能
接下来我们来看一下MyBatisPlus所提供的扩展功能这些功能不是重点,但是会给我们带来一定程度上的便捷。
代码生成器
通过之前的学习,我们会发现我们每次使用MyBatisPlus的自定义mapper接口和Service接口都要去按规定的继承BaseMaper和IService,这些代码很冗余,而且仅仅只有名字不同,名字其实是和我们的数据库表有关,这里就推荐一个可以自动生成mapper接口,service接口,service实现类,以及实体类的插件。
首先在idea插件中下载下面这个插件。
接着在最上面的工具一栏中会多了两个选项。
首先第一个选项配置好数据库信息,因为代码生成是根据数据库中的表生成的。
接着第二个选项就是设置代码生成时的一些配置。
这样我们只需要直接开发业务就好了。
静态工具
在以前我们实现业务接口时,可能会出现多个Service之间相互调用,如果我们这时采取的是传统的@Autowired自动注入,那Service之间相互注入就会出现循环依赖,这样对我们的开发是不太友好的,所以对于以后如果出现这种情况我们就可以使用Db静态工具去调用,Db静态工具中包含许多crud的方法,和Service中的方法几乎一模一样,就相当于用Db去代替Service,但是在使用的时候我们需要声明当前是哪个类使用,也就是传class字节码文件,如同下面这个业务需求就是根据id查询用户及其地址,查询地址我们就是使用Db静态工具去调用的。
@Override
public UserVO queryUserAndAddressById(Long id) {
// 1.查询用户
User user = getById(id);
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
// 2.查询地址
List<Address> addresses = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, id)
.list();
// 3.封装VO
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
if (CollUtil.isNotEmpty(addresses)) {
userVO.setAddress(BeanUtil.copyToList(addresses, AddressVO.class));
}
return userVO;
}
逻辑删除
逻辑删除就是基于代码逻辑模拟删除效果,但不会真正删除数据。思路大概就是我们可以先在表中添加一个字段标记数据是否被删除,当删除数据时把标记设置为1,查询时只查询数据标记为0的数据。
而我们现在使用的是MybatisPlus给我们提供的方法,那如果我们这时想实现逻辑删除,但是又不想自己写代码应该怎么做呢,其实MybatisPlus已经给我们提供了对应得解决方法,我们无需改变方法方法调用得方式,我们要做得就是在application.yaml文件中配置逻辑删除得字段名称和值即可。
在底层其实它的SQL语句在删除时,采取的是update语句。
枚举处理器
枚举类型相信大家都用过,这里其实就是讲清楚java中的枚举类型与数据库表中的int类型怎么进行转换。在Mybatis中存在一个类型处理器TypeHandler,这个就是可以帮我们实现一些类型转换,到MybatisPlus的时候就新增了一些,例如枚举类型处理器以及我们下面要学的JSON类型的处理器。要使用处理器其实分为两步,第一步将枚举中的与数据库对应value值添加@EnumValue注解。
第二步就是让枚举处理器生效,在yml配置文件中配置全局枚举处理器。
JSON处理器
和上面一样,JSON处理器是MybatisPlus给我们提供的类型处理器,可惜MybatisPlus并没有给我们提供全局处理器,所以需要我们自己去配置,我们也还是需要去做两件事,第一个就是给字段上定义处理器,第二件事就是给表开启自动结果映射。
插件功能
其实MybatisPlus提供了许多插件,但是其实用的最多的还是分页插件,在之前我们使用的都是pageHelper插件来实现分页查询的,但是现在MybatisPlus给我们提供了分页插件,我们来学习一下。
首先要想使用MybatisPlus给我们提供的分页插件,我们就要在配置类中注册MybatisPlus的核心插件,同时添加分页插件。
接着我们就可以使用分页的API了:
如果我们还需要添加别的插件时,在后面继续调用add那个方法就好了。
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 总拦截器
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 1.分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(1000L);
// 2.添加分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
分页的设置,分页页码数,每页展示条数以及排序条件这些其实和pageHelper是大差不差的。
@Test
void testPage() {
int pageNum = 1;
int pageSize = 2;
// 1.准备分页条件
// 1.1分页条件
Page<User> page = Page.of(pageNum, pageSize);
// 1.2排序条件
page.addOrder(new OrderItem().setColumn("balance").setAsc(true));
page.addOrder(new OrderItem().setColumn("id").setAsc(true));
// 2.分页查询
Page<User> p = userService.page(page);
// 3.解析
long total = p.getTotal();
System.out.println("total = " + total);
long pages = p.getPages();
System.out.println("pages = " + pages);
List<User> users = p.getRecords();
users.forEach(System.out::println);
}
以上就是Mybatis-Plus的所有重要知识点,也希望这一款强大好用的框架在你以后的开发过程中可以让你事半功倍。
有梦别怕苦,想赢别喊累。