練習代碼已上傳到CSDN資源中
SpringBoot 整合Mybatis-plus
一、環境搭建
1.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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mybatisplus</groupId>
<artifactId>mybatisplugsdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatisplugsdemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>druid
</dependency>
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.8.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.application.yml
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://192.168.1.2:3306/mp?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=true
username: root
password: root
application:
name: mybatisplus
3.spy.properties配置文件
module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定義日誌打印
logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
# 使用日誌系統記錄sql
#appender=com.p6spy.engine.logging.appender.StdoutLogger
appender=com.p6spy.engine.spy.appender.StdoutLogger
#appender=com.p6spy.engine.logging.appender.FileLogger
#logfile = d:/spy.log
## 配置記錄Log例外
#excludecategories=info,debug,result,batc,resultset
# 設置使用p6spy driver來做代理
deregisterdrivers=true
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 實際驅動
driverlist=com.mysql.cj.jdbc.Driver
# 是否開啓慢SQL記錄
outagedetection=true
# 慢SQL記錄標準 秒
outagedetectioninterval=2
4.創建實體類
//TODO 實體類需要注意
/*
1. @TableName
2.TableId
* */
@Data
@TableName("tbl_employee")
public class Employee implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Integer age;
}
sql語句
-- 創建庫
CREATE DATABASE mp;
-- 使用庫
USE mp;
-- 創建表
CREATE TABLE tbl_employee(
id INT(11) PRIMARY KEY AUTO_INCREMENT,
last_name VARCHAR(50),
email VARCHAR(50),
gender CHAR(1),
age INT
);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Tom','[email protected]',1,22);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Jerry','[email protected]',0,25);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Black','[email protected]',1,30);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('White','[email protected]',0,35);
5.創建Mapper
// TODO mapper的寫法: mapper 繼承 BaseMapper<T> T 實體類
public interface EmployeeMapper extends BaseMapper<Employee> {
}
6.啓動類
@SpringBootApplication
@MapperScan("com.mybatisplus.mybatisplugsdemo.mapper")
public class MybatisplugsdemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisplugsdemoApplication.class, args);
}
}
// 注意:Springboot啓動類放的位置,掃描的是當前包及子包。一般建議放到com.xxx
/*
比如:
com.mybatisplus 存放啓動類
com.mybatisplus.controller
com.mybatisplus.mapper
*/
7.測試類
@RunWith(SpringRunner.class)
@SpringBootTest
class MybatisplugsdemoApplicationTests {
@Test
void contextLoads() {
}
}
public class Test01 extends MybatisplugsdemoApplicationTests {
@Resource
private EmployeeMapper employeeMapper;
@Test
public void fn(){
/**
* 查詢所有數據
*/
employeeMapper.selectList(null).forEach(System.out::println);
}
}
只需要創建 EmployeeMapper 接口, 並繼承 BaseMapper 接口.這就是使用 MP需要完成的所有操作,甚至不需要創建 SQL 映射文件。
二、BaseMapper 、Wrapper源碼
1.BaseMapper.java
/**
* Mapper 繼承該接口後,無需編寫 mapper.xml 文件,即可獲得CRUD功能
* <p>這個 Mapper 支持 id 泛型</p>
*
* @author hubin
* @since 2016-01-23
*/
public interface BaseMapper<T> extends Mapper<T> {
/**
* 插入一條記錄
*
* @param entity 實體對象
*/
int insert(T entity);
/**
* 根據 ID 刪除
*
* @param id 主鍵ID
*/
int deleteById(Serializable id);
/**
* 根據 columnMap 條件,刪除記錄
*
* @param columnMap 表字段 map 對象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根據 entity 條件,刪除記錄
*
* @param wrapper 實體對象封裝操作類(可以爲 null)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
/**
* 刪除(根據ID 批量刪除)
*
* @param idList 主鍵ID列表(不能爲 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
/**
* 根據 ID 修改
*
* @param entity 實體對象
*/
int updateById(@Param(Constants.ENTITY) T entity);
/**
* 根據 whereEntity 條件,更新記錄
*
* @param entity 實體對象 (set 條件值,可以爲 null)
* @param updateWrapper 實體對象封裝操作類(可以爲 null,裏面的 entity 用於生成 where 語句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
/**
* 根據 ID 查詢
*
* @param id 主鍵ID
*/
T selectById(Serializable id);
/**
* 查詢(根據ID 批量查詢)
*
* @param idList 主鍵ID列表(不能爲 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
/**
* 查詢(根據 columnMap 條件)
*
* @param columnMap 表字段 map 對象
*/
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根據 entity 條件,查詢一條記錄
*
* @param queryWrapper 實體對象封裝操作類(可以爲 null)
*/
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根據 Wrapper 條件,查詢總記錄數
*
* @param queryWrapper 實體對象封裝操作類(可以爲 null)
*/
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根據 entity 條件,查詢全部記錄
*
* @param queryWrapper 實體對象封裝操作類(可以爲 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根據 Wrapper 條件,查詢全部記錄
*
* @param queryWrapper 實體對象封裝操作類(可以爲 null)
*/
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根據 Wrapper 條件,查詢全部記錄
* <p>注意: 只返回第一個字段的值</p>
*
* @param queryWrapper 實體對象封裝操作類(可以爲 null)
*/
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根據 entity 條件,查詢全部記錄(並翻頁)
*
* @param page 分頁查詢條件(可以爲 RowBounds.DEFAULT)
* @param queryWrapper 實體對象封裝操作類(可以爲 null)
*/
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根據 Wrapper 條件,查詢全部記錄(並翻頁)
*
* @param page 分頁查詢條件
* @param queryWrapper 實體對象封裝操作類
*/
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
Wrapper 表示包裝
2.Wrapper.java
/**
* 條件構造抽象類
*
* @author hubin
* @since 2018-05-25
*/
@SuppressWarnings("all")
public abstract class Wrapper<T> implements ISqlSegment {
/**
* 實體對象(子類實現)
*
* @return 泛型 T
*/
public abstract T getEntity();
public String getSqlSelect() {
return null;
}
public String getSqlSet() {
return null;
}
public String getSqlComment() {
return null;
}
/**
* 獲取 MergeSegments
*/
public abstract MergeSegments getExpression();
/**
* 獲取自定義SQL 簡化自定義XML複雜情況
* <p>使用方法</p>
* <p>`自定義sql` + ${ew.customSqlSegment}</p>
* <p>1.邏輯刪除需要自己拼接條件 (之前自定義也同樣)</p>
* <p>2.不支持wrapper中附帶實體的情況 (wrapper自帶實體會更麻煩)</p>
* <p>3.用法 ${ew.customSqlSegment} (不需要where標籤包裹,切記!)</p>
* <p>4.ew是wrapper定義別名,可自行替換</p>
*/
public String getCustomSqlSegment() {
MergeSegments expression = getExpression();
if (Objects.nonNull(expression)) {
NormalSegmentList normal = expression.getNormal();
String sqlSegment = getSqlSegment();
if (StringUtils.isNotEmpty(sqlSegment)) {
if (normal.isEmpty()) {
return sqlSegment;
} else {
return Constants.WHERE + StringPool.SPACE + sqlSegment;
}
}
}
return StringPool.EMPTY;
}
/**
* 查詢條件爲空(包含entity)
*/
public boolean isEmptyOfWhere() {
return isEmptyOfNormal() && isEmptyOfEntity();
}
/**
* 查詢條件不爲空(包含entity)
*/
public boolean nonEmptyOfWhere() {
return !isEmptyOfWhere();
}
/**
* 查詢條件爲空(不包含entity)
*/
public boolean isEmptyOfNormal() {
return CollectionUtils.isEmpty(getExpression().getNormal());
}
/**
* 查詢條件爲空(不包含entity)
*/
public boolean nonEmptyOfNormal() {
return !isEmptyOfNormal();
}
/**
* 深層實體判斷屬性
*
* @return true 不爲空
*/
public boolean nonEmptyOfEntity() {
T entity = getEntity();
if (entity == null) {
return false;
}
TableInfo tableInfo = TableInfoHelper.getTableInfo(entity.getClass());
if (tableInfo == null) {
return false;
}
if (tableInfo.getFieldList().stream().anyMatch(e -> fieldStrategyMatch(entity, e))) {
return true;
}
return StringUtils.isNotEmpty(tableInfo.getKeyProperty()) ? Objects.nonNull(ReflectionKit.getMethodValue(entity, tableInfo.getKeyProperty())) : false;
}
/**
* 根據實體FieldStrategy屬性來決定判斷邏輯
*/
private boolean fieldStrategyMatch(T entity, TableFieldInfo e) {
switch (e.getWhereStrategy()) {
case NOT_NULL:
return Objects.nonNull(ReflectionKit.getMethodValue(entity, e.getProperty()));
case IGNORED:
return true;
case NOT_EMPTY:
return StringUtils.checkValNotNull(ReflectionKit.getMethodValue(entity, e.getProperty()));
case NEVER:
return false;
default:
return Objects.nonNull(ReflectionKit.getMethodValue(entity, e.getProperty()));
}
}
/**
* 深層實體判斷屬性
*
* @return true 爲空
*/
public boolean isEmptyOfEntity() {
return !nonEmptyOfEntity();
}
}
三、CRUD操作
四、條件查詢器
3.x版本以前 條件查詢器EntityWrapper.java
,3.x版本改爲QueryWrapper.java
QueryChainWrapper<T> extends AbstractChainWrapper
//查詢年齡在18-50 之間且性別爲男姓名爲tom的所有用戶
employeerMapper.selectPage(new Page<Employeer>(1,4),new QueryWrapper<Employee>().between("age",18,50).eq("gender",1).eq("last_name","tom")).forEach(System.out::println);
AbstractWrapper.java
/**
* 所有包裝類都繼承此抽象類,此抽象類代理了大部分生成 where 條件的方法
* <li> 泛型: Children ,表示子類 </li>
* <li> 泛型: Param ,表示子類所包裝的具體 Wrapper 類型 </li>
*
* @author miemie
* @since 2018-12-19
*/
@SuppressWarnings({"serial", "unchecked"})
public abstract class AbstractChainWrapper<T, R, Children extends AbstractChainWrapper<T, R, Children, Param>, Param>
extends Wrapper<T> implements Compare<Children, R>, Func<Children, R>, Join<Children>, Nested<Param, Children> {
protected final Children typedThis = (Children) this;
/**
* 子類所包裝的具體 Wrapper 類型
*/
protected Param wrapperChildren;
/**
* 必須的構造函數
*/
public AbstractChainWrapper() {
}
@SuppressWarnings("rawtypes")
public AbstractWrapper getWrapper() {
return (AbstractWrapper) wrapperChildren;
}
@Override
public T getEntity() {
throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getEntity");
}
@Override
public MergeSegments getExpression() {
throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getExpression");
}
@Override
public String getCustomSqlSegment() {
throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getCustomSqlSegment");
}
@Override
public <V> Children allEq(boolean condition, Map<R, V> params, boolean null2IsNull) {
getWrapper().allEq(condition, params, null2IsNull);
return typedThis;
}
@Override
public <V> Children allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull) {
getWrapper().allEq(condition, filter, params, null2IsNull);
return typedThis;
}
@Override
public Children eq(boolean condition, R column, Object val) {
getWrapper().eq(condition, column, val);
return typedThis;
}
@Override
public Children ne(boolean condition, R column, Object val) {
getWrapper().ne(condition, column, val);
return typedThis;
}
@Override
public Children gt(boolean condition, R column, Object val) {
getWrapper().gt(condition, column, val);
return typedThis;
}
@Override
public Children ge(boolean condition, R column, Object val) {
getWrapper().ge(condition, column, val);
return typedThis;
}
@Override
public Children lt(boolean condition, R column, Object val) {
getWrapper().lt(condition, column, val);
return typedThis;
}
@Override
public Children le(boolean condition, R column, Object val) {
getWrapper().le(condition, column, val);
return typedThis;
}
@Override
public Children between(boolean condition, R column, Object val1, Object val2) {
getWrapper().between(condition, column, val1, val2);
return typedThis;
}
@Override
public Children notBetween(boolean condition, R column, Object val1, Object val2) {
getWrapper().notBetween(condition, column, val1, val2);
return typedThis;
}
@Override
public Children like(boolean condition, R column, Object val) {
getWrapper().like(condition, column, val);
return typedThis;
}
@Override
public Children notLike(boolean condition, R column, Object val) {
getWrapper().notLike(condition, column, val);
return typedThis;
}
@Override
public Children likeLeft(boolean condition, R column, Object val) {
getWrapper().likeLeft(condition, column, val);
return typedThis;
}
@Override
public Children likeRight(boolean condition, R column, Object val) {
getWrapper().likeRight(condition, column, val);
return typedThis;
}
@Override
public Children isNull(boolean condition, R column) {
getWrapper().isNull(condition, column);
return typedThis;
}
@Override
public Children isNotNull(boolean condition, R column) {
getWrapper().isNotNull(condition, column);
return typedThis;
}
@Override
public Children in(boolean condition, R column, Collection<?> coll) {
getWrapper().in(condition, column, coll);
return typedThis;
}
@Override
public Children notIn(boolean condition, R column, Collection<?> coll) {
getWrapper().notIn(condition, column, coll);
return typedThis;
}
@Override
public Children inSql(boolean condition, R column, String inValue) {
getWrapper().inSql(condition, column, inValue);
return typedThis;
}
@Override
public Children notInSql(boolean condition, R column, String inValue) {
getWrapper().notInSql(condition, column, inValue);
return typedThis;
}
@Override
public Children groupBy(boolean condition, R... columns) {
getWrapper().groupBy(condition, columns);
return typedThis;
}
@Override
public Children orderBy(boolean condition, boolean isAsc, R... columns) {
getWrapper().orderBy(condition, isAsc, columns);
return typedThis;
}
@Override
public Children having(boolean condition, String sqlHaving, Object... params) {
getWrapper().having(condition, sqlHaving, params);
return typedThis;
}
@Override
public Children or(boolean condition) {
getWrapper().or(condition);
return typedThis;
}
@Override
public Children apply(boolean condition, String applySql, Object... value) {
getWrapper().apply(condition, applySql, value);
return typedThis;
}
@Override
public Children last(boolean condition, String lastSql) {
getWrapper().last(condition, lastSql);
return typedThis;
}
@Override
public Children comment(boolean condition, String comment) {
getWrapper().comment(condition, comment);
return typedThis;
}
@Override
public Children exists(boolean condition, String existsSql) {
getWrapper().exists(condition, existsSql);
return typedThis;
}
@Override
public Children notExists(boolean condition, String notExistsSql) {
getWrapper().notExists(condition, notExistsSql);
return typedThis;
}
@Override
public Children and(boolean condition, Consumer<Param> consumer) {
getWrapper().and(condition, consumer);
return typedThis;
}
@Override
public Children or(boolean condition, Consumer<Param> consumer) {
getWrapper().or(condition, consumer);
return typedThis;
}
@Override
public Children nested(boolean condition, Consumer<Param> consumer) {
getWrapper().nested(condition, consumer);
return typedThis;
}
@Override
public String getSqlSegment() {
throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getSqlSegment");
}
}
QueryWrapper.java
/**
* Entity 對象封裝操作類
*
* @author hubin miemie HCL
* @since 2018-05-25
*/
@SuppressWarnings("serial")
public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>>
implements Query<QueryWrapper<T>, T, String> {
/**
* 查詢字段
*/
private SharedString sqlSelect = new SharedString();
public QueryWrapper() {
this(null);
}
public QueryWrapper(T entity) {
super.setEntity(entity);
super.initNeed();
}
public QueryWrapper(T entity, String... columns) {
super.setEntity(entity);
super.initNeed();
this.select(columns);
}
/**
* 非對外公開的構造方法,只用於生產嵌套 sql
*
* @param entityClass 本不應該需要的
*/
private QueryWrapper(T entity, Class<T> entityClass, AtomicInteger paramNameSeq,
Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments,
SharedString lastSql, SharedString sqlComment) {
super.setEntity(entity);
this.entityClass = entityClass;
this.paramNameSeq = paramNameSeq;
this.paramNameValuePairs = paramNameValuePairs;
this.expression = mergeSegments;
this.lastSql = lastSql;
this.sqlComment = sqlComment;
}
@Override
public QueryWrapper<T> select(String... columns) {
if (ArrayUtils.isNotEmpty(columns)) {
this.sqlSelect.setStringValue(String.join(StringPool.COMMA, columns));
}
return typedThis;
}
@Override
public QueryWrapper<T> select(Predicate<TableFieldInfo> predicate) {
return select(entityClass, predicate);
}
@Override
public QueryWrapper<T> select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) {
this.entityClass = entityClass;
this.sqlSelect.setStringValue(TableInfoHelper.getTableInfo(getCheckEntityClass()).chooseSelect(predicate));
return typedThis;
}
@Override
public String getSqlSelect() {
return sqlSelect.getStringValue();
}
/**
* 返回一個支持 lambda 函數寫法的 wrapper
*/
public LambdaQueryWrapper<T> lambda() {
return new LambdaQueryWrapper<>(entity, entityClass, sqlSelect, paramNameSeq, paramNameValuePairs, expression,
lastSql, sqlComment);
}
/**
* 用於生成嵌套 sql
* <p>
* 故 sqlSelect 不向下傳遞
* </p>
*/
@Override
protected QueryWrapper<T> instance() {
return new QueryWrapper<>(entity, entityClass, paramNameSeq, paramNameValuePairs, new MergeSegments(),
SharedString.emptyString(), SharedString.emptyString());
}
}
查詢條件
或者
//或者 or() (gender = ? and last_name like ? or email like ?)
new QueryWrapper().eq("gender",0).like("last_name","老師").or().like("email","a");
//orNew() (Gender = ? and last_name like ?)or(email liek ?) 3.x版本之後都沒有這個方法
new QueryWrapper().eq("gender",0).like("last_name","老師").orNew().like("email","a");
條件修改
//修改zx 的年齡爲29
Employee employee = new Employee();
employee.setAge(29);
int update = employeeMapper.update(employee, new UpdateWrapper<Employee>().eq("last_name", "zx"));
/*
UPDATE tbl_employee SET age=?
WHERE (last_name = ?)
UPDATE tbl_employee SET age=29
WHERE (last_name = 'zx');
*/
排序
//查詢性別爲 0 ,根據age排序 asc/desc ,簡單分頁
List<Employee> employees = employeeMapper.selectList(new QueryWrapper<Employee>()
.eq("gender", 0)
.orderByAsc("age")
//.orderByDesc("age","gender")
//last() 方法,有sql注入的風險
.last("limit 1,2")
);
/*
SELECT id,last_name,gender,age,email FROM tbl_employee
WHERE (gender = ?) ORDER BY age ASC limit 1,2
寫法有很多種,根據自己習慣進行選擇
*/
五、AR模式【不推薦】
僅僅需要讓實體類繼承 Model 類且實現主鍵指定方法,即可開啓 AR 之旅.這個是版本在3.x以前的寫法,3.x版本以後的寫法就是直接繼承Model<T>
@Data
@TableName("tb_user")
public class User extends Model<User> {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
// mybatisplus版本在3.x以後就不用寫
protected Serializable pkVal() {
return this.id; }
}
CREATE TABLE tb_user(
id INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(50),
age INT
);
錯誤:Not Found TableInfoCache.
/*
mybatisplus activerecord之mybatisplus entity XXX Not Found TableInfoCache.
1.需要重寫pkVal()方法 // mybatisplus版本在3.x以後就不用寫
2.編寫mapper.比如:public interface UserMapper extends BaseMapper<User>{ }
*/
案例:
public class TestModel extends MybatisplugsdemoApplicationTests {
//添加
@Test
public void fn(){
User user = new User();
user.setName("marry");
user.setAge(11);
user.insert();
}
//查詢
@Test
public void findAll(){
User user = new User();
user.selectList(null).forEach(System.out::println);
}
}
六、代碼生成器
AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個模塊的代碼,極大的提升了開發效率。
說明
自定義模板有哪些可用參數?Github Gitee AbstractTemplateEngine 類中方法 getObjectMap 返回 objectMap 的所有值都可用
- 表及字段命名策略選擇在 MP 中,我們建議數據庫表名 和 表字段名採用駝峯命名方式, 如果採用下劃線命名方式 請開啓全局下劃線開關,如果表名字段名命名方式不一致請註解指定,我們建議最好保持一致(2.3版本以後默認是開啓的)。這麼做的原因是爲了避免在對應實體類時產生的性能損耗,這樣字段不用做映射就能直接和實體類對應。當然如果項目裏不用考慮這點性能損耗,那麼你採用下滑線也是沒問題的,只需要在生成代碼時配置 dbColumnUnderline 屬性就可以
1.MP 的代碼生成器默認使用的是 Apache 的 Velocity 模板,當然也可以更換爲別的模板技術,例如 freemarker。
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
Strategy 策率
頂級Service層
IService.java
/**
* 頂級 Service
*
* @author hubin
* @since 2018-06-23
*/
public interface IService<T> {
/**
* 插入一條記錄(選擇字段,策略插入)
*
* @param entity 實體對象
*/
boolean save(T entity);
/**
* 插入(批量)
*
* @param entityList 實體對象集合
*/
@Transactional(rollbackFor = Exception.class)
default boolean saveBatch(Collection<T> entityList) {
return saveBatch(entityList, 1000);
}
/**
* 插入(批量)
*
* @param entityList 實體對象集合
* @param batchSize 插入批次數量
*/
boolean saveBatch(Collection<T> entityList, int batchSize);
/**
* 批量修改插入
*
* @param entityList 實體對象集合
*/
@Transactional(rollbackFor = Exception.class)
default boolean saveOrUpdateBatch(Collection<T> entityList) {
return saveOrUpdateBatch(entityList, 1000);
}
/**
* 批量修改插入
*
* @param entityList 實體對象集合
* @param batchSize 每次的數量
*/
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
/**
* 根據 ID 刪除
*
* @param id 主鍵ID
*/
boolean removeById(Serializable id);
/**
* 根據 columnMap 條件,刪除記錄
*
* @param columnMap 表字段 map 對象
*/
boolean removeByMap(Map<String, Object> columnMap);
/**
* 根據 entity 條件,刪除記錄
*
* @param queryWrapper 實體包裝類 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
boolean remove(Wrapper<T> queryWrapper);
/**
* 刪除(根據ID 批量刪除)
*
* @param idList 主鍵ID列表
*/
boolean removeByIds(Collection<? extends Serializable> idList);
/**
* 根據 ID 選擇修改
*
* @param entity 實體對象
*/
boolean updateById(T entity);
/**
* 根據 whereEntity 條件,更新記錄
*
* @param entity 實體對象
* @param updateWrapper 實體對象封裝操作類 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/
boolean update(T entity, Wrapper<T> updateWrapper);
/**
* 根據 UpdateWrapper 條件,更新記錄 需要設置sqlset
*
* @param updateWrapper 實體對象封裝操作類 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/
default boolean update(Wrapper<T> updateWrapper) {
return update(null, updateWrapper);
}
/**
* 根據ID 批量更新
*
* @param entityList 實體對象集合
*/
@Transactional(rollbackFor = Exception.class)
default boolean updateBatchById(Collection<T> entityList) {
return updateBatchById(entityList, 1000);
}
/**
* 根據ID 批量更新
*
* @param entityList 實體對象集合
* @param batchSize 更新批次數量
*/
boolean updateBatchById(Collection<T> entityList, int batchSize);
/**
* TableId 註解存在更新記錄,否插入一條記錄
*
* @param entity 實體對象
*/
boolean saveOrUpdate(T entity);
/**
* 根據 ID 查詢
*
* @param id 主鍵ID
*/
T getById(Serializable id);
/**
* 查詢(根據ID 批量查詢)
*
* @param idList 主鍵ID列表
*/
Collection<T> listByIds(Collection<? extends Serializable> idList);
/**
* 查詢(根據 columnMap 條件)
*
* @param columnMap 表字段 map 對象
*/
Collection<T> listByMap(Map<String, Object> columnMap);
/**
* 根據 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);
/**
* 根據 Wrapper,查詢一條記錄
*
* @param queryWrapper 實體對象封裝操作類 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
Map<String, Object> getMap(Wrapper<T> queryWrapper);
/**
* 根據 Wrapper,查詢一條記錄
*
* @param queryWrapper 實體對象封裝操作類 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
* @param mapper 轉換函數
*/
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
/**
* 根據 Wrapper 條件,查詢總記錄數
*
* @param queryWrapper 實體對象封裝操作類 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
int count(Wrapper<T> queryWrapper);
/**
* 查詢總記錄數
*
* @see Wrappers#emptyWrapper()
*/
default int count() {
return count(Wrappers.emptyWrapper());
}
/**
* 查詢列表
*
* @param queryWrapper 實體對象封裝操作類 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
List<T> list(Wrapper<T> queryWrapper);
/**
* 查詢所有
*
* @see Wrappers#emptyWrapper()
*/
default List<T> list() {
return list(Wrappers.emptyWrapper());
}
/**
* 翻頁查詢
*
* @param page 翻頁對象
* @param queryWrapper 實體對象封裝操作類 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
/**
* 無條件翻頁查詢
*
* @param page 翻頁對象
* @see Wrappers#emptyWrapper()
*/
default IPage<T> page(IPage<T> page) {
return page(page, Wrappers.emptyWrapper());
}
/**
* 查詢列表
*
* @param queryWrapper 實體對象封裝操作類 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
/**
* 查詢所有列表
*
* @see Wrappers#emptyWrapper()
*/
default List<Map<String, Object>> listMaps() {
return listMaps(Wrappers.emptyWrapper());
}
/**
* 查詢全部記錄
*/
default List<Object> listObjs() {
return listObjs(Function.identity());
}
/**
* 查詢全部記錄
*
* @param mapper 轉換函數
*/
default <V> List<V> listObjs(Function<? super Object, V> mapper) {
return listObjs(Wrappers.emptyWrapper(), mapper);
}
/**
* 根據 Wrapper 條件,查詢全部記錄
*
* @param queryWrapper 實體對象封裝操作類 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default List<Object> listObjs(Wrapper<T> queryWrapper) {
return listObjs(queryWrapper, Function.identity());
}
/**
* 根據 Wrapper 條件,查詢全部記錄
*
* @param queryWrapper 實體對象封裝操作類 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
* @param mapper 轉換函數
*/
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
/**
* 翻頁查詢
*
* @param page 翻頁對象
* @param queryWrapper 實體對象封裝操作類 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
/**
* 無條件翻頁查詢
*
* @param page 翻頁對象
* @see Wrappers#emptyWrapper()
*/
default IPage<Map<String, Object>> pageMaps(IPage<T> page) {
return pageMaps(page, Wrappers.emptyWrapper());
}
/**
* 獲取對應 entity 的 BaseMapper
*
* @return BaseMapper
*/
BaseMapper<T> getBaseMapper();
/**
* 以下的方法使用介紹:
*
* 一. 名稱介紹
* 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 new QueryChainWrapper<>(getBaseMapper());
}
/**
* 鏈式查詢 lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaQueryWrapper 的包裝類
*/
default LambdaQueryChainWrapper<T> lambdaQuery() {
return new LambdaQueryChainWrapper<>(getBaseMapper());
}
/**
* 鏈式更改 普通
*
* @return UpdateWrapper 的包裝類
*/
default UpdateChainWrapper<T> update() {
return new UpdateChainWrapper<>(getBaseMapper());
}
/**
* 鏈式更改 lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaUpdateWrapper 的包裝類
*/
default LambdaUpdateChainWrapper<T> lambdaUpdate() {
return new LambdaUpdateChainWrapper<>(getBaseMapper());
}
/**
* <p>
* 根據updateWrapper嘗試更新,否繼續執行saveOrUpdate(T)方法
* 此次修改主要是減少了此項業務代碼的代碼量(存在性驗證之後的saveOrUpdate操作)
* </p>
*
* @param entity 實體對象
*/
default boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) {
return update(entity, updateWrapper) || saveOrUpdate(entity);
}
}
ServiceImpl.java
**
* IService 實現類( 泛型:M 是 mapper 對象,T 是實體 , PK 是主鍵泛型 )
*
* @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;
}
/**
* 判斷數據庫操作是否成功
*
* @param result 數據庫操作返回影響條數
* @return boolean
*/
protected boolean retBool(Integer result) {
return SqlHelper.retBool(result);
}
protected Class<T> currentModelClass() {
return (Class<T>) ReflectionKit.getSuperClassGenericType(getClass(), 1);
}
/**
* 批量操作 SqlSession
*/
protected SqlSession sqlSessionBatch() {
return SqlHelper.sqlSessionBatch(currentModelClass());
}
/**
* 釋放sqlSession
*
* @param sqlSession session
*/
protected void closeSqlSession(SqlSession sqlSession) {
SqlSessionUtils.closeSqlSession(sqlSession, GlobalConfigUtils.currentSessionFactory(currentModelClass()));
}
/**
* 獲取 SqlStatement
*
* @param sqlMethod ignore
* @return ignore
*/
protected String sqlStatement(SqlMethod sqlMethod) {
return SqlHelper.table(currentModelClass()).getSqlStatement(sqlMethod.getMethod());
}
@Override
public boolean save(T entity) {
return retBool(baseMapper.insert(entity));
}
/**
* 批量插入
*
* @param entityList ignore
* @param batchSize ignore
* @return ignore
*/
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) {
String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
try (SqlSession batchSqlSession = sqlSessionBatch()) {
int i = 0;
for (T anEntityList : entityList) {
batchSqlSession.insert(sqlStatement, anEntityList);
if (i >= 1 && i % batchSize == 0) {
batchSqlSession.flushStatements();
}
i++;
}
batchSqlSession.flushStatements();
}
return true;
}
/**
* TableId 註解存在更新記錄,否插入一條記錄
*
* @param entity 實體對象
* @return boolean
*/
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveOrUpdate(T entity) {
if (null != entity) {
Class<?> cls = entity.getClass();
TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
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.getMethodValue(cls, entity, tableInfo.getKeyProperty());
return StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal)) ? save(entity) : updateById(entity);
}
return false;
}
Servcie層寫法
public interface EmployeeService extends IService<Employee>{}
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper,Employee> implements EmployeeService{}
七、分頁插件
1.插件機制:
Mybatis 通過插件(Interceptor) 可以做到攔截四大對象相關方法的執行,根據需求,完成相關數據的動態改變。
Executor
StatementHandler
ParameterHandler
ResultSetHandler
2.插件原理
四大對象的每個對象在創建時,都會執行 interceptorChain.pluginAll(),會經過每個插件對象的 plugin()方法,目的是爲當前的四大對象創建代理。代理對象就可以攔截到四大對象相關方法的執行,因爲要執行四大對象的方法需要經過代理
分頁插件
com.baomidou.mybatisplus.plugins.PaginationInterceptor
@Configuration
public class MyBatisPlusConfig {
//註冊插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
Page<Employee> employeeIPage =(Page<Employee>) employeeMapper.selectPage(new Page<>(2, 2), null);
employeeIPage.getRecords().forEach(System.out::println);
public class Page<T> implements IPage<T> {
private static final long serialVersionUID = 8545996863226528798L;
/**
* 查詢數據列表
*/
private List<T> records = Collections.emptyList();
/**
* 總數
*/
private long total = 0;
/**
* 每頁顯示條數,默認 10
*/
private long size = 10;
/**
* 當前頁
*/
private long current = 1;
/**
* 排序字段信息
*/
private List<OrderItem> orders = new ArrayList<>();
/**
* 自動優化 COUNT SQL
*/
private boolean optimizeCountSql = true;
/**
* 是否進行 count 查詢
*/
private boolean isSearchCount = true;
public Page() {
}
/**
* 分頁構造函數
*
* @param current 當前頁
* @param size 每頁顯示條數
*/
public Page(long current, long size) {
this(current, size, 0);
}
public Page(long current, long size, long total) {
this(current, size, total, true);
}
public Page(long current, long size, boolean isSearchCount) {
this(current, size, 0, isSearchCount);
}
public Page(long current, long size, long total, boolean isSearchCount) {
if (current > 1) {
this.current = current;
}
this.size = size;
this.total = total;
this.isSearchCount = isSearchCount;
}
/**
* 是否存在上一頁
*
* @return true / false
*/
public boolean hasPrevious() {
return this.current > 1;
}
/**
* 是否存在下一頁
*
* @return true / false
*/
public boolean hasNext() {
return this.current < this.getPages();
}
@Override
public List<T> getRecords() {
return this.records;
}
@Override
public Page<T> setRecords(List<T> records) {
this.records = records;
return this;
}
@Override
public long getTotal() {
return this.total;
}
@Override
public Page<T> setTotal(long total) {
this.total = total;
return this;
}
@Override
public long getSize() {
return this.size;
}
@Override
public Page<T> setSize(long size) {
this.size = size;
return this;
}
@Override
public long getCurrent() {
return this.current;
}
@Override
public Page<T> setCurrent(long current) {
this.current = current;
return this;
}
/**
* 獲取當前正序排列的字段集合
* <p>
* 爲了兼容,將在不久後廢棄
*
* @return 正序排列的字段集合
* @see #getOrders()
* @deprecated 3.2.0
*/
@Override
@Nullable
@Deprecated
public String[] ascs() {
return CollectionUtils.isNotEmpty(orders) ? mapOrderToArray(OrderItem::isAsc) : null;
}
/**
* 查找 order 中正序排序的字段數組
*
* @param filter 過濾器
* @return 返回正序排列的字段數組
*/
private String[] mapOrderToArray(Predicate<OrderItem> filter) {
List<String> columns = new ArrayList<>(orders.size());
orders.forEach(i -> {
if (filter.test(i)) {
columns.add(i.getColumn());
}
});
return columns.toArray(new String[0]);
}
/**
* 移除符合條件的條件
*
* @param filter 條件判斷
*/
private void removeOrder(Predicate<OrderItem> filter) {
for (int i = orders.size() - 1; i >= 0; i--) {
if (filter.test(orders.get(i))) {
orders.remove(i);
}
}
}
/**
* 添加新的排序條件,構造條件可以使用工廠:{@link OrderItem#build(String, boolean)}
*
* @param items 條件
* @return 返回分頁參數本身
*/
public Page<T> addOrder(OrderItem... items) {
orders.addAll(Arrays.asList(items));
return this;
}
/**
* 添加新的排序條件,構造條件可以使用工廠:{@link OrderItem#build(String, boolean)}
*
* @param items 條件
* @return 返回分頁參數本身
*/
public Page<T> addOrder(List<OrderItem> items) {
orders.addAll(items);
return this;
}
/**
* 設置需要進行正序排序的字段
* <p>
* Replaced:{@link #addOrder(OrderItem...)}
*
* @param ascs 字段
* @return 返回自身
* @deprecated 3.2.0
*/
@Deprecated
public Page<T> setAscs(List<String> ascs) {
return CollectionUtils.isNotEmpty(ascs) ? setAsc(ascs.toArray(new String[0])) : this;
}
/**
* 升序
* <p>
* Replaced:{@link #addOrder(OrderItem...)}
*
* @param ascs 多個升序字段
* @deprecated 3.2.0
*/
@Deprecated
public Page<T> setAsc(String... ascs) {
// 保證原來方法 set 的語意
removeOrder(OrderItem::isAsc);
for (String s : ascs) {
addOrder(OrderItem.asc(s));
}
return this;
}
/**
* 獲取需簡要倒序排列的字段數組
* <p>
*
* @return 倒序排列的字段數組
* @see #getOrders()
* @deprecated 3.2.0
*/
@Override
@Deprecated
public String[] descs() {
return mapOrderToArray(i -> !i.isAsc());
}
/**
* Replaced:{@link #addOrder(OrderItem...)}
*
* @param descs 需要倒序排列的字段
* @return 自身
* @deprecated 3.2.0
*/
@Deprecated
public Page<T> setDescs(List<String> descs) {
// 保證原來方法 set 的語意
if (CollectionUtils.isNotEmpty(descs)) {
removeOrder(item -> !item.isAsc());
for (String s : descs) {
addOrder(OrderItem.desc(s));
}
}
return this;
}
/**
* 降序,這方法名不知道是誰起的
* <p>
* Replaced:{@link #addOrder(OrderItem...)}
*
* @param descs 多個降序字段
* @deprecated 3.2.0
*/
@Deprecated
public Page<T> setDesc(String... descs) {
setDescs(Arrays.asList(descs));
return this;
}
@Override
public List<OrderItem> orders() {
return getOrders();
}
public List<OrderItem> getOrders() {
return orders;
}
public void setOrders(List<OrderItem> orders) {
this.orders = orders;
}
@Override
public boolean optimizeCountSql() {
return optimizeCountSql;
}
@Override
public boolean isSearchCount() {
if (total < 0) {
return false;
}
return isSearchCount;
}
public Page<T> setSearchCount(boolean isSearchCount) {
this.isSearchCount = isSearchCount;
return this;
}
public Page<T> setOptimizeCountSql(boolean optimizeCountSql) {
this.optimizeCountSql = optimizeCountSql;
return this;
}
}
執行分析插件
- com.baomidou.mybatisplus.plugins.SqlExplainInterceptor
- SQL 執行分析攔截器,只支持 MySQL5.6.3 以上版本
- 該插件的作用是分析 DELETE UPDATE 語句,防止小白
或者惡意進行 DELETE UPDATE 全表操作 - 只建議在開發環境中使用,不建議在生產環境使用
- 在插件的底層 通過 SQL 語句分析命令:Explain 分析當前的 SQL 語句,
根據結果集中的 Extra 列來斷定當前是否全表操作。
@Configuration
public class MyBatisPlusConfig {
//註冊插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
//分析插件
@Bean
public SqlExplainInterceptor sqlExplainInterceptor(){
SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
sqlParserList.add(new BlockAttackSqlParser());
sqlExplainInterceptor.setSqlParserList(sqlParserList);
return sqlExplainInterceptor;
}
}
@Test
public void fn(){
try {
employeeMapper.delete(null);
}catch (Exception e){
e.printStackTrace();
System.out.println("不能全表刪除");
}
}
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error updating database. Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:78)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
at com.sun.proxy.$Proxy65.delete(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.delete(SqlSessionTemplate.java:303)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:68)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:61)
at com.sun.proxy.$Proxy68.delete(Unknown Source)
at com.mybatisplus.mybatisplugsdemo.DeleTest.fn(DeleTest.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error updating database. Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:199)
at org.apache.ibatis.session.defaults.DefaultSqlSession.delete(DefaultSqlSession.java:212)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426)
... 37 more
Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:49)
at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38)
at com.baomidou.mybatisplus.core.toolkit.Assert.notNull(Assert.java:72)
at com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser.processDelete(BlockAttackSqlParser.java:40)
at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.processParser(AbstractJsqlParser.java:94)
at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.parser(AbstractJsqlParser.java:67)
at com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler.sqlParser(AbstractSqlParserHandler.java:76)
at com.baomidou.mybatisplus.extension.plugins.SqlExplainInterceptor.intercept(SqlExplainInterceptor.java:63)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
at com.sun.proxy.$Proxy89.update(Unknown Source)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197)
... 43 more
不能全表刪除
性能分析插件
- com.baomidou.mybatisplus.plugins.PerformanceInterceptor
- 性能分析攔截器,用於輸出每條 SQL 語句及其執行時間
- SQL 性能執行分析,開發環境使用,超過指定時間,停止運行。有助於發現問題
性能分析插件是3.2.0 以上版本移除,推薦使用第三方擴展 執行 SQL
樂觀鎖插件
- com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor
- 如果想實現如下需求: 當要更新一條記錄的時候,希望這條記錄沒有被別人更新
- 樂觀鎖的實現原理:
取出記錄時,獲取當前 version 2
更新時,帶上這個 version 2
執行更新時, set version = yourVersion+1 where version = yourVersion
如果 version 不對,就更新失敗 - @Version 用於註解實體字段,必須要有。
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
OptimisticLockerInterceptor optimisticLockerInterceptor = new OptimisticLockerInterceptor();
return optimisticLockerInterceptor;
}
特別說明:
- 支持的數據類型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整數類型下
newVersion = oldVersion + 1
newVersion
會回寫到entity
中- 僅支持
updateById(id)
與update(entity, wrapper)
方法- 在
update(entity, wrapper)
方法下,wrapper
不能複用!!!
@Test
public void fn (){
//跟新操作
Employee employee = new Employee();
employee.setId(10);
employee.setLastName("tome");
employee.setEmail("[email protected]");
employee.setGender(1);
employee.setAge(22);
employee.setVersion(3);
employeeMapper.update(employee,null);
}
UPDATE tbl_employee SET last_name=?,
gender=?,
version=?,
age=?,
email=?
WHERE (version = ?)
UPDATE tbl_employee SET last_name='tome',
gender=1,
version=4,
age=22,
email='[email protected]'
WHERE (version = 3);
update tbl_user set name = 'update',version = 3 where id = 100 and version = 2
# 條件中version是我們的。 set 中的version是數據庫返回來的。
八、自定義全局操作 SQL注入
SQL 自動注入器接口 ISqlInjector
@Configuration
public class MySqlInjector implements ISqlInjector {
@Override
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
//將EmployeeMapper中定義deleteAll,處理成對已的MapperStatment對象,加入到configuration對線中
}
}
/**
* SQL 自動注入器
*
* @author hubin
* @since 2018-04-07
*/
public abstract class AbstractSqlInjector implements ISqlInjector {
private static final Log logger = LogFactory.getLog(AbstractSqlInjector.class);
@Override
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
Class<?> modelClass = extractModelClass(mapperClass);
if (modelClass != null) {
String className = mapperClass.toString();
Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
if (!mapperRegistryCache.contains(className)) {
List<AbstractMethod> methodList = this.getMethodList(mapperClass);
if (CollectionUtils.isNotEmpty(methodList)) {
TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
// 循環注入自定義方法
methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
} else {
logger.debug(mapperClass.toString() + ", No effective injection method was found.");
}
mapperRegistryCache.add(className);
}
}
}
/**
* <p>
* 獲取 注入的方法
* </p>
*
* @param mapperClass 當前mapper
* @return 注入的方法集合
* @since 3.1.2 add mapperClass
*/
public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass);
/**
* 提取泛型模型,多泛型的時候請將泛型T放在第一位
*
* @param mapperClass mapper 接口
* @return mapper 泛型
*/
protected Class<?> extractModelClass(Class<?> mapperClass) {
Type[] types = mapperClass.getGenericInterfaces();
ParameterizedType target = null;
for (Type type : types) {
if (type instanceof ParameterizedType) {
Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments();
if (ArrayUtils.isNotEmpty(typeArray)) {
for (Type t : typeArray) {
if (t instanceof TypeVariable || t instanceof WildcardType) {
break;
} else {
target = (ParameterizedType) type;
break;
}
}
}
break;
}
}
return target == null ? null : (Class<?>) target.getActualTypeArguments()[0];
}
}
九、邏輯刪除
- application.yml 加入配置(如果你的默認值和mp默認的一樣,該配置可無):
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag #全局邏輯刪除字段值 3.3.0開始支持,詳情看下面。
logic-delete-value: 1 # 邏輯已刪除值(默認爲 1)
logic-not-delete-value: 0 # 邏輯未刪除值(默認爲 0)
- 實體類字段上加上
@TableLogic
註解
@Data
@TableName("tb_user")
public class User{
@TableId(type=IdType.AUTO)
private Integer id;
private String name;
@TableLogic
private Integer logicfalg; //logic_falg
}
說明:
- 字段支持所有數據類型(推薦使用
Integer
,Boolean
,LocalDateTime
)- 如果使用
LocalDateTime
,建議邏輯未刪除值設置爲字符串null
,邏輯刪除值只支持數據庫函數例如now()
- 效果: 使用mp自帶方法刪除和查找都會附帶邏輯刪除功能
# 刪除
update user set deleted=1 where id =1 and deleted=0
# 查找
select * from user where deleted=0
十、公共字段自動填充
實現元對象處理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
https://mp.baomidou.com/guide/auto-fill-metainfo.html
insertFill(MetaObject metaObject)
updateFill(MetaObject metaObject)
metaobject: 元對象. 是 Mybatis 提供的一個用於更加方便,更加優雅的訪問對象的屬性,
給對象的屬性設置值 的一個對象. 還會用於包裝對象. 支持對 Object 、Map、Collection
等對象進行包裝
本質上 metaObject 獲取對象的屬性值或者是給對象的屬性設置值,最終是要
通過 Reflector 獲取到屬性的對應方法的 Invoker, 最終 invoke.
/**
* 元對象字段填充控制器抽象類,實現公共字段自動寫入
*
* @author hubin
* @since 2016-08-28
*/
public interface MetaObjectHandler {
/**
* 樂觀鎖常量
*
* @deprecated 3.1.1 {@link Constants#MP_OPTLOCK_ET_ORIGINAL}
*/
String MP_OPTLOCK_ET_ORIGINAL = "MP_OPTLOCK_ET_ORIGINAL";
/**
* 插入元對象字段填充(用於插入時對公共字段的填充)
*
* @param metaObject 元對象
*/
void insertFill(MetaObject metaObject);
/**
* 更新元對象字段填充(用於更新時對公共字段的填充)
*
* @param metaObject 元對象
*/
void updateFill(MetaObject metaObject);
/**
* 通用填充
*
* @param fieldName java bean property name
* @param fieldVal java bean property value
* @param metaObject meta object parameter
*/
default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
if (Objects.nonNull(fieldVal)) {
if (metaObject.hasSetter(fieldName) && metaObject.hasGetter(fieldName)) {
metaObject.setValue(fieldName, fieldVal);
} else if (metaObject.hasGetter(Constants.ENTITY)) {
Object et = metaObject.getValue(Constants.ENTITY);
if (et != null) {
MetaObject etMeta = SystemMetaObject.forObject(et);
if (etMeta.hasSetter(fieldName)) {
etMeta.setValue(fieldName, fieldVal);
}
}
}
}
return this;
}
/**
* insert 時填充,只會填充 fill 被標識爲 INSERT 與 INSERT_UPDATE 的字段
*
* @param fieldName java bean property name
* @param fieldVal java bean property value
* @param metaObject meta object parameter
* @since 3.0.7
*/
default MetaObjectHandler setInsertFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
return setFieldValByName(fieldName, fieldVal, metaObject, FieldFill.INSERT);
}
/**
* update 時填充,只會填充 fill 被標識爲 UPDATE 與 INSERT_UPDATE 的字段
*
* @param fieldName java bean property name
* @param fieldVal java bean property value
* @param metaObject meta object parameter
* @since 3.0.7
*/
default MetaObjectHandler setUpdateFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
return setFieldValByName(fieldName, fieldVal, metaObject, FieldFill.UPDATE);
}
/**
* Common method to set value for java bean.
* <p>如果包含前綴 et 使用該方法,否則可以直接 metaObject.setValue(fieldName, fieldVal);</p>
*
* @param fieldName java bean property name
* @param fieldVal java bean property value
* @param metaObject meta object parameter
* @param fieldFill 填充策略枚舉
* @since 3.0.7
*/
default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject, FieldFill fieldFill) {
if (Objects.nonNull(fieldVal)) {
if (metaObject.hasSetter(fieldName) && metaObject.hasGetter(fieldName)
&& isFill(fieldName, fieldVal, metaObject, fieldFill)) {
metaObject.setValue(fieldName, fieldVal);
} else if (metaObject.hasGetter(Constants.ENTITY)) {
Object et = metaObject.getValue(Constants.ENTITY);
if (et != null) {
MetaObject etMeta = SystemMetaObject.forObject(et);
if (etMeta.hasSetter(fieldName) && isFill(fieldName, fieldVal, etMeta, fieldFill)) {
etMeta.setValue(fieldName, fieldVal);
}
}
}
}
return this;
}
/**
* get value from java bean by propertyName
* <p>如果包含前綴 et 使用該方法,否則可以直接 metaObject.setValue(fieldName, fieldVal);</p>
*
* @param fieldName java bean property name
* @param metaObject parameter wrapper
* @return 字段值
*/
default Object getFieldValByName(String fieldName, MetaObject metaObject) {
if (metaObject.hasGetter(fieldName)) {
return metaObject.getValue(fieldName);
} else if (metaObject.hasGetter(Constants.ENTITY_DOT + fieldName)) {
return metaObject.getValue(Constants.ENTITY_DOT + fieldName);
}
return null;
}
/**
* 填充判斷
* <li> 如果是主鍵,不填充 </li>
* <li> 根據字段名找不到字段,不填充 </li>
* <li> 字段類型與填充值類型不匹配,不填充 </li>
* <li> 字段類型需在TableField註解裏配置fill: @TableField(value="test_type", fill = FieldFill.INSERT), 沒有配置或者不匹配時不填充 </li>
* v_3.1.0以後的版本(不包括3.1.0),子類的值也可以自動填充,Timestamp的值也可以填入到java.util.Date類型裏面
*
* @param fieldName java bean property name
* @param fieldVal java bean property value
* @param metaObject meta object parameter
* @param fieldFill 填充策略枚舉
* @return 是否進行填充
* @since 3.0.7
*/
default boolean isFill(String fieldName, Object fieldVal, MetaObject metaObject, FieldFill fieldFill) {
TableInfo tableInfo = metaObject.hasGetter(Constants.MP_OPTLOCK_ET_ORIGINAL) ?
TableInfoHelper.getTableInfo(metaObject.getValue(Constants.MP_OPTLOCK_ET_ORIGINAL).getClass())
: TableInfoHelper.getTableInfo(metaObject.getOriginalObject().getClass());
if (Objects.nonNull(tableInfo)) {
Optional<TableFieldInfo> first = tableInfo.getFieldList().stream()
//v_3.1.1+ 設置子類的值也可以通過
.filter(e -> e.getProperty().equals(fieldName) && e.getPropertyType().isAssignableFrom(fieldVal.getClass()))
.findFirst();
if (first.isPresent()) {
FieldFill fill = first.get().getFieldFill();
return fill.equals(fieldFill) || FieldFill.INSERT_UPDATE.equals(fill);
}
}
return false;
}
/**
* 是否開啓了插入填充
*/
default boolean openInsertFill() {
return true;
}
/**
* 是否開啓了更新填充
*/
default boolean openUpdateFill() {
return true;
}
}
開發步驟
- 註解填充字段 @TableFile(fill = FieldFill.INSERT) 查看 FieldFill
- 自定義公共字段填充處理器
- MP 全局注入 自定義公共字段填充處理器
@Data
@TableName("tb_user")
public class User extends Model<User> {
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
@TableField(fill = FieldFill.UPDATE)
private String name;
private Integer age;
/* protected Serializable pkVal() {
return id;
}*/
}
/**
* 自定義填充處理器
*/
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//獲取到需要填充字段的值
Object fieldVale = getFieldValByName("name", metaObject);
if(fieldVale == null){
System.out.println("插入操作,滿足填充條件");
setFieldValByName("name","xxx", metaObject);
}
}
@Override
public void updateFill(MetaObject metaObject) {
/* 獲取到需要填充字段的值 */
Object fieldVale = getFieldValByName("name", metaObject);
if(fieldVale == null){
System.out.println("修改操作,滿足填充條件");
setFieldValByName("name","xxx", metaObject);
}
}
}
@Test
public void updateMeta(){
User user = new User();
user.setId(3);
user.setAge(23);
userMapper.update(user,new QueryWrapper<User>().eq("id",1));
}
十一、Oracle主鍵Sequence
MySQL: 支持主鍵自增。 IdType.Auto
Oracle: 序列(Sequence)
https://mp.baomidou.com/guide/sequence.html#spring-boot
- 實體類配置主鍵 Sequence @KeySequence(value=”序列名”,clazz=xxx.class 主鍵屬性類型)
- 全局 MP 主鍵生成策略爲 IdType.INPUT
- 全局 MP 中配置 Oracle 主鍵 Sequence
com.baomidou.mybatisplus.incrementer.OracleKeyGenerator - 可以將@keySequence 定義在父類中,可實現多個子類對應的多個表公用一個 Sequence