MyBatis-Plus——實踐篇


MyBatis-Plus——實踐篇

MyBatis-Plus (簡稱 MP)是一個 MyBatis的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。進行數據庫操作常用的第三方組件。

目前主流的持久層框架:

JPA (springboot用得多)、 tk-mapper(通用框架用的多)、MyBatisPlus

一句話概括:MyBatis 本來就是簡化 JDBC 操作的,而MyBatis-Plus用於簡化 MyBatis 操作。

特性

  • 無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
  • 損耗小:啟動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作。BaseMapper<對象泛型>進行操作。
  • 強大的 CRUD 操作:內置通用 Mapper、通用 Service,僅僅通過少量配置即可實現單表大部分 CRUD 操作(簡單的CRUD操作不需要再編寫xml語句),更有強大的條件構造器,滿足各類使用需求
  • 支持 Lambda 形式調用:通過 Lambda 表達式,方便的編寫各類查詢條件,無需再擔心字段寫錯
  • 支持主鍵自動生成:支持多達 4 種主鍵策略(內含分布式唯一 ID 生成器 - Sequence,雪花算法實現),可自由配置,完美解決主鍵問題
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式調用,實體類只需繼承 Model 類即可進行強大的 CRUD 操作
  • 支持自定義全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 內置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,支持模板引擎,更有超多自定義配置等您來使用
  • 內置分頁插件基於 MyBatis 物理分頁,開發者無需關心具體操作,配置好插件之后,寫分頁等同於普通 List 查詢
  • 分頁插件支持多種數據庫:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多種數據庫
  • 內置性能分析插件:可輸出 Sql 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢
  • 內置全局攔截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規則,預防誤操作

支持數據庫

任何能使用 mybatis 進行 crud, 並且支持標准 sql 的數據庫

框架結構

image.png

快速入門

使用第三方組件一般通用步驟:前三步是基礎入門,最后一步是使用組件的擴展技術。
1、導入對應的依賴
2、研究依賴如何配置
3、代碼如何編寫
4、提高擴展技術能力

項目准備:

1、數據庫創建:
image.png

2、表和數據:

DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主鍵ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年齡',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',
version int(5) NULL DEFAULT NULL COMMENT '版本號',
gmt_create datetime NULL DEFAULT NULL COMMENT '創建時間',
gmt_modified datetime NULL DEFAULT NULL COMMENT '修改時間',
delete_flag int(1) NULL DEFAULT NULL COMMENT '是否刪除 0:否,1:是',
PRIMARY KEY (id)
);


INSERT INTO user (id, name, age, email,version,gmt_create,gmt_modified,delete_flag) VALUES
(1, 'Jone', 18, 'test1@baomidou.com',1,now(),null,0),
(2, 'Jack', 20, 'test2@baomidou.com',1,now(),null,0),
(3, 'Tom', 28, 'test3@baomidou.com',1,now(),null,0),
(4, 'Sandy', 21, 'test4@baomidou.com',1,now(),null,0),
(5, 'Billie', 24, 'test5@baomidou.com',1,now(),null,0);

3、idea插件:lombok,簡化bean對象的getter/setter、構造器等的編寫。

4、創建springboot項目

5、導入依賴

	<dependencies>
<!--		mysql數據庫驅動-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
<!--		lombok依賴-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
<!--		mybatis-plus依賴-->
	<!--	使用 mybatis-plus 可以節省我們大量的代碼,因為!版本的差異,盡量不要同時導入 mybatis 和 mybatisplus-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.0.5</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

6、連接mysql數據庫

# mysql 5 驅動不同 com.mysql.jdbc.Driver
#useSSL=true 表示是否使用安全連接,mac如果報錯就改為false
#useUnicode=true&characterEncoding=utf-8 表示是否使用字符集編碼並給定字符集編碼類型utf-8
#serverTimezone=GMT%2B8 時區的配置,mysql8使用
spring:
    datasource:
        username: root
        password: 123456
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
# mysql 8 驅動不同com.mysql.cj.jdbc.Driver(雖然驅動不同,但是這個驅動是向上兼容mysql5的)、需要增加時區的配置,serverTimezone=GMT%2B8
#spring:
#    datasource:
#        username: root
#        password: 123456
#        driver-class-name: com.mysql.cj.jdbc.Driver
#        url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8

7、使用mybatis-plus進行單元測試

示例代碼:

pojo:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private Integer version;
    private LocalDateTime gmtCreate;
    private LocalDateTime gmtModified;
    private Integer deleteFlag;
}

dao或者mapper接口:

//可以使用@Mapper或@Repository代表持久層,注入spring容器
//@Mapper
@Repository
public interface UserDao extends BaseMapper<User> {
// 在對應的Mapper上面繼承基本的類 BaseMapper,所有的CRUD操作都已經編寫完成了,直接使用BaseMapper上定義的方法
}

啟動類上去掃描我們的mapper包下的所有接口:@MapperScan("com.kuang.mapper")

//啟動類上去掃描我們的dao包下的所有接口
@MapperScan("com.wayne.dao")
@SpringBootApplication
public class MybatisPlusApplication {
	public static void main(String[] args) {
		SpringApplication.run(MybatisPlusApplication.class, args);
	}
}

測試類:

@SpringBootTest
class MybatisPlusApplicationTests {
	// 繼承了BaseMapper,所有的方法都來自己父類
// 我們也可以編寫自己的擴展方法!
	@Autowired
	private UserDao userDao;

	@Test
	public void testQuery(){
		//參數是一個 Wrapper,條件構造器,這里我們先不用 null,默認查詢全部用戶
		List<User> users = userDao.selectList(null);
		users.forEach(System.out::println);
	}
}

結果:

User(id=1, name=Jone, age=18, email=test1@baomidou.com, version=1, gmtCreate=2020-12-15T23:39:47, gmtModified=null, deleteFlag=0)
User(id=2, name=Jack, age=20, email=test2@baomidou.com, version=1, gmtCreate=2020-12-15T23:39:47, gmtModified=null, deleteFlag=0)
User(id=3, name=Tom, age=28, email=test3@baomidou.com, version=1, gmtCreate=2020-12-15T23:39:47, gmtModified=null, deleteFlag=0)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com, version=1, gmtCreate=2020-12-15T23:39:47, gmtModified=null, deleteFlag=0)
User(id=5, name=Billie, age=24, email=test5@baomidou.com, version=1, gmtCreate=2020-12-15T23:39:47, gmtModified=null, deleteFlag=0)

報錯:The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone.

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userDao' defined in file [F:\waynecode\mybatis_plus\target\classes\com\wayne\dao\UserDao.class]: Unsatisfied dependency expressed through bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Error: GlobalConfigUtils setMetaData Fail !  Cause:java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the 'serverTimezone' configuration property) to use a more specific time zone value if you want to utilize time zone support.

A:這是因為我們的mysql-connector-java依賴默認的版本是mysql8的,需要配置時區。在mysql連接的url配置上增加時區配置serverTimezone=GMT%2B8。GMT%2B8表示東8區。還有需要注意的是serverTimezone不要寫錯成serverTimeZone。

Q:@Repository是用於修飾類,讓spring掃描注冊到容器中,那么接口上加@Repository有意義么?

A: 在接口上加@Repository注解沒有意義

首先,如果AttachmentFileMapper不加@Repository注解的話有些版本的idea會報NoSuchBeanDefinitionException的紅色下划線錯誤,實際是可以啟動成功的,並沒有什么影響。其次,@Repository注解作用是告訴spring容器這是個bean實例,需要掃描加載到容器中,但是我們用@Repository標記的AttachmentFileMapper是個接口,是無法創建bean實例的。所以在接口上加@Repository注解沒有意義;最后,AttachmentFileMapper之所以只有接口而沒有實現,在運行時也沒有報錯的原因是mybatis通過@MapperScan 掃描這些mapper接口重新注冊beandefinition,對 BeanDefinition 進行了加工 processBeanDefinitions() 。當我們需要AttachmentFileMapper時會去工廠( MapperFactoryBean )里面扔了個 AttachmentFileMapper.class 的參數進去,工廠的 getObject() 方法給我們返回了它制造的 AttachmentFileMapper。

日志配置

用於開發日志打印,需要擴展slf4j接口,logback實現的日志配置。

#日志配置
mybatis-plus:
    configuration:
        #默認控制台日志輸出配置StdOutImpl,如果要使用slf4j,logback,log4j2輸出需要加對應組件依賴和更改log-impl實現的日志類
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

CRUD實踐

插入操作

示例:

@Test
public void testInsert(){
    User user = new User();
    user.setName("大青山");
    user.setAge(20);
    user.setEmail("daqingshan@qq.com");
    int result = userDao.insert(user);//幫我們自動生成id,默認主鍵生成策略是ID_WORKER 全局唯一id(雪花算法),id=1339951110603628546
    System.out.println(result);//1 ,受影響的行數
    System.out.println(user);//// 注意:id會自動回填到user對象中
}

輸出:

1
User(id=1339951110603628546, name=大青山, age=20, email=daqingshan@qq.com, version=null, gmtCreate=null, gmtModified=null, deleteFlag=null)

主鍵生成策略

數據庫插入的id的默認值為:全局的唯一id

默認 ID_WORKER 全局唯一id(即雪花算法)

分布式系統唯一ID生成方案匯總

雪花算法:
snowflake是Twitter開源的分布式ID生成算法,結果是一個long型的ID。其核心思想是:使用41bit作為
毫秒數,10bit作為機器的ID(5個bit是數據中心,5個bit的機器ID),12bit作為毫秒內的流水號(意味
着每個節點在每毫秒可以產生 4096 個 ID),最后還有一個符號位,永遠是0。可以保證幾乎全球唯
一!

主鍵自增

1、實體類字段上 @TableId(type = IdType.AUTO)
2、數據庫字段一定要是自增,AUTO_INCREMENT

報錯:主鍵自增時報 Field 'id' doesn't have a default value。

### Cause: java.sql.SQLException: Field 'id' doesn't have a default value
; Field 'id' doesn't have a default value; nested exception is java.sql.SQLException: Field 'id' doesn't have a default value

A:這是因為當我們設置id為主鍵自增時,即@TableId(type = IdType.AUTO),數據庫表相應的也要勾選保存主鍵為自動遞增,即AUTO_INCREMENT。

其他自增策略

public enum IdType {
    AUTO(0), // 數據庫id自增
    NONE(1), // 未設置主鍵
    INPUT(2), // 手動輸入
    ID_WORKER(3), // 默認的全局唯一id
    UUID(4), // 全局唯一id uuid
    ID_WORKER_STR(5); //ID_WORKER 字符串表示法
}

更新操作

所有的sql都是自動幫你動態配置的。

示例:

 @Test
    public void testUpdate() {
        //// 通過條件自動拼接動態sql
        User user = new User();
        user.setName("大青山GREEN");
        user.setAge(210);
        user.setEmail("daqingshan@qq.com");
        //UPDATE user SET name=?, age=?, email=? WHERE id=?  注意如果id為null則執行成功但沒有行數據被更新,result=0
        user.setId(9L);
        // 注意:updateById 但是參數是一個 對象!
        int result = userDao.updateById(user);
        System.out.println(result);//1 ,受影響的行數
        System.out.println(user);//注意更新后的值也會回填到user對象中 User(id=9, name=大青山GREEN, age=210, email=daqingshan@qq.com, version=null, gmtCreate=null, gmtModified=null, deleteFlag=null)
    }

自動填充

創建時間、修改時間!這些個操作一遍都是自動化完成的,我們不希望手動更新!
阿里巴巴開發手冊:所有的數據庫表:gmt_create、gmt_modified幾乎所有的表都要配置上!而且需
要自動化!

方式一:數據庫級別

數據庫加入默認值,由數據庫完成自動填充。

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
  `name` varchar(30) NOT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年齡',
  `email` varchar(50) DEFAULT NULL COMMENT '郵箱',
  `version` int(5) DEFAULT NULL COMMENT '版本號',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
  `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',
  `delete_flag` int(1) DEFAULT NULL COMMENT '是否刪除 0:否,1:是',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

方式二:代碼級別

這時候就不需要數據庫表設計字段默認值了。

1、實體類字段屬性上需要增加注解

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime gmtCreate;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime gmtModified;

2、編寫處理器來處理這個注解,實現MetaObjectHandler接口。有兩種寫法setFieldValByNamestrictInsertFill

3.0.5版本寫法:

//把處理器加到IOC容器中!
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    //// 插入時的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.error("insertFill start...........");
        this.setFieldValByName("gmtCreate", LocalDateTime.now(),metaObject);
        this.setFieldValByName("gmtModified", LocalDateTime.now(),metaObject);
        log.error("insertFill end...........");
    }

    // 更新時的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.error("updateFill start...........");
        this.setFieldValByName("gmtModified", LocalDateTime.now(),metaObject);
        log.error("updateFill end...........");
    }
}

3.3.0之后版本3.4.2,當然前面的版本也兼容:

@Slf4j
@Component
public class MyNewMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.error("insertFill start...........");
        this.strictInsertFill(metaObject, "gmtCreate", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推薦使用)
        // 或者
        this.strictUpdateFill(metaObject, "gmtModified", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推薦)

        log.error("insertFill end...........");
 }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.error("insertFill start...........");
        this.strictUpdateFill(metaObject, "gmtModified", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推薦)

        log.error("insertFill end...........");
    }
}

樂觀鎖

樂觀鎖 : 故名思意十分樂觀,它總是認為不會出現問題,無論干什么不去上鎖!如果出現了問題,
再次更新值測試
悲觀鎖:故名思意十分悲觀,它總是認為總是出現問題,無論干什么都會上鎖!再去操作!

樂觀鎖實現方式:
取出記錄時,獲取當前 version
更新時,帶上這個version
執行更新時, set version = newVersion where version = oldVersion
如果version不對,就更新失敗

--樂觀鎖:1、先查詢,獲得版本號 version = 1
update user set name = "kuangshen", version = version + 1
where id = 2 and version = 1

測試Mybatis-plus的樂觀鎖插件實現

1、數據庫表加version字段。

  `version` int(5) DEFAULT '1' COMMENT '版本號',

2、實體類加對應的字段

//樂觀鎖Version注解
@Version
private Integer version;

3、注冊組件

//啟動類上去掃描我們的dao包下的所有接口,我們可以把@MapperScan放在我們的MP配置類上也行
@MapperScan("com.wayne.dao")
@Configuration
@EnableTransactionManagement
public class MyBatisPlusConfig {

    // 注冊樂觀鎖插件
    @Bean
    public OptimisticLockerInterceptor getOptimisticLockerInterceptor (){
        return new OptimisticLockerInterceptor();
    }

// 注冊樂觀鎖插件
/*    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mbp = new MybatisPlusInterceptor();
        mbp.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mbp;
    }*/
}

4、測試:

   @Test
    public void testOptimistic() {
        User user = userDao.selectById(9L);
        user.setName("大青山GGFG");
        user.setAge(21);
        user.setEmail("daqingshan@qq.com");
        int result = userDao.updateById(user);
        System.out.println(result);//1 ,受影響的行數
        System.out.println(user);//注意更新后的值也會回填到user對象中 User(id=9, name=大青山GREEN, age=210, email=daqingshan@qq.com, version=null, gmtCreate=null, gmtModified=null, deleteFlag=null)
    }

    @Test
    public void testOptimisticFail() {
        User user = userDao.selectById(9L);
        user.setName("大青山GGFG");
        user.setAge(21);
        user.setEmail("daqingshan@qq.com");
        // 模擬另外一個線程執行了插隊操作
        User user2 = userDao.selectById(9L);
        user2.setName("大青山wwwwwww");
        user2.setEmail("daqingshan@163.com");
        userDao.updateById(user2);
        //注意這里並不會報錯,而是會返回執行影響行數為0,表示沒有update成功。因為下面這個語句的version值改變了所以更新失敗
        //PDATE user SET name=?, age=?, email=?, version=?, gmt_modified=? WHERE id=? AND version=?
        int result = userDao.updateById(user);
        System.out.println(result);//1 ,受影響的行數
        //自旋鎖來多次嘗試提交!重新查一遍才能得到最新的version
    /*    while (0 == userDao.updateById(user)) {
            user = userDao.selectById(9L);
            user.setName("大青山GGFG");
            user.setAge(21);
            user.setEmail("daqingshan@qq.com");
        }*/
        System.out.println(user);//注意更新后的值也會回填到user對象中 User(id=9, name=大青山GREEN, age=210, email=daqingshan@qq.com, version=null, gmtCreate=null, gmtModified=null, deleteFlag=null)
    }

//待補充

疑問:

1、在引入maven依賴的時候為什么有些組件依賴可以不寫version版本號就能依賴成功,而有些卻不行呢?

如下面的lombok和mybatis-plus-boot-starter

<!--		lombok依賴-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
<!--		mybatis-plus依賴-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.0.5</version>
		</dependency>

這是因為maven繼承的原因,父模塊集成了公共的maven依賴,只需要依賴GA即可。

2、1、MyBatis-Plus如何幫我們把SQL寫好了,我們的UserDao接口在實際運行中是如何創建實現類來具體實現我們想要的方法的?

參考

MyBatis-Plus官網

MyBatis-Plus教程——狂神

MyBatis、Spring、SpringMVC

基於3.0.5及3.4.2版本

擴展

Q: UTC和GMT的區別?

A:UTC是我們現在用的時間標准,GMT是老的時間計量標准。UTC是根據原子鍾來計算時間,而GMT是根據地球的自轉和公轉來計算時間,也就是太陽每天經過位於英國倫敦郊區的皇家格林威治天文台的時間就是中午12點,。由於現在世界上最精確的原子鍾50億年才會誤差1秒(最精確原子鍾問世:50億年誤差一秒),可以說非常精確。而GMT因為是根據地球的轉動來計算時間的,而地球的自轉正在緩速變慢,所以使用GMT的話,總有一天,打個比方,中午12點,並不是一天太陽當頭照的時候,很可能就是早上或者晚上了。所以說UTC更加精確。

[參考]( UTC和GMT什么關系? - 知乎用戶的回答 - 知乎 https://www.zhihu.com/question/27052407/answer/56923233 )


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM