需要的基礎:MyBatis、Spring、SpringMVC就可以學習這個了! 為什麼要學習它呢?MyBatisPlus可以節省我們大量工作時間,所有的CRUD代碼它都可以自動化完成!
JPA 、 tk-mapper、MyBatisPlus,偷懶用的!
是什麼? MyBatis 本來就是簡化 jdbc 操作的! 官網:https://baomidou.com/,簡化 MyBatis !
官方描述
地址:https://baomidou.com/guide/quick-start.html
使用第三方組件套路:
1、導入對應的依賴
2、研究依賴如何配置
3、代碼如何編寫
4、提高擴展技術能力!
1、創建資料庫 mybatis_plus
2、創建user表
DROP TABLE IF EXISTS user;CREATE TABLE user(id BIGINT(20) NOT NULL COMMENT &39;,name VARCHAR(30) NULL DEFAULT NULL COMMENT &39;, age INT(11) NULL DEFAULT NULL COMMENT &39;,email VARCHAR(50) NULL DEFAULT NULL COMMENT &39;, PRIMARY KEY (id));INSERT INTO user (id, name, age, email) VALUES(1, &39;, 18, &39;),(2, &39;, 20, &39;),(3, &39;, 28, &39;),(4, &39;, 21, &39;),(5, &39;, 24, &39;);-- 真實開發中,version(樂觀鎖)、deleted(邏輯刪除)、gmt_create、gmt_modified1234567891011121314
3、編寫項目,初始化項目!使用SpringBoot初始化!
4、導入依賴
<!-- 資料庫驅動 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- mybatis-plus --> <!-- mybatis-plus 是自己開發,並非官方的! --> <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-web</artifactId> </dependency>12345678910111213141516171819202122
說明:我們使用 mybatis-plus 可以節省我們大量的代碼,儘量不要同時導入 mybatis 和 mybatis- plus!版本的差異!
5、連接資料庫!這一步和 mybatis 相同!
配置日誌mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl12
Insert 插入
// 測試插入 @Test public void testInsert(){ User user = new User(); user.setName(&34;); user.setAge(18); user.setEmail(&34;); int result = userMapper.insert(user); // 幫我們自動生成id System.out.println(result); // 受影響的行數 System.out.println(user); // 發現,id會自動回填 }123456789101112
資料庫插入的id的默認值為:全局的唯一id
默認 ID_WORKER 全局唯一id
分布式系統唯一id生成:9種分布式ID生成方式,總有一款適合你
雪花算法:
snowflake是Twitter開源的分布式ID生成算法,結果是一個long型的ID。其核心思想是:使用41bit作為 毫秒數,10bit作為機器的ID(5個bit是數據中心,5個bit的機器ID),12bit作為毫秒內的流水號(意味 著每個節點在每毫秒可以產生 4096 個 ID),最後還有一個符號位,永遠是0。可以保證幾乎全球唯 一!
主鍵自增
我們需要配置主鍵自增:
1、實體類欄位上
// 對應資料庫中的主鍵 (uuid、自增id、雪花算法、redis、zookeeper!) @TableId(type = IdType.AUTO) private Long id;123
2、資料庫欄位一定要是自增!
再次運行:
public enum IdType {AUTO(0), // 資料庫id自增NONE(1), // 未設置主鍵INPUT(2), // 手動輸入ID_WORKER(3), // 默認的全局唯一idUUID(4), // 全局唯一id uuidID_WORKER_STR(5); //ID_WORKER 字符串表示法}12345678
所有的sql都是自動幫你動態配置的!
// 測試更新 @Test public void testUpdate(){ User user = new User(); // 通過條件自動拼接動態sql user.setId(2L); user.setName(&34;); user.setAge(20); // 注意:updateById 但是參數是一個對象! int i = userMapper.updateById(user); System.out.println(i); }123456789101112
創建時間、修改時間!這些個操作一遍都是自動化完成的,我們不希望手動更新!
阿里巴巴開發手冊:所有的資料庫表:gmt_create、gmt_modified幾乎所有的表都要配置上!而且需 要自動化!
方式一:資料庫級別(工作中不允許你修改資料庫)
1、在表中新增欄位 create_time, update_time
2、再次測試插入方法,我們需要先把實體類同步!
private Date createTime;private Date updateTime;12
3、再次更新查看結果即可
方式二:代碼級別
1、刪除資料庫的默認值、更新操作!
2、實體類欄位屬性上需要增加註解
@TableField(fill = FieldFill.INSERT) private Date createTime; // @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;12345
3、編寫處理器來處理這個註解即可! 系統會自動查詢那些欄位添加了這樣的註解然後將時間插入。
@Slf4j@Component // 一定不要忘記把處理器加到IOC容器中!public class MyMetaObjectHandler implements MetaObjectHandler { // 插入時的填充策略 @Override public void insertFill(MetaObject metaObject) { log.info(&34;); // setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject this.setFieldValByName(&34;,new Date(),metaObject); this.setFieldValByName(&34;,new Date(),metaObject); } // 更新時的填充策略 @Override public void updateFill(MetaObject metaObject) { log.info(&34;); this.setFieldValByName(&34;,new Date(),metaObject); }}12345678910111213141516171819
4、測試插入
5、測試更新、觀察時間即可!
PS : Mybatis-plus 會自動將Java中的駝峰命名變更為下劃線命名比如 createTime ==> create_time,如果想關閉需要在yml中添加
mybatis-plus.configuration.map-underscore-to-camel-case=false1
6、只更新若干欄位的時候指定即可。比如我把name屏蔽掉
// 測試更新 @Test public void testUpdate(){ User user = new User(); // 通過條件自動拼接動態sql user.setId(6L); //user.setName(&34;); user.setAge(22); // 注意:updateById 但是參數是一個 對象! int i = userMapper.updateById(user); System.out.println(i); }123456789101112
在面試過程中,我們經常會被問道樂觀鎖,悲觀鎖!這個其實非常簡單!
樂觀說 : 顧名思義十分樂觀,它總是認為不會出現問題,無論幹什麼不去上鎖!如果出現了問題, 再次更新值測試
悲觀鎖:顧名思義十分悲觀,它總是認為總是出現問題,無論幹什麼都會上鎖!再去操作!
樂觀鎖實現方式:
樂觀鎖:1、先查詢,獲得版本號 version = 1-- Aupdate user set name = &34;, version = version + 1 where id = 2 and version = 1-- B 線程搶先完成,這個時候 version = 2,會導致 A 修改失敗! update user set name = &34;, version = version + 1 where id = 2 and version = 112345
1、給資料庫中增加version欄位!
2、我們實體類加對應的欄位
@Version //樂觀鎖Version註解 private Integer version;12
3、註冊組件
// 掃描我們的 mapper 文件夾@MapperScan(&34;)@EnableTransactionManagement@Configuration // 配置類public class MyBatisPlusConfig { // 註冊樂觀鎖插件 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); }}123456789101112
4、測試一下!
// 測試樂觀鎖成功! @Test public void testOptimisticLocker(){ // 1、查詢用戶信息 User user = userMapper.selectById(1L); // 2、修改用戶信息 user.setName(&34;); user.setEmail(&34;); // 3、執行更新操作 userMapper.updateById(user); } // 測試樂觀鎖失敗!多線程下 @Test public void testOptimisticLocker2(){ // 線程 1 User user = userMapper.selectById(1L); user.setName(&34;); user.setEmail(&34;); // 模擬另外一個線程執行了插隊操作 User user2 = userMapper.selectById(1L); user2.setName(&34;); user2.setEmail(&34;); userMapper.updateById(user2); // 自旋鎖來多次嘗試提交! userMapper.updateById(user); // 如果沒有樂觀鎖就會覆蓋插隊線程的值! }12345678910111213141516171819202122232425262728293031
成功場景
失敗場景:
// 測試查詢 @Test public void testSelectById(){ User user = userMapper.selectById(1L); System.out.println(user); } // 測試批量查詢! where id in (1,2,3) 這樣的操作 @Test public void testSelectByBatchId(){ List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3)); users.forEach(System.out::println); } // 按條件查詢之一 使用map操作 name = &39; and age = 22 @Test public void testSelectByBatchIds(){ HashMap<String, Object> map = new HashMap<>(); // 自定義要查詢 map.put(&34;,&34;); map.put(&34;,22); List<User> users = userMapper.selectByMap(map); users.forEach(System.out::println); }123456789101112131415161718192021222324
分頁在網站使用的十分之多!
1、原始的 limit 進行分頁
2、pageHelper 第三方插件
3、MP 其實也內置了分頁插件!
如何使用!
1、配置攔截器組件即可 MyBatisPlusConfig
// 分頁插件 @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); }12345
2、直接使用Page對象即可!
@Test public void testPage(){ // 參數一:當前頁 // 參數二:頁面大小 // 使用了分頁插件之後,所有的分頁操作也變得簡單的! 3~4 Page<User> page = new Page<>(2,2); userMapper.selectPage(page,null); page.getRecords().forEach(System.out::println); System.out.println(page.getTotal()); // 等價 SELECT COUNT(1) FROM user }1234567891011
1、根據 id 刪除記錄
// 測試刪除 @Test public void testDeleteById(){ userMapper.deleteById(1L); } // 通過id批量刪除 id in (2,3) @Test public void testDeleteBatchId(){ userMapper.deleteBatchIds(Arrays.asList(2L,3L)); } // 通過map刪除 @Test public void testDeleteMap(){ HashMap<String, Object> map = new HashMap<>(); map.put(&34;,&34;); userMapper.deleteByMap(map); }12345678910111213141516171819
在工作中會遇到一些問題:邏輯刪除!
物理刪除 :從資料庫中直接移除
邏輯刪除 :在資料庫中沒有被移除,而是通過一個變量來讓它失效! deleted = 0 => deleted = 1,類似與跟記錄一個flag標識位
1、在數據表中增加一個 deleted 欄位
2、實體類中增加屬性
@TableLogic //邏輯刪除 private Integer deleted;12
3、配置!
// 邏輯刪除組件! @Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector(); }12345
application.properties
設置開發環境spring.profiles.active=dev12
2、配置開啟
* SQL執行效率插件 */ @Bean @Profile({&34;,&34;})// 設置 dev test 環境開啟,保證我們的效率 public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); //ms 設置sql執行的最大時間,如果超過了則不執行 performanceInterceptor.setMaxTime(20); // 開啟格式化 performanceInterceptor.setFormat(true); return performanceInterceptor; }123456789101112
故意設置小一些可以看到如下輸出:
// 測試查詢 @Test public void testSelectById(){ User user = userMapper.selectById(1L); System.out.println(user); }123456
條件構造器 十分重要:Wrapper 我們寫一些複雜的sql就可以使用它來替代!
說白了就是練習使用QueryWrapper下面到各種函數使用。
package com.sowhat;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.sowhat.mapper.UserMapper;import com.sowhat.pojo.User;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;import java.util.Map;@SpringBootTestpublic class WrapperTest { @Autowired private UserMapper userMapper; @Test void contextLoads() { // 查詢name不為空的用戶,並且郵箱不為空的用戶,年齡 >= 12 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper .isNotNull(&34;) .isNotNull(&34;) .ge(&34;, 12); userMapper.selectList(wrapper).forEach(System.out::println); // 和我們剛才學習的map對比一下 } @Test void test2() { // 查詢名字 sowhat QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq(&34;, &34;); //User user = userMapper.selectOne(wrapper); // 查詢一個數據,出現多個結果使用List 或者 Map //System.out.println(user); userMapper.selectList(wrapper).forEach(System.out::println); } @Test void test3() { // 查詢年齡在 20 ~ 30 歲之間的用戶 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.between(&34;, 20, 30); // 區間 Integer count = userMapper.selectCount(wrapper);// 查詢結果數 System.out.println(count); } // 模糊查詢 @Test void test4() { // 名字不包含e, QueryWrapper<User> wrapper = new QueryWrapper<>(); // likeRight t% // likeLeft %t wrapper .notLike(&34;, &34;) .likeRight(&34;, &34;); List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); } // 內查詢 @Test void test5() { QueryWrapper<User> wrapper = new QueryWrapper<>(); // id 在子查詢中查出來 ID in () wrapper.inSql(&34;, &34;); List<Object> objects = userMapper.selectObjs(wrapper); // 查詢到一切對象 objects.forEach(System.out::println); } // 排序 @Test void test6() { QueryWrapper<User> wrapper = new QueryWrapper<>(); // 通過id進行排序 wrapper.orderByAsc(&34;); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
核心:根據官網的條件構造器 實驗嘗試函數,每次嘗試後注意 最終執行的SQL是什麼樣的!
參考官網自動生成器開寫即可。
package com.sowhat;import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.generator.AutoGenerator;import com.baomidou.mybatisplus.generator.config.DataSourceConfig;import com.baomidou.mybatisplus.generator.config.GlobalConfig;import com.baomidou.mybatisplus.generator.config.PackageConfig;import com.baomidou.mybatisplus.generator.config.StrategyConfig;import com.baomidou.mybatisplus.generator.config.po.TableFill;import com.baomidou.mybatisplus.generator.config.rules.DateType;import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;import java.util.ArrayList;/** * 代碼自動生成器 */public class SowhatCode { public static void main(String[] args) { // 需要構建一個 代碼自動生成器 對象 AutoGenerator mpg = new AutoGenerator(); // 配置策略// 1、全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty(&34;); // 獲得用戶當前目錄 gc.setOutputDir(projectPath + &34;); //代碼生成目錄 gc.setAuthor(&34;); gc.setOpen(false); // 是否打開文件夾 運行後 gc.setFileOverride(false); // 是否覆蓋 gc.setServiceName(&34;); // 去Service的I前綴 gc.setIdType(IdType.ID_WORKER); gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true); mpg.setGlobalConfig(gc);//2、設置數據源 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl(&34;); dsc.setDriverName(&34;); dsc.setUsername(&34;); dsc.setPassword(&34;); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc);//3、包的配置 PackageConfig pc = new PackageConfig(); pc.setModuleName(&34;); pc.setParent(&34;); pc.setEntity(&34;); pc.setMapper(&34;); pc.setService(&34;); pc.setController(&34;); mpg.setPackageInfo(pc);//4、策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setInclude(&34;, &34;, &34;, &34;, &34;, &34;); // 設置要映射的表名 strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); // 自動lombok; strategy.setLogicDeleteFieldName(&34;); // 邏輯刪除// 自動填充配置 TableFill gmtCreate = new TableFill(&34;, FieldFill.INSERT); TableFill gmtModified = new TableFill(&34;, FieldFill.INSERT_UPDATE); ArrayList<TableFill> tableFills = new ArrayList<>(); tableFills.add(gmtCreate); tableFills.add(gmtModified); strategy.setTableFillList(tableFills);// 樂觀鎖 strategy.setVersionFieldName(&34;); strategy.setRestControllerStyle(true); // 駝峰命名 strategy.setControllerMappingHyphenStyle(true); // localhost:8080/hello_id_2 mpg.setStrategy(strategy); mpg.execute(); //執行 }}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677