Mybatis和MybatisPlus3.4的使用
1 RESTFUL
前後端風你開發的專案中,前後端之間是介面進行請求和響應,後端向前端提供請求時就要對外暴露一個URL;URL的設計不能是隨意的,需要遵從一定的設計規範----RESTFUL。
RESTful是一種web api的標準,也就是一種url設計風格/規範
-
每個URL請求路徑代表伺服器上的唯一資源。
傳統的URL設計:
http://localhost:8080/goods/delete?goodsId=1 商品1
http://localhost:8080/goods/delete?goodsId=2 商品2
RESTful設計:
示例:
@DeleteMapping("/delete/{id}") public ResultVO deleteGoods(@PathVariable("id") Integer goodsId) { return new ResultVO(10003,"請求成功",goodsId); }
-
使用不同的請求方式表示不同的操作,保證唯一URL對應唯一資源
- @GetMapping 查詢
- @PutMapping 修改
- @PostMapping 新增
- @DeleteMapping 刪除
根據id刪除一個商品:
//http://localhost:8080/goods/1 [delete] @DeleteMapping("/{id}") public ResultVO deleteGoods(@PathVariable("id") Integer goodsId) { return new ResultVO(10003,"請求成功",goodsId); }
根據id查詢一個商品:
//http://localhost:8080/goods/1 [get] @GetMapping("/{id}") public ResultVO getGoods(@PathVariable("id") Integer goodsId) { return new ResultVO(10003,"請求成功",goodsId); }
-
介面響應的資源表現形式使用JSON
- 可以在控制類或者需要的介面方法上新增
@ResponseBody
註解講返回的物件格式化為JSON - 也可以在控制類上使用
@RestController
宣告控制器
- 可以在控制類或者需要的介面方法上新增
-
前端(Android\ios\PC)通過無狀態的HTTP協議與後端介面互動
2 逆向工程
mybatis官方提供了一種根據資料庫表生成mybatis執行程式碼的工具,這個工具就是一個逆向工程
逆向工程:針對資料庫單表—->生成程式碼(
mapper.xml、mapper.java、pojo。。
)這裡我最開始使用的是
mybatis
逆向工程,可以生成mapper.xml、mapper.java、pojo
後面我想嘗試使用
MP
做逆向工程,所以使用了兩種方式,從簡潔上來看,還是MP
比較方便一點.
2.1 tkMybatis逆向工程
逆向工程並且自動新增swagger註解,注意需要在beans的pom也要新增swagger依賴
2.1.1 匯入依賴
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
- 修改啟動類註解
@MapperScan
的包,因為這個註解還有個包是org.mybatis.spring.annotation.MapperScan;
2.1.2 新增外掛
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client --><!-- https://mvnrepository.com/artifact/tk.mybatis/mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.1.5</version>
</dependency>
<dependency>
<groupId>com.github.misterchangray.mybatis.generator.plugins</groupId>
<artifactId>myBatisGeneratorPlugins</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
同步成功後會出現
2.1.3 新增配置檔案
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--匯入屬性配置-->
<!-- <properties resource="db.properties"></properties>-->
<!--指定特定資料庫的jdbc驅動jar包的位置-->
<!--<classPathEntry location="${jdbc.location}"/>-->
<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<!--結合通用Mapper外掛 指定生成 Mapper 的繼承模板-->
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="com.mymall.general.GeneralDao"/>
</plugin>
<!--pojo實現序列化介面-->
<plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
<!--pojo類中增加toString方法-->
<plugin type="org.mybatis.generator.plugins.ToStringPlugin" />
<!--覆蓋生成XML檔案 每次執行,把以前的mapper.xml覆蓋而不是合併-->
<!-- <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />-->
<!-- 自動為entity生成swagger2文件-->
<plugin type="mybatis.generator.plugins.GeneratorSwagger2Doc">
<property name="apiModelAnnotationPackage" value="io.swagger.annotations.ApiModel"/>
<property name="apiModelPropertyAnnotationPackage" value="io.swagger.annotations.ApiModelProperty"/>
</plugin>
<!--注意, plugin 需要寫在commentGenerator上面-->
<commentGenerator>
<!-- 是否去除自動生成的註釋 true:是 : false:否 -->
<property name="suppressAllComments" value="false" />
</commentGenerator>
<!--jdbc的資料庫連線 -->
<jdbcConnection
driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mymall?useSSL=false&serverTimezone=GMT%2b8"
userId="root"
password="123456">
<property name="nullCatalogMeansCurrent" value="true"/>
</jdbcConnection>
<!-- 生成 JavaBean 物件重寫 toString方法 -->
<!-- <plugin type="org.mybatis.generator.plugins.ToStringPlugin" />-->
<!-- 生成 JavaBean 物件繼承 Serializable 類 -->
<!-- <plugin type="org.mybatis.generator.plugins.SerializablePlugin" />-->
<!-- 生成 JavaBean 物件重寫 equals 和 hashCode 方法 -->
<!-- <plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin" /> -->
<!-- 對應生成的pojo所在包 -->
<javaModelGenerator targetPackage="com.mymall.entity" targetProject="src/main/java">
<!-- 是否對model新增 建構函式 -->
<property name="constructorBased" value="true"/>
</javaModelGenerator>
<!-- 對應生成的mapper.xml所在目錄 -->
<sqlMapGenerator targetPackage="/" targetProject="src/main/resources/mappers"/>
<!-- 配置mapper對應的java對映 -->
<javaClientGenerator targetPackage="com.mymall.dao" targetProject="src/main/java"
type="XMLMAPPER"/>
<!-- 配置需要指定生成的資料庫和表,%代表所有表,開發中不要這麼做,可能會生成很多無用的表-->
<!-- <table tableName="%"/>-->
<table tableName="category" />
<table tableName="index_img" />
<table tableName="order_item" />
<table tableName="orders" />
<table tableName="product" />
<table tableName="product_comments" />
<table tableName="product_img" />
<table tableName="product_params" />
<table tableName="product_sku" />
<table tableName="shopping_cart" />
<table tableName="user_addr" />
<table tableName="user_login_history" />
<table tableName="users" />
<!-- <table schema="" tableName="oauth_access_token" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
<domainObjectRenamingRule searchString="^Tb" replaceString="" />
</table>
<table schema="" tableName="oauth_approvals" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
<domainObjectRenamingRule searchString="^Tb" replaceString="" />
</table>-->
<!--將你要生成的表盡數羅列 -->
</context>
</generatorConfiguration>
-
調整配置資訊
-
建立
GeneralDao
介面package com.mymall.general; import tk.mybatis.mapper.common.Mapper; import tk.mybatis.mapper.common.MySqlMapper; /** * @author 18230 * @version 1.0.0 * @ClassName GereralDao.java * @Description * @createTime 2021年09月25日 15:50:00 */ public interface GeneralDao<T> extends Mapper<T>, MySqlMapper<T> { }
<!--配置GeneralDao--> <plugin type="tk.mybatis.mapper.generator.MapperPlugin"> <property name="mappers" value="com.mymall.general.GneralDao"/> </plugin>
-
配置資料庫連線資訊
<!--jdbc的資料庫連線,url後面的配置不要修改 --> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mymall?useSSL=false&serverTimezone=GMT%2b8" userId="root" password="123456"> </jdbcConnection>
-
配置實體類路徑
<!-- 對應生成的pojo所在包 --> <javaModelGenerator targetPackage="com.mymall.entity" targetProject="src/main/java"> <!-- 是否對model新增 建構函式 --> <property name="constructorBased" value="true"/> </javaModelGenerator>
-
配置
mapper.xml
目錄<!-- 對應生成的mapper.xml所在目錄 --> <sqlMapGenerator targetPackage="/" targetProject="src/main/resources/mappers"/>
-
配置dao的目錄和生成表的名稱
<!-- 配置mapper對應的java對映 --> <javaClientGenerator targetPackage="com.mymall.dao" targetProject="src/main/java" type="XMLMAPPER"/> <!-- 配置需要指定生成的資料庫和表,%代表所有表,開發中不要這麼做,可能會生成很多無用的表--> <!-- <table tableName="%"/>--> <table tableName="category" /> <table tableName="index_img" /> <table tableName="order_item" /> <table tableName="orders" /> <table tableName="product" /> <table tableName="product_comments" /> <table tableName="product_img" /> <table tableName="product_params" /> <table tableName="product_sku" /> <table tableName="shopping_cart" /> <table tableName="user_addr" /> <table tableName="user_login_history" /> <table tableName="users" />
-
將配置檔案
generatorConfig.xml
配置到逆向工程的maven外掛中<groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.5</version> <configuration> <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile> </configuration>
-
2.1.4 執行逆向
2.1.5 生成成功
-
將生成的entity剪下到beans子專案中
將生成的entity移到到beans目錄之後會出現報錯,因為使用了tkMapper的註解,所以需要將tkMapper的依賴直接剪下到beans中,mapper專案引用了beans的依賴所以就可以不再pom中宣告tkMapper的依賴了.
2.1.6 使用tkMybatis
package com.qc.dao;
import com.qc.TkMapperDemoApplication;
import com.qc.entity.Category;
import org.apache.ibatis.session.RowBounds;
import org.junit.Assert;
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 tk.mybatis.mapper.entity.Example;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author 18230
* @version 1.0.0
* @ClassName CategoryDaoTest.java
* @Description
* @createTime 2021年09月05日 20:40:00
*/
@SpringBootTest(classes = TkMapperDemoApplication.class)
@RunWith(SpringRunner.class)
public class CategoryDaoTest {
@Autowired
private CategoryDao categoryDao;
@Test
public void testInsert() {
Category category = new Category(0,"test02",1,0,"01.png","hehe","111.jpg","black");
//將資料插入表中,返回結果,1,成功
//int i = categoryDao.insert(category);
//將資料插入後將生成的主鍵返回給物件儲存
int i = categoryDao.insertUseGeneratedKeys(category);
System.out.println(category.getCategoryId());
assertEquals(1,i);
}
@Test
public void testUpdate() {
Category category = new Category(13,"test03",1,0,"01.png","hehe","111.jpg","black");
//根據主鍵修改資料
int i = categoryDao.updateByPrimaryKey(category);
assertEquals(1,i);
}
@Test
public void testDelete() {
//根據主鍵刪除資料
int i = categoryDao.deleteByPrimaryKey(13);
assertEquals(1,i);
}
@Test
public void testSelect1() {
//查詢所有
List<Category> categories = categoryDao.selectAll();
for (Category category : categories) {
System.out.println(category);
}
}
@Test
public void testSelect2() {
// 根據主鍵查詢
Category category = categoryDao.selectByPrimaryKey(13);
System.out.println(category);
}
@Test
public void testSelect3() {
// 條件查詢
// 1.建立一個example封裝,類別category查詢條件
Example example = new Example(Category.class);
Example.Criteria criteria = example.createCriteria();
//criteria.andEqualTo("categoryLevel", 1);
criteria.andLike("categoryName", "%幹%");
List<Category> categories = categoryDao.selectByExample(example);
for (Category category : categories) {
System.out.println(category);
}
}
@Test
public void testSelect4() {
// 分頁查詢
int pageNum = 2;
int pageSize= 5;
int start =(pageNum-1)*pageSize;
RowBounds rowBounds = new RowBounds(start,pageSize);
List<Category> categories = categoryDao.selectByRowBounds(new Category(), rowBounds);
for (Category category : categories) {
System.out.println(category);
}
// 查詢總記錄數
int i = categoryDao.selectCount(new Category());
System.out.println(i);
}
@Test
public void testSelect5() {
//準備條件
Example example = new Example(Category.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("categoryLevel", 1);
//準備分頁條件
int pageNum = 1;
int pageSize= 3;
int start =(pageNum-1)*pageSize;
RowBounds rowBounds = new RowBounds(start,pageSize);
//條件分頁顯示
List<Category> categories = categoryDao.selectByExampleAndRowBounds(example, rowBounds);
for (Category category : categories) {
System.out.println(category);
}
// 查詢滿足條件的記錄數
int i = categoryDao.selectCountByExample(example);
System.out.println(i);
}
}
2.2 MybatisPlus使用教程
2.2.1 在mapper子專案中新增依賴
在建立mapper子專案的時候已經新增了
mybatis-plus-boot-starter
,所以這裡不需要額外新增依賴
2.2.2 配置application.yml
當時用
mysql8
時,需要mysql的驅動同:com.mysql.cj.jdbc.Driver
,同時需要增加時區設定:serverTimezone=GMT%2b8
datasource:
druid:
#資料庫連線驅動
driver-class-name: com.mysql.cj.jdbc.Driver
#資料庫連線地址
url: jdbc:mysql://localhost:3306/mymall?serverTimezone=GMT%2b8&useSSL=false&allowPublicKeyRetrieval=true
#資料庫連線使用者名稱和密碼
username: root
password: 123456
2.2.3 配置@MapperScan
package com.mymall;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.oas.annotations.EnableOpenApi;
@SpringBootApplication
@MapperScan("com.mymall.dao")
@EnableOpenApi
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
2.2.4 編寫實體類Users
package com.mymall.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {
Integer userId;
String username;
String password;
}
2.2.5 編寫Mapper.java
繼承模版BaseMapper
package com.mymall.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mymall.entity.Users;
import org.springframework.stereotype.Repository;
@Repository
public interface UsersMapper extends BaseMapper<Users> {
}
2.2.6 測試是否能夠正常呼叫MP
package com.mymall;
import com.mymall.entity.Users;
import com.mymall.dao.UsersMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class ApiApplicationTests {
@Autowired
private UsersMapper usersMapper;
@Test
void contextLoads() {
List<Users> users = usersMapper.selectList(null);
for (Users user : users) {
System.out.println(user);
}
System.out.println(users);
}
}
測試成功!
2.2.7 配置日誌
mybatis-plus:
configuration:
# 配置日誌
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
顯示效果
2.2.8 CRUD擴充套件
2.2.8.1 資料庫插入的id的預設值:全域性的唯一id
-
雪花演算法:snowflake是Twitter開源的分散式ID生成演算法,結果是一個long型的ID。其核心思想是:使用41bit作為毫秒數,10bit作為機器的ID(5個bit是資料中心,5個bit的機器ID),12bit作為毫秒內的流水號(意味著每個節點在每毫秒可以產生 4096 個 ID),最後還有一個符號位,永遠是0。具體實現的程式碼可以參看https://github.com/twitter/snowflake。雪花演算法支援的TPS可以達到419萬左右(2^22*1000)。
-
注意:
使用雪花或者uuid的時候在資料中的欄位不能是自增,不然不會生效.
-
設定要求:
-
主鍵需要設定成自增
-
實體類欄位上新增
MP
註解@TableId(type = IdType.ASSIGN_ID)
-
IdType
型別原始碼package com.baomidou.mybatisplus.annotation; import lombok.Getter; /** * 生成ID型別列舉類 * * @author hubin * @since 2015-11-10 */ @Getter public enum IdType { /** * 資料庫ID自增 * <p>該型別請確保資料庫設定了 ID自增 否則無效</p> */ AUTO(0), /** * 該型別為未設定主鍵型別(註解裡等於跟隨全域性,全域性裡約等於 INPUT) */ NONE(1), /** * 使用者輸入ID * <p>該型別可以通過自己註冊自動填充外掛進行填充</p> */ INPUT(2), /* 以下2種型別、只有當插入物件ID 為空,才自動填充。 */ /** * 分配ID (主鍵型別為number或string), * 預設實現類 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花演算法) * * @since 3.3.0 */ ASSIGN_ID(3), /** * 分配UUID (主鍵型別為 string) * 預設實現類 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-","")) */ ASSIGN_UUID(4), private final int key; IdType(int key) { this.key = key; } }
-
-
Insert自動生成id
@Test//測試插入 public void insertTest(){ User user = new User(); user.setName("wsk"); user.setEmail("2803708553@qq.com"); Integer result = userMapper.insert(user); //會幫我們自動生成id System.out.println(result); //受影響的行數 System.out.println(user); //通過日誌發現id會自動回填 }
2.2.8.2 自動填充
建立時間、更改時間! 這些操作一般都是自動化完成,我們不希望手動更新
阿里巴巴開發手冊︰幾乎所有的表都要配置 gmt_create、gmt_modified !而且需要自動化
-
方式一:資料庫級別(工作中不允許修改資料庫級別)
1、在表中增加欄位:create_time,update_time
-- auto-generated definition create table mp_user ( user_id bigint auto_increment comment '使用者id' primary key, username varchar(32) not null comment '使用者名稱', password varchar(32) not null comment '密碼', create_time datetime default CURRENT_TIMESTAMP not null comment '建立時間', update_time datetime default CURRENT_TIMESTAMP not null comment '更新時間' );
2、更新實體類中的成員變數
package com.mymall.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; @Data @AllArgsConstructor @NoArgsConstructor public class MpUser { @TableId(type = IdType.ASSIGN_ID) Long userId; String username; String password; Date createTime; Date updateTime; }
3、再次執行之前插入或更新一條資料的程式碼顯示成功自動插入時間
-
方式二:程式碼級別
1、刪除資料庫的預設值,更新操作!
2、實體類欄位屬性上需要增加註解
@Data @AllArgsConstructor @NoArgsConstructor public class MpUser { @TableId(type = IdType.ASSIGN_ID) Long userId; String username; String password; //插入時註解 @TableField(fill = FieldFill.INSERT) Date createTime; //更新時註解 @TableField(fill = FieldFill.UPDATE) Date updateTime; }
註解詳情
package com.baomidou.mybatisplus.annotation; /** * 欄位填充策略列舉類 * <p> * 判斷注入的 insert 和 update 的 sql 指令碼是否在對應情況下忽略掉欄位的 if 標籤生成 * <if test="...">......</if> * 判斷優先順序比 {@link FieldStrategy} 高 * </p> * @author hubin * @since 2017-06-27 */ public enum FieldFill { /** * 預設不處理 */ DEFAULT, /** * 插入時填充欄位 */ INSERT, /** * 更新時填充欄位 */ UPDATE, /** * 插入和更新時填充欄位 */ INSERT_UPDATE }
3、編寫處理器來處理這個註解即可!
@Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推薦使用) // 或者 this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推薦) // 或者 this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 該方法有bug) } @Override public void updateFill(MetaObject metaObject) { log.info("start update fill ...."); this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推薦) // 或者 this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推薦) // 或者 this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 該方法有bug) } }
注意事項:
- 填充原理是直接給
entity
的屬性設定值!!! - 註解則是指定該屬性在對應情況下必有值,如果無值則入庫會是
null
MetaObjectHandler
提供的預設方法的策略均為:如果屬性有值則不覆蓋,如果填充值為null
則不填充- 欄位必須宣告
TableField
註解,屬性fill
選擇對應策略,該宣告告知Mybatis-Plus
需要預留注入SQL
欄位 - 填充處理器
MyMetaObjectHandler
在 Spring Boot 中需要宣告@Component
或@Bean
注入 - 要想根據註解
FieldFill.xxx
和欄位名
以及欄位型別
來區分必須使用父類的strictInsertFill
或者strictUpdateFill
方法 - 不需要根據任何來區分可以使用父類的
fillStrategy
方法
4.測試執行插入或更新語句
- 填充原理是直接給
2.2.9 樂觀鎖
在面試過程中經常被問到樂觀鎖/悲觀鎖,這個其實很簡單
樂觀鎖:顧名思義十分樂觀,他總是認為不會出現問題,無論幹什麼都不上鎖!如果出現了問題,再次更新值測試
悲觀鎖:顧名思義十分悲觀,他總是認為出現問題,無論幹什麼都會上鎖!再去操作!
當要更新一條記錄的時候,希望這條記錄沒有被別人更新
樂觀鎖實現方式:
-
取出記錄時,獲取當前version
-
更新時,帶上這個version
-
執行更新時,set version = newVersion where version = oldVersion
-
如果version不對,就更新失敗
樂觀鎖:先查詢,獲得版本號 -- A update user set name = "wsk",version = version+1 where id = 1 and version = 1 -- B (B執行緒搶先完成,此時version=2,會導致A執行緒修改失敗!) update user set name = "wsk",version = version+1 where id = 1 and version = 1
-
更新資料庫,新增
version
欄位-- auto-generated definition create table mp_user ( user_id bigint auto_increment comment '使用者id' primary key, username varchar(32) not null comment '使用者名稱', password varchar(32) not null comment '密碼', version int null comment '樂觀鎖', create_time datetime default CURRENT_TIMESTAMP not null comment '建立時間', update_time datetime default CURRENT_TIMESTAMP not null comment '更新時間' );
-
實體類新增加對應的欄位和註解
@Version Integer version;
-
註冊元件
//掃描mapper資料夾 @MapperScan("com.mymall.dao")//交給mybatis做的,可以讓這個配置類做掃描 @EnableTransactionManagement//自動管理事務 @Configuration//配置類 public class MyBatisPlusConfig { //註冊樂觀鎖外掛 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }
-
測試
-
測試樂觀鎖成功
@Test void testOptimisticLocker1() { //1、查詢使用者資訊 MpUser mpUser = mpUserMapper.selectById(1L); //2、修改使用者資訊 mpUser.setUsername("qq"); //3、執行更新操作 int update = mpUserMapper.updateById(mpUser); System.out.println(mpUser); }
-
模擬多執行緒測試樂觀鎖插入資料失敗
@Test void testOptimisticLocker2() { //執行緒1 MpUser mpUser = mpUserMapper.selectById(1L); mpUser.setUsername("aa"); mpUser.setPassword("666666"); //模擬另外一個執行緒執行了插隊操作 MpUser mpUser1 = mpUserMapper.selectById(1L); mpUser1.setUsername("bb"); mpUser1.setPassword("12344444"); mpUserMapper.updateById(mpUser1); //自旋鎖來多次嘗試提交! mpUserMapper.updateById(mpUser);//如果沒有樂觀鎖就會覆蓋插隊執行緒的值 }
-
2.2.10 查詢
-
通過id查詢單個使用者
@Test public void testSelectById(){ User user = userMapper.selectById(1L); System.out.println(user); }
-
通過id查詢多個使用者
@Test public void testSelectBatchIds(){ List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L)); users.forEach(System.out::println); }
-
條件查詢 通過map封裝
@Test public void testMap(){ HashMap<String, Object> map = new HashMap<>(); //自定義要查詢的 map.put("name","www"); map.put("age",18); List<User> users = userMapper.selectByMap(map); users.forEach(System.out::println); }
-
分頁查詢
分頁在網站的使用十分之多!
1、原始的limit分頁
2、PageHelper第三方外掛
3、MybatisPlus其實也內建了分頁外掛!
使用步驟:
-
配置攔截器元件
package com.mymall.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @MapperScan("com.mymall.dao") @EnableTransactionManagement public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //新增樂觀鎖 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); //新的分頁外掛,一緩和二緩遵循mybatis的規則, //需要設定 MybatisConfiguration#useDeprecatedExecutor = false // 避免快取出現問題(該屬性會在舊外掛移除後一同移除) //也可以不傳DbType.MYSQL,為空時會通過URL判斷資料庫型別 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } @Bean public ConfigurationCustomizer configurationCustomizer() { return configuration -> configuration.setUseDeprecatedExecutor(false); } }
-
測試分頁查詢
@Test public void testPage(){ //引數一current:當前頁 引數二size:頁面大小 //使用了分頁外掛之後,所有的分頁操作都變得簡單了 Page<User> page = new Page<>(2,5); //第二個引數是高階查詢,暫設為null userMapper.selectPage(page,null); page.getRecords().forEach(System.out::println); System.out.println("總頁數==>"+page.getTotal()); }
-
2.2.11 刪除
-
基本刪除操作
@Test public void testDeleteById(){ userMapper.deleteById(1); } @Test public void testDeleteBatchIds(){ userMapper.deleteBatchIds(Arrays.asList(1,2)); } @Test public void testD(){ HashMap<String, Object> map = new HashMap<>(); map.put("age","18"); map.put("name","lol"); userMapper.deleteByMap(map); }
-
邏輯刪除
物理刪除:從資料庫中直接刪除
邏輯刪除:在資料庫中沒有被刪除,而是通過一個變數來使他失效! deleted=0 ==> deleted=1
管理員可以檢視被刪除的記錄!防止資料的丟失,類似於回收站!
實現步驟:
-
資料庫中新增
deleted
欄位deleted int default 0 null comment '邏輯刪除:0顯示1刪除',
-
新增配置
application.yml
mybatis-plus: global-config: db-config: logic-delete-field: deleted # 全域性邏輯刪除的實體欄位名(since 3.3.0,配置後可以忽略不配置步驟2) logic-delete-value: 1 # 邏輯已刪除值(預設為 1) logic-not-delete-value: 0 # 邏輯未刪除值(預設為 0)
-
在實體類上新增
deleted
欄位並加上@TableLogic
註解(上面配置了application.yml
後可以不新增註解,但是deleted
欄位一定要新增)@TableLogic private Integer deleted;
-
測試刪除一條資料後查詢是否資料還存在,測試結果,只是
deleted
設定為了1@Test void testLogicDelete() { //刪除id為2的資料 int i = mpUserMapper.deleteById(2); MpUser mpUser = mpUserMapper.selectById(2); System.out.println(mpUser); }
-
檢視資料庫,發現記錄還在,deleted變為1
再次測試查詢被刪除的使用者,發現查詢為空
2.2.12 效能分析外掛(新版本已經移除)
我們在平時的開發中,會遇到一些慢Sql。測試、druid···
MybatisPlus也提供了效能分析外掛,如果超過這個時間就停止執行!
2.2.13 條件構造器wrapper
警告:
不支援以及不贊成在 RPC 呼叫中把 Wrapper 進行傳輸
- wrapper 很重
- 傳輸 wrapper 可以類比為你的 controller 用 map 接收值(開發一時爽,維護火葬場)
- 正確的 RPC 呼叫姿勢是寫一個 DTO 進行傳輸,被呼叫方再根據 DTO 執行相應的操作
-
常用api請到網頁中檢視條件構造器 | MyBatis-Plus (baomidou.com)
-
使用示例
-
查詢不為空且比某值大的資料
@Test void testWrapper() { //引數是一個wrapper ,條件構造器,和剛才的map對比學習! //查詢username不為空,password不為空,user_id大於18的使用者 //注意這裡的column引數一定是欄位名,不是實體類中的成員變數 QueryWrapper<MpUser> wrapper = new QueryWrapper<>(); wrapper .isNotNull("username") .isNotNull("password") .ge("user_id",2); List<MpUser> userList = mpUserMapper.selectList(wrapper); userList.forEach(System.out::println); }
-
查詢某欄位值為確定值的資料,返回結果有一個和多個的可能
@Test public void testWrapper2() { //查詢name=wsk的使用者 QueryWrapper<MpUser> wrapper = new QueryWrapper<>(); wrapper.eq("username","MPInsertTest1"); //查詢一個資料selectOne,若查詢出多個會報錯 //Expected one result (or null) to be returned by selectOne(), but found: * //若出現多個結果使用list或map MpUser mpUser = mpUserMapper.selectOne(wrapper);//查詢一個資料,若出現多個結果使用list或map System.out.println(mpUser); List<MpUser> mpUsers = mpUserMapper.selectList(wrapper); }
-
查詢欄位值範圍在某個區間的資料
@Test public void testWrapper3() { //查詢age在10-20之間的使用者的使用者數量 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.between("age", 10, 20);//區間 Integer count = userMapper.selectCount(wrapper);//輸出查詢的數量selectCount System.out.println(count); }
-
模糊查詢,
notLike,likeRight,likeLeft
@Test public void testWrapper4() { //模糊查詢 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper .notLike("name","s") .likeRight("email","t");//qq% 左和右? List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); }
-
子查詢
@Test public void testWrapper5() { // SELECT id,name,age,email,version,deleted,create_time,update_time //FROM user //WHERE deleted=0 AND id IN //(select id from user where id<5) QueryWrapper<User> wrapper = new QueryWrapper<>(); //id 在子查詢中查出來 wrapper.inSql("id","select id from user where id<5"); List<Object> objects = userMapper.selectObjs(wrapper); objects.forEach(System.out::println); }
-
通過id進行排序
@Test public void testWrapper6() { QueryWrapper<User> wrapper = new QueryWrapper<>(); //通過id進行降序排序 wrapper.orderByDesc("id"); List<User> userList = userMapper.selectList(wrapper); userList.forEach(System.out::println); }
-
2.2.14 程式碼生成器,MP
逆向工程
-
新增依賴
MyBatis-Plus 從
3.0.3
之後移除了程式碼生成器與模板引擎的預設依賴,需要手動新增生成器和模版引擎依賴這裡嘗試使用3.5.1版本(以下教程僅適用 3.5.1 以上版本,對歷史版本的不相容)
// 注意!!當前包未傳遞依賴 mp 包,需要自己引入 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>Latest Version</version> </dependency>
新增 模板引擎 依賴,MyBatis-Plus 支援 Velocity(預設)、Freemarker、Beetl,使用者可以選擇自己熟悉的模板引擎,如果都不滿足您的要求,可以採用自定義模板引擎。這裡按照官方文件選擇了
freemarker
Velocity(預設): <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>latest-velocity-version</version> </dependency> Freemarker: <!-- https://mvnrepository.com/artifact/org.freemarker/freemarker --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency> Beetl: <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> <version>latest-beetl-version</version> </dependency>
-
在
mpper
子專案中建立程式碼生成器配置類package com.mymall; import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import java.util.Collections; /** * @author 18230 * @version 1.0.0 * @ClassName CodeGenerator.java * @Description * @createTime 2021年09月30日 22:37:00 */ public class CodeGenerator { public static void main(String[] args) { /* * url jdbc路徑 jdbc:mysql://127.0.0.1:3306/mybatis-plus * username 資料庫賬號 root * password 資料庫密碼 123456 */ FastAutoGenerator.create("jdbc:mysql://localhost:3306/mymall?serverTimezone=GMT%2b8&useSSL=false&allowPublicKeyRetrieval=true", "root", "123456") .globalConfig(builder -> { builder.author("QC") // 設定作者 .enableSwagger() // 開啟 swagger 模式 .fileOverride() // 覆蓋已生成檔案 //禁止開啟目錄 .disableOpenDir() // 指定輸出目錄,這裡使用絕對路徑 .outputDir("F:\\JavaProjcet\\MyMallPractice2\\mapper\\src\\main\\java"); //如果使用/src/main/java會在根目錄下建立檔案,也就是沒成功 //原始碼:this.outputDir = System.getProperty("os.name").toLowerCase().contains("windows") ? "D://" : "/tmp"; //System.getProperty("os.name")獲取作業系統名稱,如果名稱包含windows就"D://"否則就"/tmp" //判斷是windows系統,否則就是linux系統,所以還是隻能使用絕對地址 //.outputDir("classpath:/src/main/java"); }) .packageConfig(builder -> { builder.parent("com.mymall") // 設定父包名 //.moduleName("mapper") // 設定父包模組名,也就是在父包名中新增一個包 //但是這裡設定相對路徑成功了一次,但後面還是生成了根目錄,所以還是使用絕對路徑 //.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "/src/main/resources/mapper")); // 設定mapperXml生成路徑 .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "F:\\JavaProjcet\\MyMallPractice2\\mapper\\src\\main\\resources\\mapper")); // 設定mapperXml生成路徑 }) .strategyConfig(builder -> { builder.addInclude("users") // 設定需要生成的表名 .addInclude("category") .addInclude("index_img") .addInclude("mp_user") .addInclude("product") .addInclude("product_sku") .addInclude("product_img") .addInclude("orders") .addInclude("product_comments") .addInclude("product_params") //.setEntityLombokModel(true) //開啟lombook支援,新版本已經沒有了 .addTablePrefix("mp_"); // 設定過濾表字首,也就是將字首去掉,只保留後面對的內容作為實體名 }) .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,預設的是Velocity引擎模板 .execute(); } }
生成成功
3 常見問題解決辦法
3.1 資料庫url中的SSL問題
解決辦法:將gerneratorConfig.xml
中的url改成useSSL=false
connectionURL="jdbc:mysql://localhost:3306/mymall?useSSL=false&serverTimezone=GMT%2b8"&useUnicode=true&characterEncoding=utf-8
3.2 外掛沒找到
解決:調整mybatis-generator-maven-plugin
版本
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
3.3 將資料庫中的其他表都生成了
解決:在gerneratorConfig.xml
只新增需要生成的表名
<table tableName="category" />
3.4 users表生成了其他表
解決辦法:
-
修改我的表名稱
-
想辦法不讓MySql保留表影響到我的逆向工程
要保證只生成自己指定的 database 中的 User 表,首先要在
中的 connectionURL 中指定資料庫的例項名,然後在 中新增相關配置資訊,即 ,即可保證只生成自己需要的 User 類。
配置如下:<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/xxx" userId="xxx" password="xxx"> <property name="nullCatalogMeansCurrent" value="true"/> </jdbcConnection>