推荐:​​MyBatis Plus汇总​​

MyBatis-Plus 之通用Service

首先创建一个数据库表,如下图所示:

MyBatis-Plus 之通用Service_java

然后创建一个Spring Boot项目,pom.xml和配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.kaven</groupId>
<artifactId>mybatis-plus</artifactId>
<version>1.0-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/>
</parent>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
spring:
application:
name: mybatis-plus
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: ITkaven@123
url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8&useSSL=false

server:
port: 8085

logging:
level:
root: warn
com.kaven.mybatisplus.dao: trace
pattern:
console: '%p%m%n'

实体类User:

package com.kaven.mybatisplus.entity;

import com.baomidou.mybatisplus.annotation.SqlCondition;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@TableName("user")
@Data
public class User{

@TableId
private String id;

@TableField(value = "username" , condition = SqlCondition.LIKE)
private String username;

@TableField("password")
private String password;

@TableField(value = "age" , condition = "%s>#{%s}")
private Integer age;

/**
* 使用 @TableField(exist = false) ,表示该字段在数据库中不存在 ,所以不会插入数据库中
* 使用 transient 、 static 修饰属性也不会插入数据库中
*/
@TableField(exist = false)
private String phone;
}

Mapper接口UserMapper:

package com.kaven.mybatisplus.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.kaven.mybatisplus.entity.User;
import org.springframework.stereotype.Component;


@Component
public interface UserMapper extends BaseMapper<User> {}

启动类:

package com.kaven.mybatisplus;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan(basePackages = "com.kaven.mybatisplus.dao")
public class AppRun {
public static void main(String[] args) {
SpringApplication.run(AppRun.class , args);
}
}

​@MapperScan(basePackages = "com.kaven.mybatisplus.dao")​​这个一定要加上。

我们先在数据库中添加几行数据,方便演示。

MyBatis-Plus 之通用Service_后端_02


想要使用MyBatis-Plus的通用Service,首先我们需要写一个Service,然后继承MyBatis-Plus的通用Service(​​IService<T>​​)。

package com.kaven.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.kaven.mybatisplus.entity.User;

public interface UserService extends IService<User> {}

然后再写一个ServiceImpl并且继承MyBatis-Plus的​​ServiceImpl<M extends BaseMapper<T>, T>​​,当然还要实现我们写的Service。

package com.kaven.mybatisplus.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kaven.mybatisplus.dao.UserMapper;
import com.kaven.mybatisplus.entity.User;
import com.kaven.mybatisplus.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper , User> implements UserService {}

这样就可以使用MyBatis-Plus的通用Service了。

主要来看看​​ServiceImpl<M extends BaseMapper<T>, T>​​这个类,因为它实现了通用Service的CRUD方法,源码如下:

package com.baomidou.mybatisplus.extension.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

/**
* IService 实现类( 泛型:M 是 mapper 对象,T 是实体 )
*
* @author hubin
* @since 2018-06-23
*/
@SuppressWarnings("unchecked")
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {

protected Log log = LogFactory.getLog(getClass());

@Autowired
protected M baseMapper;

@Override
public M getBaseMapper() {
return baseMapper;
}

protected Class<?> entityClass = currentModelClass();

protected Class<?> mapperClass = currentMapperClass();

/**
* 判断数据库操作是否成功
*
* @param result 数据库操作返回影响条数
* @return boolean
* @deprecated 3.3.1
*/
@Deprecated
protected boolean retBool(Integer result) {
return SqlHelper.retBool(result);
}

protected Class<T> currentMapperClass() {
return (Class<T>) ReflectionKit.getSuperClassGenericType(getClass(), 0);
}

protected Class<T> currentModelClass() {
return (Class<T>) ReflectionKit.getSuperClassGenericType(getClass(), 1);
}

/**
* 批量操作 SqlSession
*
* @deprecated 3.3.0
*/
@Deprecated
protected SqlSession sqlSessionBatch() {
return SqlHelper.sqlSessionBatch(entityClass);
}

/**
* 释放sqlSession
*
* @param sqlSession session
* @deprecated 3.3.0
*/
@Deprecated
protected void closeSqlSession(SqlSession sqlSession) {
SqlSessionUtils.closeSqlSession(sqlSession, GlobalConfigUtils.currentSessionFactory(entityClass));
}

/**
* 获取 SqlStatement
*
* @param sqlMethod ignore
* @return ignore
* @see #getSqlStatement(SqlMethod)
* @deprecated 3.4.0
*/
@Deprecated
protected String sqlStatement(SqlMethod sqlMethod) {
return SqlHelper.table(entityClass).getSqlStatement(sqlMethod.getMethod());
}

/**
* 批量插入
*
* @param entityList ignore
* @param batchSize ignore
* @return ignore
*/
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) {
String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
}

/**
* 获取mapperStatementId
*
* @param sqlMethod 方法名
* @return 命名id
* @since 3.4.0
*/
protected String getSqlStatement(SqlMethod sqlMethod) {
return SqlHelper.getSqlStatement(mapperClass, sqlMethod);
}

/**
* TableId 注解存在更新记录,否插入一条记录
*
* @param entity 实体对象
* @return boolean
*/
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveOrUpdate(T entity) {
if (null != entity) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);
Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
String keyProperty = tableInfo.getKeyProperty();
Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
Object idVal = ReflectionKit.getFieldValue(entity, tableInfo.getKeyProperty());
return StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal)) ? save(entity) : updateById(entity);
}
return false;
}

@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
String keyProperty = tableInfo.getKeyProperty();
Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
return SqlHelper.saveOrUpdateBatch(this.entityClass, this.mapperClass, this.log, entityList, batchSize, (sqlSession, entity) -> {
Object idVal = ReflectionKit.getFieldValue(entity, keyProperty);
return StringUtils.checkValNull(idVal)
|| CollectionUtils.isEmpty(sqlSession.selectList(getSqlStatement(SqlMethod.SELECT_BY_ID), entity));
}, (sqlSession, entity) -> {
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
param.put(Constants.ENTITY, entity);
sqlSession.update(getSqlStatement(SqlMethod.UPDATE_BY_ID), param);
});
}

@Transactional(rollbackFor = Exception.class)
@Override
public boolean updateBatchById(Collection<T> entityList, int batchSize) {
String sqlStatement = getSqlStatement(SqlMethod.UPDATE_BY_ID);
return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
param.put(Constants.ENTITY, entity);
sqlSession.update(sqlStatement, param);
});
}

@Override
public T getOne(Wrapper<T> queryWrapper, boolean throwEx) {
if (throwEx) {
return baseMapper.selectOne(queryWrapper);
}
return SqlHelper.getObject(log, baseMapper.selectList(queryWrapper));
}

@Override
public Map<String, Object> getMap(Wrapper<T> queryWrapper) {
return SqlHelper.getObject(log, baseMapper.selectMaps(queryWrapper));
}

@Override
public <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper) {
return SqlHelper.getObject(log, listObjs(queryWrapper, mapper));
}

/**
* 执行批量操作
*
* @param consumer consumer
* @since 3.3.0
* @deprecated 3.3.1 后面我打算移除掉 {@link #executeBatch(Collection, int, BiConsumer)} }.
*/
@Deprecated
protected boolean executeBatch(Consumer<SqlSession> consumer) {
return SqlHelper.executeBatch(this.entityClass, this.log, consumer);
}

/**
* 执行批量操作
*
* @param list 数据集合
* @param batchSize 批量大小
* @param consumer 执行方法
* @param <E> 泛型
* @return 操作结果
* @since 3.3.1
*/
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
return SqlHelper.executeBatch(this.entityClass, this.log, list, batchSize, consumer);
}

/**
* 执行批量操作(默认批次提交数量{@link IService#DEFAULT_BATCH_SIZE})
*
* @param list 数据集合
* @param consumer 执行方法
* @param <E> 泛型
* @return 操作结果
* @since 3.3.1
*/
protected <E> boolean executeBatch(Collection<E> list, BiConsumer<SqlSession, E> consumer) {
return executeBatch(list, DEFAULT_BATCH_SIZE, consumer);
}

}

来使用一下​​getOne()​​,它的源码如下:

/**
* 根据 Wrapper,查询一条记录 <br/>
* <p>结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default T getOne(Wrapper<T> queryWrapper) {
return getOne(queryWrapper, true);
}

/**
* 根据 Wrapper,查询一条记录
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
* @param throwEx 有多个 result 是否抛出异常
*/
T getOne(Wrapper<T> queryWrapper, boolean throwEx);

一个参数的​​getOne()​​​是通过调用​​getOne(queryWrapper, true)​​​来实现的,所以我们需要来看一下两个参数的​​getOne()​​是怎么实现的,源码如下:

@Override
public T getOne(Wrapper<T> queryWrapper, boolean throwEx) {
if (throwEx) {
return baseMapper.selectOne(queryWrapper);
}
return SqlHelper.getObject(log, baseMapper.selectList(queryWrapper));
}
@Autowired
protected M baseMapper;

可以看到,它用到了baseMapper,而baseMapper会自动注入(由Spring的BeanFactory来管理)。

其实这个baseMapper就是我们上面的UserMapper。

MyBatis-Plus 之通用Service_后端_03


MyBatis-Plus 之通用Service_java_04


所以当​​throwEx​​​为​​true​​​时,会返回​​baseMapper.selectOne(queryWrapper)​​的结果,在之前的博客已经介绍过这个方法,当符合条件构造器的数据有多条时,会抛出异常。

当​​throwEx​​​为​​false​​​时,会返回​​SqlHelper.getObject(log, baseMapper.selectList(queryWrapper))​​的结果,源码如下:

public static <E> E getObject(Log log, List<E> list) {
if (CollectionUtils.isNotEmpty(list)) {
int size = list.size();
if (size > 1) {
log.warn(String.format("Warn: execute Method There are %s results.", size));
}
return list.get(0);
}
return null;
}

从源码可知当符合条件构造器的数据有多条时,首先会报一个警告,再返回符合条件的第一条数据(下标为​​0​​​)
所以​​​throwEx​​就是表示当符合条件构造器的数据有多条时,是否需要抛出异常信息。

来测试一下:

package com.kaven.mybatisplus.dao;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.kaven.mybatisplus.entity.User;
import com.kaven.mybatisplus.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {

@Autowired
private UserService userService;

@Test
public void getOne(){
LambdaQueryWrapper<User> userLambdaQueryWrapper = Wrappers.lambdaQuery();
userLambdaQueryWrapper.gt(User::getAge , 20);

User user = userService.getOne(userLambdaQueryWrapper);
System.out.println(user);
}
}

很显然抛出异常了:

MyBatis-Plus 之通用Service_java_05


再来测试一下:

package com.kaven.mybatisplus.dao;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.kaven.mybatisplus.entity.User;
import com.kaven.mybatisplus.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {

@Autowired
private UserService userService;

@Test
public void getOne(){
LambdaQueryWrapper<User> userLambdaQueryWrapper = Wrappers.lambdaQuery();
userLambdaQueryWrapper.gt(User::getAge , 20);

User user = userService.getOne(userLambdaQueryWrapper , false);
System.out.println(user);
}
}

结果如下:

MyBatis-Plus 之通用Service_java_06

和我们分析的一样,首先会发出警告,然后选择第一条符合条件的数据。

再来使用一下​​saveBatch()​​,源码如下:

/**
* 插入(批量)
*
* @param entityList 实体对象集合
*/
@Transactional(rollbackFor = Exception.class)
default boolean saveBatch(Collection<T> entityList) {
return saveBatch(entityList, DEFAULT_BATCH_SIZE);
}

/**
* 插入(批量)
*
* @param entityList 实体对象集合
* @param batchSize 插入批次数量
*/
boolean saveBatch(Collection<T> entityList, int batchSize);
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) {
String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
}
/**
* 默认批次提交数量
*/
int DEFAULT_BATCH_SIZE = 1000;

可以看到这两个方法都加了事务​​@Transactional(rollbackFor = Exception.class)​​​,默认批次提交数量​​DEFAULT_BATCH_SIZE​​​为​​1000​​,代码就不深入研究了。

来测试一下:

package com.kaven.mybatisplus.dao;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.kaven.mybatisplus.entity.User;
import com.kaven.mybatisplus.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {

@Autowired
private UserService userService;

@Transactional
@Test
public void saveBatch(){
List<User> userList = new ArrayList<>();
for (int i = 0; i < 2000; i++) {
User user = new User();
user.setUsername("kaven-"+i);
user.setPassword("itkaven-"+i);
user.setAge(22);
userList.add(user);
}

long start = System.currentTimeMillis();
userService.saveBatch(userList);
long end = System.currentTimeMillis();
System.out.println("耗时: "+(end-start)+"ms");
}
}

在测试方法上加上事务,会在测试方法完成后,帮我们回滚到原始的数据,避免测试阶段污染数据库中的数据。

结果如下:

MyBatis-Plus 之通用Service_java_07


耗时还挺长的,可能是因为服务器的数据库(网络原因),并且还打印了​​sql​​信息,以及还需要回滚数据。

再来测试下一次性插入:

package com.kaven.mybatisplus.dao;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.kaven.mybatisplus.entity.User;
import com.kaven.mybatisplus.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {

@Autowired
private UserService userService;

@Transactional
@Test
public void saveBatch(){
List<User> userList = new ArrayList<>();
for (int i = 0; i < 2000; i++) {
User user = new User();
user.setUsername("kaven-"+i);
user.setPassword("itkaven-"+i);
user.setAge(22);
userList.add(user);
}

long start = System.currentTimeMillis();
userService.saveBatch(userList , 2000);
long end = System.currentTimeMillis();
System.out.println("耗时: "+(end-start)+"ms");
}
}

结果如下:

MyBatis-Plus 之通用Service_后端_08


稍微好一点点。

强调一下,还有些方法比较好用,会返回​​ChainWrapper<T>​​​这种链式条件构造器,这里就不演示了,下面有源码,自己看一看,还是比较简单的,我在​​Lambda​​条件构造器那一篇博客中有使用过这种链式条件构造器。

​​MyBatis-Plus 之Lambda条件构造器​​

/**
* 以下的方法使用介绍:
*
* 一. 名称介绍
* 1. 方法名带有 query 的为对数据的查询操作, 方法名带有 update 的为对数据的修改操作
* 2. 方法名带有 lambda 的为内部方法入参 column 支持函数式的
* 二. 支持介绍
*
* 1. 方法名带有 query 的支持以 {@link ChainQuery} 内部的方法名结尾进行数据查询操作
* 2. 方法名带有 update 的支持以 {@link ChainUpdate} 内部的方法名为结尾进行数据修改操作
*
* 三. 使用示例,只用不带 lambda 的方法各展示一个例子,其他类推
* 1. 根据条件获取一条数据: `query().eq("column", value).one()`
* 2. 根据条件删除一条数据: `update().eq("column", value).remove()`
*
*/

/**
* 链式查询 普通
*
* @return QueryWrapper 的包装类
*/
default QueryChainWrapper<T> query() {
return ChainWrappers.queryChain(getBaseMapper());
}

/**
* 链式查询 lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaQueryWrapper 的包装类
*/
default LambdaQueryChainWrapper<T> lambdaQuery() {
return ChainWrappers.lambdaQueryChain(getBaseMapper());
}

/**
* 链式更改 普通
*
* @return UpdateWrapper 的包装类
*/
default UpdateChainWrapper<T> update() {
return ChainWrappers.updateChain(getBaseMapper());
}

/**
* 链式更改 lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaUpdateWrapper 的包装类
*/
default LambdaUpdateChainWrapper<T> lambdaUpdate() {
return ChainWrappers.lambdaUpdateChain(getBaseMapper());
}

其他方法也是差不多的用法,可以自己去研究一下。

写博客是博主记录自己的学习过程,如果有错误,请指正,谢谢!