一、MyBatis回顧
1.1、回顧MyBatis
1.1.1、建庫建表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
複制
1.1.2、引入依賴
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
複制
1.1.3、application.properties
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql:///db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
複制
1.1.4、編寫Mapper接口
public interface UserMapper {
List<Employee> selectAll();
}
複制
1.1.5、編寫Mapper.xml
<select id="selectAll" resultMap="BaseResultMap">
select id,username,password
from user
select>
複制
1.1.6、MyBatis存在的缺點
我們可以發現傳統的MyBatis存在很緻命的問題,每個實體表對應一個實體類,對應一個Mapper.java接口,對應一個Mapper.xml配置檔案每個Mapper.java接口都有重複的crud方法,每一個Mapper.xml都有重複的crud的sql配置。如果想解決這個問題,唯一的辦法就是使用MyBatis-Plus。
二、了解Mybatis-Plus
MyBatis-Plus(簡稱 MP)是一個 MyBatis 的增強工具,在 MyBatis 的基礎上隻做增強不做改變,為簡化開發、提高效率而生。
2.1、代碼以及文檔
文檔位址:https://mybatis.plus/guide/
源碼位址:https://github.com/baomidou/mybatis-plus
2.2、特性
- 無侵入:隻做增強不做改變,引入它不會對現有工程産生影響,如絲般順滑。
- 損耗小:啟動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作。
- 強大的 CRUD 操作:内置通用 Mapper、通用 Service,僅僅通過少量配置即可實作單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求。
- 支援 Lambda 形式調用:通過 Lambda 表達式,友善的編寫各類查詢條件,無需再擔心字段寫錯。
- 支援多種資料庫:支援 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多種資料庫。
- 支援主鍵自動生成:支援多達 4 種主鍵政策(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問題。
- 支援 XML 熱加載:Mapper 對應的 XML 支援熱加載,對于簡單的 CRUD 操作,甚至可以無 XML 啟動。
-
支援 ActiveRecord 模式:支援 ActiveRecord 形式調用,實體類隻需繼承 Model 類即可進行強大的 CRUD 操
作。
- 支援自定義全局通用操作:支援全局通用方法注入( Write once, use anywhere )。
- 支援關鍵詞自動轉義:支援資料庫關鍵詞(order、key…)自動轉義,還可自定義關鍵詞。
-
内置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,
支援模闆引擎,更有超多自定義配置等您來使用。
- 内置分頁插件:基于 MyBatis 實體分頁,開發者無需關心具體操作,配置好插件之後,寫分頁等同于普通 List查詢。
- 内置性能分析插件:可輸出 Sql 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢。
- 内置全局攔截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規則,預防誤操作。
- 内置 Sql 注入剝離器:支援 Sql 注入剝離,有效預防 Sql 注入攻擊。
2.3、快速開始
2.3.1、導入依賴
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.1.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
dependency>
複制
2.3.2、log4j.properties
og4j.rootLogger=DEBUG,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n
複制
2.3.3、編寫實體類
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private String password;
private String type;
}
複制
2.3.4、編寫mapper
// 直接繼承Myabtis-Plus的BaseMapper即可,泛型表示實體類
public interface UserMapper extends BaseMapper<User> {
}
複制
2.3.5、編寫啟動類
@SpringBootApplication
// 設定mapper接口掃描包
@MapperScan("cn.linstudy.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
複制
2.3.6、測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
List<User> userList = userMapper.selectList(null);
for (User user : userList) {
System.out.println(user);
}
}
}
複制
2.4、架構
三、常用注解
3.1、@TableName
MyBatis-Plus中預設表名是跟實體類名一緻,當我們實體類的類名和表名不一緻的時候,MyBatis-Plus就會報錯,但是我們實際上又有這種需求的時候,我們就需要使用
@TableName
這個注解,來指定目前實體類映射哪張資料庫表。
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
private Integer id;
private String username;
private String password;
private String type;
}
複制
3.2、@TableId
我們在使用insert方法的時候會發現一個很奇怪的現象。他生成的ID格外長,這是因為他使用的算法是使用雪花算法生成的ID,我們想要的是自增的ID,是以我們需要設定主鍵增長的政策。
我們可以使用
@TableId
這個注解。他的作用是主鍵注解,标記目前屬性映射表的主鍵,其中type是屬性指定的主鍵類型,他有這幾個值:
- IdType.AUTO:資料庫ID自增。
- IdType.NONE:無狀态,該類型為未設定主鍵類型(注解裡等于跟随全局,全局裡約等于 INPUT)。
- IdType.INPUT:insert前自行set主鍵值。
- IdType.ASSIGN_ID:配置設定ID(主鍵類型為Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(預設實作類為DefaultIdentifierGenerator雪花算法)。
- 配置設定UUID,主鍵類型為String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(預設default方法)
3.3、@TableField
我們有些時候,資料庫的字段名和實體類的名字可能會不一樣,或者是說實體類中有的字段而資料庫中卻沒有,我們需要用
@TableField
這個注解。
@TableField
注解用于标記非主鍵字段,他的作用是指定目前屬性映射資料庫表哪一列, 預設是跟屬性名一緻。常用于解決以下兩個問題:
- 對象中的屬性名和字段名不一緻的問題(非駝峰)
- 對象中的屬性字段在表中不存在的問題
他還有另一種用法,就是指定某個字段不加入查詢。
四、通用CRUD
我們之前學過,使用MyBatis-Plus的時候,Mapper接口裡面的方法不需要我們再自己寫了,隻需要繼承BaseMapper接口即可擷取到各種各樣的單表操作。
4.1、插入操作
4.1.1、方法定義
MyBatis-Plus中對于insert的方法定義是:
/**
* 插入一條記錄
*
* @param entity 實體對象
*/
int insert(T entity);
複制
4.1.2、測試
package cn.linstudy.test
import cn.itcast.mp.mapper.UserMapper;
import cn.itcast.mp.pojo.User;
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 java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testInsert() {
User user = new User();
user.setAge(20);
user.setEmail("[email protected]");
user.setName("曹操");
user.setUserName("caocao");
user.setPassword("123456");
int result = this.userMapper.insert(user); //傳回的result是受影響的行數,并不是自增後的id
System.out.println("result = " + result);
System.out.println(user.getId()); //自增後的id會回填到對象中
}
}
複制
4.2、更新操作
4.2.1、updateById
4.2.1.1、方法定義
/**
* 根據 ID 修改
*
* @param entity 實體對象
*/
int updateById(@Param(Constants.ENTITY) T entity);
複制
4.2.1.2、測試
// 需求: 将id=1使用者名字修改為xiaolin
@Test
public void testUpdateById(){
Employee employee = new Employee();
employee.setId(1L);
employee.setName("xiaolin");
employeeMapper.updateById(employee);
}
// 注意: 拼接sql時,所有非null 字段都進行set 拼接
// UPDATE employee SET name=?, age=?, admin=? WHERE id=?
// 改進的方法是先查,再替換,最後更新
// 需求: 将id=1使用者名字修改為xiaolin
@Test
public void testUpdateById2(){
Employee employee = employeeMapper.selectById(1L);
employee.setName("xiaolin");
employeeMapper.updateById(employee);
}
複制
4.2.2、update
4.2.2.1、方法定義
/**
* 根據 whereEntity 條件,更新記錄
*
* @param entity 實體對象 (set 條件值,可以為 null)
* @param updateWrapper 實體對象封裝操作類(可以為 null,裡面的 entity 用于生成 where 語句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T>
updateWrapper);
複制
4.2.2.2、測試
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
// 方法一:使用QueryWrapper
@Test
public void testUpdate() {
User user = new User();
user.setAge(22); //更新的字段
//更新的條件
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("id", 6);
//執行更新操作
int result = this.userMapper.update(user, wrapper);
System.out.println("result = " + result);
}
//方法二: 通過UpdateWrapper進行更新
@Test
public void testUpdate(){
//更新的條件以及字段
UpdateWrapper<User> wrapper=new UpdateWrapper<>();
wrapper.eq("id",6).set("age",23);
//執行更新操作
int result=this.userMapper.update(null,wrapper);
System.out.println("result = "+result);
}
}
複制
4.2.2.3、使用建議
- 知道id,并且所有更新使用
updateById
- 部分字段更新,使用
update
4.3、删除操作
4.3.1、deleteById
4.3.1.1、方法定義
/**
* 根據 ID 删除
*
* @param id 主鍵ID
*/
int deleteById(Serializable id);
複制
4.3.1.2、測試
@Test
public void testDeleteById() {
//執行删除操作
int result = this.userMapper.deleteById(6L);
System.out.println("result = " + result);
}
複制
4.3.2、deleteByMap
4.3.2.1、方法定義
/**
* 根據 columnMap 條件,删除記錄
*
* @param columnMap 表字段 map 對象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
複制
4.3.2.2、測試
@Test
public void testDeleteByMap() {
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("age",20);
columnMap.put("name","張三");
//将columnMap中的元素設定為删除的條件,多個之間為and關系
int result = this.userMapper.deleteByMap(columnMap);
System.out.println("result = " + result);
}
複制
4.3.3、delete
4.3.3.1、方法定義
/**
* 根據 entity 條件,删除記錄
*
* @param wrapper 實體對象封裝操作類(可以為 null)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
複制
4.3.3.2、測試
@Test
public void testDeleteByMap() {
User user = new User();
user.setAge(20);
user.setName("張三");
//将實體對象進行包裝,包裝為操作條件
QueryWrapper<User> wrapper = new QueryWrapper<>(user);
int result = this.userMapper.delete(wrapper);
System.out.println("result = " + result);
}
複制
4.3.4、deleteBatchIds
4.3.4.1、方法定義
/**
* 删除(根據ID 批量删除)
*
* @param idList 主鍵ID清單(不能為 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable>
idList);
複制
4.3.4.2、測試
@Test
public void testDeleteByMap() {
//根據id集合批量删除
int result = this.userMapper.deleteBatchIds(Arrays.asList(1L,10L,20L));
System.out.println("result = " + result);
}
複制
4.4、查詢操作
MyBatis-Plus提供了多種查詢操作,包括根據id查詢、批量查詢、查詢單條資料、查詢清單、分頁查詢等操作。
4.4.1、selectById
4.4.1.1、方法定義
/**
* 根據 ID 查詢
*
* @param id 主鍵ID
*/
T selectById(Serializable id);
複制
4.1.1.2、測試
@Test
public void testSelectById() {
//根據id查詢資料
User user = this.userMapper.selectById(2L);
System.out.println("result = " + user);
}
複制
4.4.2、selectBatchIds
4.4.2.1、方法定義
/**
* 查詢(根據ID 批量查詢)
*
* @param idList 主鍵ID清單(不能為 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable>
idList);
複制
4.2.2.2、測試
@Test
public void testSelectBatchIds() {
//根據id集合批量查詢
List<User> users = this.userMapper.selectBatchIds(Arrays.asList(2L, 3L, 10L));
for (User user : users) {
System.out.println(user);
}
}
複制
4.4.3、selectOne
4.4.3.1、方法定義
/**
* 根據 entity 條件,查詢一條記錄
*
* @param queryWrapper 實體對象封裝操作類(可以為 null)
*/
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
複制
4.4.3.2、測試
@Test
public void testSelectOne() {
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.eq("name", "李四");
//根據條件查詢一條資料,如果結果超過一條會報錯
User user = this.userMapper.selectOne(wrapper);
System.out.println(user);
}
複制
4.4.4、selectCount
4.4.4.1、方法定義
/**
* 根據 Wrapper 條件,查詢總記錄數
*
* @param queryWrapper 實體對象封裝操作類(可以為 null)
*/
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
複制
4.4.4.2、測試
@Test
public void testSelectCount() {
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.gt("age", 23); //年齡大于23歲
Integer count = this.userMapper.selectCount(wrapper);
System.out.println("count = " + count);
}
複制
4.4.5、selectList
4.4.5.1、方法定義
/**
* 根據 entity 條件,查詢全部記錄
*
* @param queryWrapper 實體對象封裝操作類(可以為 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
複制
4.4.5.2、測試
@Test
public void testSelectList() {
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.gt("age", 23); //年齡大于23歲
//根據條件查詢資料
List<User> users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println("user = " + user);
}
}
複制
4.4.6、selectPage
4.4.6.1 方法定義
/**
* 根據 entity 條件,查詢全部記錄(并翻頁)
*
* @param page 分頁查詢條件(可以為 RowBounds.DEFAULT)
* @param queryWrapper 實體對象封裝操作類(可以為 null)
*/
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
複制
4.4.6.2、配置分頁插件
@Configuration
@MapperScan("cn.itcast.mp.mapper") //設定mapper接口的掃描包
public class MybatisPlusConfig {
/**
* 分頁插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
複制
4.4.6.3、測試
@Test
public void testSelectPage() {
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.gt("age", 20); //年齡大于20歲
Page<User> page = new Page<>(1,1);
//根據條件查詢資料
IPage<User> iPage = this.userMapper.selectPage(page, wrapper);
System.out.println("資料總條數:" + iPage.getTotal());
System.out.println("總頁數:" + iPage.getPages());
List<User> users = iPage.getRecords();
for (User user : users) {
System.out.println("user = " + user);
}
}
複制
4.4.7、SQL注入原理
MP在啟動後會将BaseMapper中的一系列的方法注冊到meppedStatements中,那麼究竟是如何注入的呢?流程又是怎麼樣的?
在MP中,ISqlInjector負責SQL的注入工作,它是一個接口,AbstractSqlInjector是它的實作類,實作關系如下:
在AbstractSqlInjector中,主要是由inspectInject()方法進行注入的:
@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();
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);
}
}
}
複制
以SelectById為例檢視:
public class SelectById extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?>
modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.LOGIC_SELECT_BY_ID;
SqlSource sqlSource = new RawSqlSource(configuration,
String.format(sqlMethod.getSql(),
sqlSelectColumns(tableInfo, false),
tableInfo.getTableName(), tableInfo.getKeyColumn(),
tableInfo.getKeyProperty(),
tableInfo.getLogicDeleteSql(true, false)), Object.class);
return this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(),
sqlSource, modelClass, tableInfo);
}
}
複制
可以看到,生成了SqlSource對象,再将SQL通過addSelectMappedStatement方法添加到meppedStatements中。
五、條件構造器
條件構造器可以簡單了解為條件拼接對象,用于生成 sql 的 where 條件。
5.1、繼承體系
在MyBatis-Plus中,Wrapper接口的實作類關系如下:
- AbstractWrapper: 用于查詢條件封裝,生成 sql 的 where 條件。
- QueryWrapper: Entity 對象封裝操作類,不是用lambda文法。
- UpdateWrapper: Update 條件封裝,用于Entity對象更新操作。
- AbstractLambdaWrapper: Lambda 文法使用 Wrapper統一處了解析 lambda 擷取 column。
- LambdaQueryWrapper:看名稱也能明白就是用于Lambda文法使用的查詢Wrapper。
- LambdaUpdateWrapper: Lambda 更新封裝Wrapper。
5.2、更新操作
5.2.1、普通更新
@Test
public void testUpdate(){
Employee employee = new Employee();
employee.setId(1L);
employee.setName("xiaolin");
employeeMapper.updateById(employee);
}
複制
這種更新會導緻資料的丢失,因為我隻想更新部分的字段。
5.2.2、UpdateWrapper更新
5.2.2.1、set
如果說我們需要更新部分的字段可以選擇UpdateWrapper進行更新。他的方法主要是有兩個:
- set(String column, Object val)
- set(boolean condition, String column, Object val)
// 需求:将id=1的員工name改為xiaolin
@Test
public void testUpdate2(){
UpdateWrapper<Employee> wrapper = new UpdateWrapper<>();
wrapper.eq("id", 1L);
// 相當于sql語句中的set name = xiaolin
wrapper.set("name", "xiaolin");
employeeMapper.update(null, wrapper);
}
複制
5.2.2.2、
MyBatis-Plus還提供了另一種方式來修改,那就是可以利用setSql直接寫入sql語句。
// 需求:将id=1的使用者name改為xiaolin
@Test
public void testUpdate3(){
UpdateWrapper<Employee> wrapper = new UpdateWrapper<>();
wrapper.eq("id", 1L);
wrapper.setSql("name='xiaolin'");
employeeMapper.update(null, wrapper);
}
複制
5.2.3、LambdaUpdateWrapper更新
我們還可以利用JDK8的新文法配合LambdaUpdateWrapper來進行操作。
// 需求:将id=1的使用者name改為xiaolin
@Test
public void testUpdate4(){
LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(Employee::getId, 1L);
wrapper.set(Employee::getName, "xiaolin");
employeeMapper.update(null, wrapper);
}
複制
5.2.4、開發建議
推薦使用LambdaUpdateWrapper更新。
5.3、查詢操作
5.3.1、普通查詢
// 需求:查詢name=xiaolin, age=18的使用者
@Test
public void testQuery1(){
Map<String, Object> map = new HashMap<>();
map.put("name", "xiaolin");
map.put("age", 18);
System.out.println(employeeMapper.selectByMap(map));
}
複制
5.3.2、QueryWrapper查詢
// 需求:查詢name=xiaolin, age=18的使用者
@Test
public void testQuery2(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.eq("name", "xiaolin").eq("age", 18);
System.out.println(employeeMapper.selectList(wrapper));
}
複制
5.3.3、LambdaQueryWrapper查詢
//需求:查詢name=xiaolin, age=18的使用者
@Test
public void testQuery3(){
LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Employee::getName, "xiaolin").eq(Employee::getAge, 18);
System.out.println(employeeMapper.selectList(wrapper));
}
複制
5.3.4、開發建議
推薦使用LambdaUpdateWrapper更新
5.4、進階查詢
5.4.1、列投影
所謂的烈投影就是指定查詢後傳回的列。我們利用的是select方法進行實作的。他有三個重載方法:
- select(String… sqlSelect) :參數是指定查詢後傳回的列。
- select(Predicate predicate):參數是Predicate 函數,滿足指定判定邏輯列才傳回。
- select(Class entityClass, Predicate predicate):參數1是通過實體屬性映射表中列,參數2是Predicate 函數, 滿足指定判定邏輯列才傳回。
// 需求:查詢所有員工, 傳回員工name, age列
@Test
public void testQuery4(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.select("name", "age");
employeeMapper.selectList(wrapper);
}
// 需求:查詢所有員工, 傳回員工以a字母開頭的列
@Test
public void testQuery4(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.select(Employee.class, tableFieldInfo->tableFieldInfo.getProperty().startsWith("a"));
employeeMapper.selectList(wrapper);
}
複制
5.4.2、排序
5.4.2.1、orderByAsc/orderByDesc
排序分為兩種,等價SQL: select …from table ORDER BY 字段, … ASC;
- orderByAsc: 正序排序。
- orderByDesc:倒序排序。
// 需求:查詢所有員工資訊按age正序排, 如果age一樣, 按id正序排
@Test
public void testQuery5(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.orderByAsc("age", "id");
employeeMapper.selectList(wrapper);
}
複制
5.4.2.2、orderBy
如果官方寫好的排序不适用于我們的話,我們可以使用定制排序-order by。等價SQL:select …from table ORDER BY 字段;
orderBy(boolean condition, boolean isAsc, R... columns)
:參數1:控制是否進行排序,參數2:控制是不是正序。
// 需求:查詢所有員工資訊按age正序排, 如果age一樣, 按id正序排
@Test
public void testQuery5(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
// orderBy(true, true, "id", "name")等價于order by id ASC,name ASC
//apper.orderByAsc("age", "id");
//等價于:
wrapper.orderBy(true, true, "age", "id");
employeeMapper.selectList(wrapper);
}
// 需求:查詢所有員工資訊按age正序排, 如果age一樣, 按id倒序排
@Test
public void testQuery7(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.orderByAsc("age");
wrapper.orderByDesc("id");
employeeMapper.selectList(wrapper);
}
複制
5.5、條件查詢
5.5.1、allEq(全等比對)
5.5.1.1、方法
allEq(Map<R, V> params) // params : key為資料庫字段名,value為字段值
allEq(Map<R, V> params, boolean null2IsNull) // 為true則在map的value為null時調用 isNull 方法,為false時則忽略value為null的
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
複制
null2IsNull
這個參數的意思是為true則在map的value為null時調用 isNull 方法,為false時則忽略value為null的。例如:
- allEq({id:1,name:“老王”,age:null})—>id = 1 and name = ‘老王’ and age is null
- allEq({id:1,name:“老王”,age:null}, false) —> id = 1 and name = ‘老王’
5.5.1.2、範例
// 需求:查詢name=xiaolin, age=18的員工資訊
@Test
public void testQuery8(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("name", "xiaolin");
map.put("age", 18);
wrapper.allEq(map);
employeeMapper.selectList(wrapper);
}
@Test
public void testQuery8(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("name", "xiaolin");
map.put("age", 18);
map.put("dept_id", null);
wrapper.allEq(map, true);
employeeMapper.selectList(wrapper);
}
複制
5.5.2、allEq(全等比對帶條件過濾的)
5.5.2.1、方法
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
複制
filter
: 過濾函數,是否允許字段傳入比對條件中 params 與 null2IsNull同上,例如:
- allEq((k,v) -> k.indexOf(“a”) >= 0, {id:1,name:“老王”,age:null})—>name = ‘老王’ and age is null
- allEq((k,v) -> k.indexOf(“a”) >= 0, {id:1,name:“老王”,age:null}, false)—>name = ‘老王’
5.5.2.2、範例
// 需求:查詢滿足條件員工資訊, 注意傳入的map條件中, 包含a的列才參與條件查詢
@Test
public void testQuery9(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("name", "xiaolin");
map.put("age", 18);
wrapper.allEq((k, v)-> k.contains("m"), map);
employeeMapper.selectList(wrapper);
}
複制
5.5.3、eq
我們可以使用eq來判斷單個參數判斷是否相等。eq(“name”, “老王”)等價于name = ‘老王’。
5.5.3.1、方法
eq(R column, Object val)
eq(boolean condition, R column, Object val) // 相較于上一個方法,多了一個條件,目前面的條件成立時,才拼接後面語句,常用于判斷當某個值不為空的時候進行拼接。
複制
5.5.3.2、範例
// 需求:查詢name=xiaolin員工資訊
@Test
public void testQuery10(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.eq("name", "xiaolin");
employeeMapper.selectList(wrapper);
}
複制
5.5.4、ne
我們可以使用ne來判斷某個參數是否不相等。 ne(“name”, “老王”)等價于name != ‘老王’。
// 需求:查詢name !=xiaolin員工資訊
@Test
public void testQuery11(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.ne("name", "xiaolin");
employeeMapper.selectList(wrapper);
}
複制
5.5.5、gt
gt表示大于。gt(“age”, 18)等價于age > 18
5.5.5.1、方法
gt(R column, Object val)
gt(boolean condition, R column, Object val)
複制
5.5.5.2、範例
// 需求:查詢age > 12 員工資訊
@Test
public void testQuery12(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.gt("age", "12");
employeeMapper.selectList(wrapper);
}
複制
5.5.6、ge
ge表示大于等于。ge(“age”, 18)等價于age > =18
5.5.6.1、方法
ge(R column, Object val)
ge(boolean condition, R column, Object val)
複制
5.5.6.2、範例
// 需求:查詢age >= 12 員工資訊
@Test
public void testQuery12(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.ge("age", "12");
employeeMapper.selectList(wrapper);
}
複制
5.5.7、lt
lt表示小于1。lt(“age”, 18)等價于age < 18
5.5.7.1、方法
lt(R column, Object val)
lt(boolean condition, R column, Object val
複制
5.5.7.2、範例
// 需求:查詢age < 12 員工資訊
@Test
public void testQuery12(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.lt("age", "12");
employeeMapper.selectList(wrapper);
}
複制
5.5.8、lt
lt表示小于1。lt(“age”, 18)等價于age < 18
5.5.8、方法
lt(R column, Object val)
lt(boolean condition, R column, Object val
複制
5.5.7.2、範例
// 需求:查詢age < 12 員工資訊
@Test
public void testQuery12(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.lt("age", "12");
employeeMapper.selectList(wrapper);
}
複制
5.5.9、le
le表示小于等于。le(“age”, 18)等價于age <= 18。
le(R column, Object val)
le(boolean condition, R column, Object val)
複制
5.5.10、between、notBetween
我們使用between/notBetween來表示介于/不介于兩者之間。
between(“age”, 18, 30)等價于age between 18 and 30。
notBetween(“age”, 18, 30)等價于age not between 18 and 30
5.5.10.1、方法
// between : BETWEEN 值1 AND 值2
between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
// notBetween : NOT BETWEEN 值1 AND 值2
notBetween(R column, Object val1, Object val2)
notBetween(boolean condition, R column, Object val1, Object val2)
複制
5.5.10.2、範例
// 需求:查詢年齡介于18~30歲的員工資訊
@Test
public void testQuery13(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.between("age", 18, 30);
employeeMapper.selectList(wrapper);
}
// 需求:查詢年齡小于18或者大于30歲的員工資訊【用between實作】
@Test
public void testQuery13(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.notBetween("age", 18, 30);
employeeMapper.selectList(wrapper);
}
複制
5.5.11、isNull、isNotNull
我們可以使用isNull/isNotNull來表示為空/不為空。
isNull(“name”)等價于name is null。
isNotNull(“name”)等價于name is not null。
5.5.11.1、方法
// isNull : 字段 IS NULL
isNull(R column)
isNull(boolean condition, R column)
// isNotNull : 字段 IS NOT NULL
isNotNull(R column)
isNotNull(boolean condition, R column)
複制
5.5.11.2、範例
// 需求: 查詢dept_id 為null 員工資訊
@Test
public void testQuery16(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.isNull("dept_id");
employeeMapper.selectList(wrapper);
}
// 需求: 查詢dept_id 為不為null 員工資訊
@Test
public void testQuery16(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.isNotNull("dept_id");
employeeMapper.selectList(wrapper);
}
複制
5.5.12、in、notIn
我們可以使用in/notIn來表示值在/不在這裡面。
in(“age”,{1,2,3})—>age in (1,2,3)
notIn(“age”,{1,2,3})—>age not in (1,2,3)
5.5.12.1、方法
// in : 字段 IN (value1, value2, ...)
in(R column, Collection<?> value)
in(boolean condition, R column, Collection<?> value)
// notIn : 字段 NOT IN (value1, value2, ...)
notIn(R column, Object... values)
notIn(boolean condition, R column, Object... values)
複制
5.5.12.2、範例
// 需求: 查詢id為1, 2 的員工資訊
@Test
public void testQuery18(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.in("id", 1L, 2L);
employeeMapper.selectList(wrapper);
}
// 需求: 查詢id不為1,2 的員工資訊
@Test
public void testQuery19(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.notIn("id", 1L, 2L);
employeeMapper.selectList(wrapper);
}
複制
5.5.13、inSql、notInSql
與上一個不同的是,他的格式是:字段 IN ( sql語句 ) / 字段 NOT IN ( sql語句 ),接的是SQL語句片段。
5.5.13.1、方法
inSql(“age”, “1,2,3,4,5,6”)等價于age in (1,2,3,4,5,6)
notInSql(“id”, “select id from table where id < 3”)—>id not in (select id from table where id < 3)
// inSql : 字段 IN ( sql語句 )
inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)
// notInSql : 字段 NOT IN ( sql語句 )
notInSql(R column, String inValue)
notInSql(boolean condition, R column, String inValue)
複制
5.5.13.2、範例
// 需求: 查詢id為1, 2 的員工資訊
@Test
public void testQuery20(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.inSql("id", "1,2");
employeeMapper.selectList(wrapper);
}
// 需求: 查詢id不為1,2 的員工資訊
@Test
public void testQuery21(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.notInSql("id", "1,2");
employeeMapper.selectList(wrapper);
}
複制
5.6、模糊查詢
5.6.1、like、notLike
5.6.1.1、方法
// like: LIKE '%值%':like("name", "王")等價于name like '%王%'
like: LIKE '%值%'
like(R column, Object val)
like(boolean condition, R column, Object val)
// notLike("name", "王")--->name not like '%王%'
notLike : NOT LIKE '%值%'
notLike(R column, Object val)
notLike(boolean condition, R column, Object val)
複制
5.6.1.2、範例
// 需求: 查詢name中含有lin字樣的員工
@Test
public void testQuery14(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.like("name", "lin");
employeeMapper.selectList(wrapper);
}
// 需求: 查詢name中不含有lin字樣的員工
@Test
public void testQuery14(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.notLike("name", "lin");
employeeMapper.selectList(wrapper);
}
複制
5.6.2、likeLeft、likeRight
5.6.2.1、方法
// likeLeft("name", "王")--->name like '%王'
likeLeft : LIKE '%值'
likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)
// likeRight("name", "王")--->name like '王%'
likeRight : LIKE '值%'
likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)
複制
5.6.2.2、範例
// 需求: 查詢name以lin結尾的員工資訊
@Test
public void testQuery15(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.likeLeft("name", "lin");
employeeMapper.selectList(wrapper);
}
// 需求: 查詢姓王的員工資訊
@Test
public void testQuery16(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.likeRight("name", "王");
employeeMapper.selectList(wrapper);
}
複制
5.7、邏輯運算符
5.7.1、or
5.7.1.1、方法
// eq("id",1).or().eq("name","老王")--->id = 1 or name = '老王'
or : 拼接 OR
or()
or(boolean condition)
// or還可以嵌套使用
// or(i -> i.eq("name", "李白").ne("status", "活着"))--->or (name = '李白' and status <> '活着')
or(Consumer<Param> consumer)
or(boolean condition, Consumer<Param> consumer)
複制
主動調用or表示緊接着下一個方法不是用and連接配接!(不調用or則預設為使用and連接配接)
5.7.1.2、範例
// 需求: 查詢age = 18 或者 name=xiaolin 或者 id =1 的使用者
@Test
public void testQuery24(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.eq("age", 18)
.or()
.eq("name", "xiaolin")
.or()
.eq("id", 1L);
employeeMapper.selectList(wrapper);
}
// 需求:查詢name含有lin字樣的,或者 年齡在18到30之間的使用者
@Test
public void testQuery25(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.like("name", "lin")
.or(wr -> wr.le("age", 30).ge("age", 18));
employeeMapper.selectList(wrapper);
}
複制
5.7.2、and
5.7.2.1、方法
// 嵌套and:
and(Consumer<Param> consumer)
and(boolean condition, Consumer<Param> consumer)
複制
5.7.2.2、範例
// 需求:查詢年齡介于18~30歲的員工資訊
@Test
public void testQuery26(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.le("age", 30).ge("age", 18);
employeeMapper.selectList(wrapper);
}
// 需求:查詢name含有lin字樣的并且 年齡在小于18或者大于30的使用者
@Test
public void testQuery27(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.like("name", "lin")
.and(wr -> wr.le("age", 30)
.or()
.ge("age", 18));
employeeMapper.selectList(wrapper);
}
複制
5.8、分組查詢
5.8.1、groupBy
5.8.1.1、方法
// groupBy("id", "name")--->group by id,name
groupBy : 分組:GROUP BY 字段, ...
groupBy(R... columns)
groupBy(boolean condition, R... columns)
複制
5.8.1.2、範例
// 需求: 以部門id進行分組查詢,查每個部門員工個數
@Test
public void testQuery22(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.groupBy("dept_id");
wrapper.select("dept_id", "count(id) count");
employeeMapper.selectMaps(wrapper);
}
複制
5.8.2、having
5.8.2.1、方法
// having("sum(age) > 10")--->having sum(age) > 10
// having("sum(age) > {0}", 11)--->having sum(age) > 11
having : HAVING ( sql語句 )
having(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)
複制
5.8.2.2、範例
// 需求: 以部門id進行分組查詢,查每個部門員工個數, 将大于3人的部門過濾出來
@Test
public void testQuery23(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.groupBy("dept_id")
.select("dept_id", "count(id) count")
//.having("count > {0}", 3)
.having("count >3");
employeeMapper.selectMaps(wrapper);
}
複制
六、通用Service接口
6.1、傳統方式
在以前的業務層中,我們需要寫接口和實作類,其中有不少的接口都是重複的CRUD,沒有任何技術含量。
6.1.1、Service接口
public interface EmployeeService {
void save(Employee employee);
void update(Employee employee);
void delete(Long id);
Employee get(Long id);
List<Employee> list();
}
複制
6.1.2、ServiceImpl
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeMapper mapper;
@Override
public void save(Employee employee) {
mapper.insert(employee);
}
@Override
public void update(Employee employee) {
mapper.updateById(employee); //必須全量更新
}
@Override
public void delete(Long id) {
mapper.deleteById(id);
}
@Override
public Employee get(Long id) {
return mapper.selectById(id);
}
@Override
public List<Employee> list() {
return mapper.selectList(null);
}
}
複制
6.2、MyBatis-Plus的通用Service接口
既然需要重複寫那麼多沒有技術含量的代碼,那麼肯定MyBatis-Plus會幫我們做好,我們隻需要簡單兩部即可使用MyBatis-Plus給我們寫好的CRUD方法,他會自動調用mapper接口方法。
- 自定義服務接口繼承IService接口,其中泛型是實體類對象
public interface IEmployeeService extends IService<Employee> {
}
複制
- 服務接口實作類內建IService接口實作類ServiceImpl同時實作自定義接口,泛型一是實體類的mapper接口,泛型二是實體類。
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService {
}
複制
6.2.1、常用方法
- getBaseMapper():擷取引用的XxxxMapper對象。
- getOne(wrapper):指定條件查詢單個, 結果資料超過1個報錯。
- list(wrapper):指定條件查詢多個。
6.2.2、分頁
分頁所用的方法是:
page(page, wrapper)
,他也可以配合進階查詢一起。
6.2.2.1、配置分頁插件
我們需要在配置類中配置分頁插件。
//分頁
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setOverflow(true); //合理化
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
複制
6.2.2.2、編寫分頁代碼
MyBatis-Plus的分頁對象是IPage,分頁資訊封裝對象,裡面有各種分頁相關資訊等價于之前的PageInfo。
//需求:查詢第2頁員工資訊, 每頁顯示3條, 按id排序
@Test
public void testPage(){
EmployeeQuery qo = new EmployeeQuery();
qo.setPageSize(3);
qo.setCurrentPage(2);
IPage<Employee> page = employeeService.query(qo);
System.out.println("目前頁:" + page.getCurrent());
System.out.println("總頁數:" + page.getPages());
System.out.println("每頁顯示條數:" + page.getSize());
System.out.println("總記錄數:" + page.getTotal());
System.out.println("目前頁顯示記錄:" + page.getRecords());
}
複制
七、ActiveRecord
7.1、什麼是ActiveRecord
ActiveRecord也屬于ORM(對象關系映射)層,由Rails最早提出,遵循标準的ORM模型:表映射到記錄,記錄映射到對象,字段映射到對象屬性。配合遵循的命名和配置慣例,能夠很大程度的快速實作模型的操作,而且簡潔易懂。
ActiveRecord的主要思想是:
-
每一個資料庫表對應建立一個類,類的每一個對象執行個體對應于資料庫中表的一行記錄;通常表的每個字段
在類中都有相應的Field。
- ActiveRecord同時負責把自己持久化,在ActiveRecord中封裝了對資料庫的通路,即CURD。
- ActiveRecord是一種領域模型(Domain Model),封裝了部分業務邏輯;
ActiveRecord(簡稱AR)一直廣受動态語言( PHP 、 Ruby 等)的喜愛,而 Java 作為準靜态語言,對于ActiveRecord 往往隻能感歎其優雅,是以我們也在 AR 道路上進行了一定的探索,喜歡大家能夠喜歡。
7.2、開啟AR之旅
在MP中,開啟AR非常簡單,隻需要将實體對象繼承Model即可。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee extends Model<Employee> {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String name;
private String password;
private String email;
private Integer age;
private Boolean admin;
private Long deptId;
private Boolean status;
}
複制
7.2.1、查詢所有
@RunWith(SpringRunner.class)
@SpringBootTest
public class Test1 {
@Autowired
EmployeeMapper employeeMapper;
/**
* 用于測試查詢所有
*/
@Test
public void test(){
Employee employee = new Employee();
List<Employee> employees = employee.selectAll();
for (Employee employee1 : employees) {
System.out.println(employee1);
}
}
}
複制
7.2.2、根據id查詢
@RunWith(SpringRunner.class)
@SpringBootTest
public class Test2 {
/**
* 用于測試根據id查詢
*/
@Test
public void test2(){
Employee employee = new Employee();
employee.setId(1L);
System.out.println(employee.selectById());
}
}
複制
7.2.3、根據條件查詢
/**
* 用于測試根據條件查詢
*/
@Test
public void test4(){
Employee employee = new Employee();
QueryWrapper<Employee> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.le("password","123");
List<Employee> employees = employee.selectList(userQueryWrapper);
for (Employee employee1 : employees) {
System.out.println(employee1);
}
}
複制
7.2.4、新增資料
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestAR {
/**
* 用于測試新增資料
*/
@Test
public void test3(){
Employee employee = new Employee();
employee.setId(11L);
employee.setPassword("123");
employee.insert();
}
}
複制
7.2.5、更新資料
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestAR {
/**
* 用于測試更新
*/
@Test
public void test5(){
Employee employee = new Employee();
employee.setId(1L);
employee.setPassword("123456789");
employee.updateById();
}
}
複制
7.2.6、删除資料
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestAR {
/**
* 用于測試删除
*/
@Test
public void test6(){
Employee employee = new Employee();
employee.setId(1L);
employee.deleteById();
}
}
複制
八、插件機制
8.1、插件機制簡介
MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。預設情況下,MyBatis 允許使用插件來攔截的方法調用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
我們看到了可以攔截Executor接口的部分方法,比如update,query,commit,rollback等方法,還有其他接口的一些方法等。總體概括為:
- 攔截執行器的方法。
- 攔截參數的處理。
- 攔截結果集的處理。
- 攔截Sql文法建構的處理。
8.2、執行分析插件
在MP中提供了對SQL執行的分析的插件,可用作阻斷全表更新、删除的操作,注意:該插件僅适用于開發環境,不适用于生産環境。我們首先要在啟動類上配置:
@Bean
public SqlExplainInterceptor sqlExplainInterceptor(){
SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
// 攻擊 SQL 阻斷解析器、加入解析鍊
sqlParserList.add(new BlockAttackSqlParser());
sqlExplainInterceptor.setSqlParserList(sqlParserList);
return sqlExplainInterceptor;
}
複制
測試類:
@Test
public void testUpdate(){
Employee employee = new Employee();
employee.setPassword("123456");
int result = this.employeeMapper.update(employee, null);
System.out.println("result = " + result);
}
複制
執行後會發現控制台報錯了,當執行全表更新時,會抛出異常,這樣有效防止了一些誤操作。
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-JpBJRX8S-1620720482907)(MyBatisPlus.assets/image-20210509210703826.png)]
8.3、樂觀鎖插件
8.3.1、适用場景
當要更新一條記錄的時候,希望這條記錄沒有被别人更新。
樂觀鎖的實作方式:
- 取出記錄時,擷取目前version。
- 更新時,帶上這個version。
- 執行更新時, set version = newVersion where version = oldVersion。
- 如果version不對,就更新失敗。
8.3.2、插件配置
我們需要在spring.xml中進行配置。
<bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/>
複制
然後在singboot的啟動類中配置。
Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
複制
8.3.3、注解實體字段
為表添加version字段,并賦初始值為1
ALTER TABLE `employee`
ADD COLUMN `version` int(10) NULL AFTER `email`;
UPDATE `tb_user` SET `version`='1';
複制
為實體類添加version字段,并且添加@Version注解
@Version
private Integer version;
複制
測試
@Test
public void testUpdate(){
Employee employee = new Employee();
user.setPassword("456789");
user.setId(2L);
user.setVersion(1); //設定version為1
int result = this.userMapper.updateById(user);
System.out.println("result = " + result);
}
複制
8.3.4、說明
- 支援的資料類型隻有:int,Integer,long,Long,Date,Timestamp,LocalDateTime。
- 整數類型下 newVersion = oldVersion + 1。
- newVersion 會回寫到 entity 中僅支援 updateById(id) 與 update(entity, wrapper) 方法。
- 在 update(entity, wrapper) 方法下, wrapper 不能複用。