天天看點

工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制

一、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 的基礎上隻做增強不做改變,為簡化開發、提高效率而生。

工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制

2.1、代碼以及文檔

文檔位址:https://mybatis.plus/guide/

源碼位址:https://github.com/baomidou/mybatis-plus

2.2、特性

  1. 無侵入:隻做增強不做改變,引入它不會對現有工程産生影響,如絲般順滑。
  2. 損耗小:啟動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作。
  3. 強大的 CRUD 操作:内置通用 Mapper、通用 Service,僅僅通過少量配置即可實作單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求。
  4. 支援 Lambda 形式調用:通過 Lambda 表達式,友善的編寫各類查詢條件,無需再擔心字段寫錯。
  5. 支援多種資料庫:支援 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多種資料庫。
  6. 支援主鍵自動生成:支援多達 4 種主鍵政策(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問題。
  7. 支援 XML 熱加載:Mapper 對應的 XML 支援熱加載,對于簡單的 CRUD 操作,甚至可以無 XML 啟動。
  8. 支援 ActiveRecord 模式:支援 ActiveRecord 形式調用,實體類隻需繼承 Model 類即可進行強大的 CRUD 操

    作。

  9. 支援自定義全局通用操作:支援全局通用方法注入( Write once, use anywhere )。
  10. 支援關鍵詞自動轉義:支援資料庫關鍵詞(order、key…)自動轉義,還可自定義關鍵詞。
  11. 内置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,

    支援模闆引擎,更有超多自定義配置等您來使用。

  12. 内置分頁插件:基于 MyBatis 實體分頁,開發者無需關心具體操作,配置好插件之後,寫分頁等同于普通 List查詢。
  13. 内置性能分析插件:可輸出 Sql 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢。
  14. 内置全局攔截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規則,預防誤操作。
  15. 内置 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、架構

工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制

三、常用注解

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,是以我們需要設定主鍵增長的政策。

工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制

    我們可以使用

@TableId

這個注解。他的作用是主鍵注解,标記目前屬性映射表的主鍵,其中type是屬性指定的主鍵類型,他有這幾個值:

  1. IdType.AUTO:資料庫ID自增。
  2. IdType.NONE:無狀态,該類型為未設定主鍵類型(注解裡等于跟随全局,全局裡約等于 INPUT)。
  3. IdType.INPUT:insert前自行set主鍵值。
  4. IdType.ASSIGN_ID:配置設定ID(主鍵類型為Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(預設實作類為DefaultIdentifierGenerator雪花算法)。
  5. 配置設定UUID,主鍵類型為String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(預設default方法)

3.3、@TableField

    我們有些時候,資料庫的字段名和實體類的名字可能會不一樣,或者是說實體類中有的字段而資料庫中卻沒有,我們需要用

@TableField

這個注解。

@TableField

注解用于标記非主鍵字段,他的作用是指定目前屬性映射資料庫表哪一列, 預設是跟屬性名一緻。常用于解決以下兩個問題:

  1. 對象中的屬性名和字段名不一緻的問題(非駝峰)
  2. 對象中的屬性字段在表中不存在的問題
工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制

    他還有另一種用法,就是指定某個字段不加入查詢。

工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制
工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制

四、通用CRUD

    我們之前學過,使用MyBatis-Plus的時候,Mapper接口裡面的方法不需要我們再自己寫了,隻需要繼承BaseMapper接口即可擷取到各種各樣的單表操作。

工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制

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、使用建議

  1. 知道id,并且所有更新使用

    updateById

  2. 部分字段更新,使用

    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是它的實作類,實作關系如下:

工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制

    在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);
		}
	}
}	           

複制

工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制

    以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中。

工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制

五、條件構造器

    條件構造器可以簡單了解為條件拼接對象,用于生成 sql 的 where 條件。

5.1、繼承體系

    在MyBatis-Plus中,Wrapper接口的實作類關系如下:

工作量減半的開發神器,MyBatisPlus入門和部分源碼講解一、MyBatis回顧二、了解Mybatis-Plus三、常用注解四、通用CRUD五、條件構造器六、通用Service接口七、ActiveRecord八、插件機制
  1. AbstractWrapper: 用于查詢條件封裝,生成 sql 的 where 條件。
  2. QueryWrapper: Entity 對象封裝操作類,不是用lambda文法。
  3. UpdateWrapper: Update 條件封裝,用于Entity對象更新操作。
  4. AbstractLambdaWrapper: Lambda 文法使用 Wrapper統一處了解析 lambda 擷取 column。
  5. LambdaQueryWrapper:看名稱也能明白就是用于Lambda文法使用的查詢Wrapper。
  6. 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進行更新。他的方法主要是有兩個:

  1. set(String column, Object val)
  2. 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方法進行實作的。他有三個重載方法:

  1. select(String… sqlSelect) :參數是指定查詢後傳回的列。
  2. select(Predicate predicate):參數是Predicate 函數,滿足指定判定邏輯列才傳回。
  3. 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;

  1. orderByAsc: 正序排序。
  2. 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的。例如:

  1. allEq({id:1,name:“老王”,age:null})—>id = 1 and name = ‘老王’ and age is null
  2. 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同上,例如:

  1. allEq((k,v) -> k.indexOf(“a”) >= 0, {id:1,name:“老王”,age:null})—>name = ‘老王’ and age is null
  2. 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接口方法。

  1. 自定義服務接口繼承IService接口,其中泛型是實體類對象
public interface IEmployeeService extends IService<Employee> {
}           

複制

  1. 服務接口實作類內建IService接口實作類ServiceImpl同時實作自定義接口,泛型一是實體類的mapper接口,泛型二是實體類。
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService {
}           

複制

6.2.1、常用方法

  1. getBaseMapper():擷取引用的XxxxMapper對象。
  2. getOne(wrapper):指定條件查詢單個, 結果資料超過1個報錯。
  3. 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的主要思想是:

  1. 每一個資料庫表對應建立一個類,類的每一個對象執行個體對應于資料庫中表的一行記錄;通常表的每個字段

    在類中都有相應的Field。

  2. ActiveRecord同時負責把自己持久化,在ActiveRecord中封裝了對資料庫的通路,即CURD。
  3. 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 允許使用插件來攔截的方法調用包括:

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2. ParameterHandler (getParameterObject, setParameters)
  3. ResultSetHandler (handleResultSets, handleOutputParameters)
  4. StatementHandler (prepare, parameterize, batch, update, query)

    我們看到了可以攔截Executor接口的部分方法,比如update,query,commit,rollback等方法,還有其他接口的一些方法等。總體概括為:

  1. 攔截執行器的方法。
  2. 攔截參數的處理。
  3. 攔截結果集的處理。
  4. 攔截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、适用場景

    當要更新一條記錄的時候,希望這條記錄沒有被别人更新。

    樂觀鎖的實作方式:

  1. 取出記錄時,擷取目前version。
  2. 更新時,帶上這個version。
  3. 執行更新時, set version = newVersion where version = oldVersion。
  4. 如果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、說明

  1. 支援的資料類型隻有:int,Integer,long,Long,Date,Timestamp,LocalDateTime。
  2. 整數類型下 newVersion = oldVersion + 1。
  3. newVersion 會回寫到 entity 中僅支援 updateById(id) 與 update(entity, wrapper) 方法。
  4. 在 update(entity, wrapper) 方法下, wrapper 不能複用。