1. 簡介
官網(wǎng):https://baomidou.com/pages/24112f/
簡稱為MP承边,是一個Mybatis的增強(qiáng)工具阐污,在Mybatis的基礎(chǔ)上只做增強(qiáng)不做改變,為簡化開發(fā)浪谴,提高效率而生归露。
對比mybatis雕什,MP減少了sql的書寫裕照,之前的mybatis是需要將sql語句寫在xml文件中,涉及的操作比較繁瑣在刺,而MP基本不用書寫簡單SQL逆害,開發(fā)起來比較方便,而且MP擁有的特性也很受用蚣驼。
2. 特性
-
無侵入
只做增強(qiáng)不做改變魄幕,引入它不會對現(xiàn)有工程產(chǎn)生影響,如絲般順滑
-
損耗小
啟動即會自動注入基本CRUD颖杏,性能基本無損耗纯陨,直接面向?qū)ο蟛僮?/p>
-
強(qiáng)大的CRUD操作
內(nèi)置通用Mapper,通用Service留储,僅僅通過少量配置即可實(shí)現(xiàn)單表大部分CRUD操作队丝,更有強(qiáng)大的條件構(gòu)造器,滿足各類使用需求
-
支持Lambda形式調(diào)用
通過Lambda表達(dá)式欲鹏,方便的編寫各類查詢條件,無需擔(dān)心字段寫錯
-
支持主鍵自動生成
支持多達(dá)4種主鍵策略(內(nèi)含分布式唯一ID生成器- Sequence)臭墨,可自由配置赔嚎,完美解決主鍵問題
-
支持ActiveRecord模式
支持ActiveRecord形式調(diào)用,實(shí)體類只需繼承Model類即可進(jìn)行強(qiáng)大的CRUD操作
-
支持自定義全局通用操作
支持全局通用方法注入
-
內(nèi)置代碼生成器
采用代碼或者M(jìn)aven插件可快速生成Mapper胧弛,Model尤误,Service,Controller層代碼结缚,支持模板引擎损晤,更有超多自定義配置
-
內(nèi)置分頁插件
基于Mybatis物理分頁,開發(fā)者無需關(guān)心具體操作红竭,配置好插件后尤勋,寫分頁等同于普通List查詢
-
分頁插件支持多種數(shù)據(jù)庫
支持MySQL,Oracle茵宪,DB2最冰,H2,HSQL稀火,SQLite暖哨,Postgre,SQLServer等多種數(shù)據(jù)庫
-
內(nèi)置性能分析插件
可輸出SQL語句以及其執(zhí)行時間凰狞,建議開發(fā)測試時啟用該功能篇裁,能快速揪出慢查詢
-
內(nèi)置全局?jǐn)r截插件
提供全表delete沛慢,update操作只能分析阻斷,也可以自定義攔截規(guī)則达布,預(yù)防誤操作
3. 快速開始
-
首先創(chuàng)建數(shù)據(jù)表团甲,假設(shè)有一張用戶表user
CREATE TABLE `user` ( `id` bigint(20) NOT NULL COMMENT '主鍵ID', `name` varchar(32) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年齡', `email` varchar(32) DEFAULT NULL COMMENT '郵箱', PRIMARY KEY(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
這里id字段的類型為bigint,是因?yàn)閙ybatis默認(rèn)的主鍵是雪花算法計(jì)算出來的往枣,比較長超出范圍伐庭,需要用bigint
-
插入一些預(yù)置的數(shù)據(jù)
INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com');
-
創(chuàng)建Springboot工程,引入依賴
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.13</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency>
-
編寫配置(數(shù)據(jù)庫使用MySQL分冈,數(shù)據(jù)源使用Druid)
spring: # 配置數(shù)據(jù)源信息 datasource: # 配置數(shù)據(jù)源類型 type: com.alibaba.druid.pool.DruidDataSource # 配置連接數(shù)據(jù)庫信息 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatisplus?characterEncoding=utf-8&useSSL=false username: root password: root
這里有個要注意的:
-
驅(qū)動類(
driver-class-name
)的值- Springboot 2.0或使用的數(shù)據(jù)庫是5.X版本圾另,
driver-class-name
的值為:com.mysql.jdbc.Driver
- Springboot 2.1及以上或數(shù)據(jù)庫是8.x版本,
driver-class-name
的值為:com.mysql.cj.jdbc.Driver
- Springboot 2.0或使用的數(shù)據(jù)庫是5.X版本圾另,
-
連接地址(
url
)的值-
mysql5.x版本的url
jdbc:mysql://localhost:3306/mybatisplus?characterEncoding=utf8&useSSL=false
-
mysql8.x版本的url
jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
mysql8多了一個時區(qū)的設(shè)置
-
-
-
添加實(shí)體類User
@Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
這里的字段類型雕沉,盡量用包裝類集乔。
在插入數(shù)據(jù)時,由于ID是自動生成的坡椒,在設(shè)置數(shù)據(jù)時扰路,如果用了基本數(shù)據(jù)類型long,就得設(shè)置為0倔叼,就會變成了插入數(shù)據(jù)的ID了汗唱,而包裝類,可以設(shè)置為null丈攒。(個人理解)
-
添加對應(yīng)UserMapper
public interface UserMapper extends BaseMapper<User> { }
這里UserMapper繼承了BaseMapper哩罪,這個BaseMapper是MybatisPlus提供的模板Mapper,其中包含了基本的CRUD方法巡验,泛型就是要操作的實(shí)體類型际插。
Mapper 繼承該接口后,無需編寫 mapper.xml 文件显设,即可獲得CRUD功能
-
啟動類加上
MapperScan
@SpringBootApplication @MapperScan("com.zzy.mybatisplus_test.mapper") public class MybatisplusTestApplication { public static void main(String[] args) { SpringApplication.run(MybatisplusTestApplication.class, args); } }
-
編寫測試方法
@Autowired private UserMapper userMapper; @Test public void testSelectList() { userMapper.selectList(null).forEach(System.out::println); }
UserMapper中的selectList方法需要一個參數(shù)框弛,是MP內(nèi)置的條件封裝器,這里填寫null捕捂,就是指不需要條件瑟枫,選擇所有
-
測試結(jié)果
控制臺會輸出前面插入的數(shù)據(jù)
User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com)
4. MP的CRUD操作
在操作之前,我們可以配置MP的日志輸出指攒,能在控制臺顯示MP執(zhí)行的SQL語句力奋,方便Debug與學(xué)習(xí)。
# 配置Mybatis日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4.1 使用BaseMapper
在MP中幽七,基本的CRUD在內(nèi)置的BaseMapper中都已經(jīng)得到了實(shí)現(xiàn)了景殷,我們可以直接使用。
-
Insert
/** * 插入一條記錄 * * @param entity 實(shí)體對象 */ int insert(T entity);
@Test public void testInsert() { User user = new User(null, "zhangsan", 23, "zhangsan@qq.com"); int result = userMapper.insert(user); System.out.println("result: " + result); System.out.println("用戶id為:" + user.getId()); }
輸出數(shù)據(jù):
==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
==> Parameters: 1546431457684246529(Long), zhangsan(String), 23(Integer), zhangsan@qq.com(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2fd9fb34]
result: 1
用戶id為:1546431457684246529用戶id是MP默認(rèn)使用雪花算法生成的。
-
Delete
// 根據(jù) entity 條件猿挚,刪除記錄 // wrapper: 實(shí)體對象封裝操作類咐旧,可以為null int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); // 刪除(根據(jù)ID 批量刪除) // idList:主鍵ID列表,不能為null以及empty int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 根據(jù) ID 刪除 int deleteById(Serializable id); // 根據(jù) columnMap 條件绩蜻,刪除記錄 // columnMap: 表字段map對象 int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 通過Id刪除記錄 @Test public void testDeleteById() { int result = userMapper.deleteById(1546431457684246529L); System.out.println("result = " + result); } // 輸出結(jié)果 ==> Preparing: DELETE FROM user WHERE id=? ==> Parameters: 1546431457684246529(Long) <== Updates: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@687389a6] result = 1
// 根據(jù)ID批量刪除 @Test public void testDeleteByBatchId() { List<Long> list = Arrays.asList(1L, 2L, 3L); int result = userMapper.deleteBatchIds(list); System.out.println("result = " + result); } // 輸出結(jié)果 ==> Preparing: DELETE FROM user WHERE id IN ( ? , ? , ? ) ==> Parameters: 1(Long), 2(Long), 3(Long) <== Updates: 3 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4ffced4e] result = 3
// 通過map條件刪除 @Test public void testDeleteByMap() { Map<String, Object> map = new HashMap<>(); map.put("age", 24); map.put("name", "Billie"); int result = userMapper.deleteByMap(map); System.out.println("result = " + result); } // 輸出結(jié)果 ==> Preparing: DELETE FROM user WHERE name = ? AND age = ? ==> Parameters: Billie(String), 24(Integer) <== Updates: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6094de13] result = 1
-
Update
// 根據(jù) whereWrapper 條件铣墨,更新記錄 int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper); // 根據(jù) ID 修改 int updateById(@Param(Constants.ENTITY) T entity);
前面演示了刪除的方法,把數(shù)據(jù)幾乎都刪光了办绝,這里重新將原始數(shù)據(jù)添加到數(shù)據(jù)庫伊约,再測試。
// 通過Id修改 @Test public void testUpdateById() { User user = new User(3L, "zhangsan", 20, null); int result = userMapper.updateById(user); System.out.println("result = " + result); } // 輸出結(jié)果 ==> Preparing: UPDATE user SET name=?, age=? WHERE id=? ==> Parameters: zhangsan(String), 20(Integer), 3(Long) <== Updates: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@bbb6f0] result = 1 // 注意孕蝉,email設(shè)置為null屡律,在執(zhí)行時并沒有去修改email的值的。
-
Select
// 根據(jù) ID 查詢 T selectById(Serializable id); // 根據(jù) entity 條件降淮,查詢一條記錄 T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查詢(根據(jù)ID 批量查詢) List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 根據(jù) entity 條件超埋,查詢?nèi)坑涗?List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查詢(根據(jù) columnMap 條件) List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); // 根據(jù) Wrapper 條件,查詢?nèi)坑涗?List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根據(jù) Wrapper 條件佳鳖,查詢?nèi)坑涗浕襞埂W⒁猓?只返回第一個字段的值 List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根據(jù) entity 條件,查詢?nèi)坑涗洠ú⒎摚?IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根據(jù) Wrapper 條件系吩,查詢?nèi)坑涗洠ú⒎摚?IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根據(jù) Wrapper 條件来庭,查詢總記錄數(shù) Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根據(jù)Id查詢 @Test public void testSelectById() { User user = userMapper.selectById(2L); System.out.println("user = " + user); } // 輸出結(jié)果 ==> Preparing: SELECT id,name,age,email FROM user WHERE id=? ==> Parameters: 2(Long) <== Columns: id, name, age, email <== Row: 2, Jack, 20, test2@baomidou.com <== Total: 1 user = User(id=2, name=Jack, age=20, email=test2@baomidou.com)
// 根據(jù)多個Id批量查詢 @Test public void testSelectByBatchId() { List<Long> list = Arrays.asList(4L, 5L); List<User> users = userMapper.selectBatchIds(list); System.out.println("users = " + users); } // 輸出結(jié)果 ==> Preparing: SELECT id,name,age,email FROM user WHERE id IN ( ? , ? ) ==> Parameters: 4(Long), 5(Long) <== Columns: id, name, age, email <== Row: 4, Sandy, 21, test4@baomidou.com <== Row: 5, Billie, 24, test5@baomidou.com <== Total: 2 users = [User(id=4, name=Sandy, age=21, email=test4@baomidou.com), User(id=5, name=Billie, age=24, email=test5@baomidou.com)]
// 通過map條件查詢 @Test public void testSelectByMap() { Map<String, Object> map = new HashMap<>(); map.put("age", 21); List<User> users = userMapper.selectByMap(map); System.out.println(users); } // 輸出結(jié)果 ==> Preparing: SELECT id,name,age,email FROM user WHERE age = ? ==> Parameters: 21(Integer) <== Columns: id, name, age, email <== Row: 4, Sandy, 21, test4@baomidou.com <== Total: 1 [User(id=4, name=Sandy, age=21, email=test4@baomidou.com)]
4.2 使用通用Service
說明:
- 通用Service CRUD封裝了
IService
接口,進(jìn)一步封裝CRUD采用get 查詢單行
,remove 刪除
,list 查詢集合
,page 分頁
前綴命名的方式區(qū)分Mapper
層穿挨,避免混淆 - 泛型T為任意實(shí)體對象
- 建議如果存在自定義通用Service方法的可能月弛,請創(chuàng)建自己的
IBaseService
繼承MP提供的基類
-
IService
MP中有一個接口
IService
和其實(shí)現(xiàn)類ServiceImpl
,封裝了常見的業(yè)務(wù)層邏輯(具體翻看源碼)Save
// 插入一條記錄(選擇字段,策略插入) boolean save(T entity); // 插入(批量) boolean saveBatch(Collection<T> entityList); // 插入(批量) boolean saveBatch(Collection<T> entityList, int batchSize);
SaveOrUpdate
// TableId 注解存在更新記錄絮蒿,否插入一條記錄 boolean saveOrUpdate(T entity); // 根據(jù)updateWrapper嘗試更新,否繼續(xù)執(zhí)行saveOrUpdate(T)方法 boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper); // 批量修改插入 boolean saveOrUpdateBatch(Collection<T> entityList); // 批量修改插入 boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
Remove
// 根據(jù) entity 條件叁鉴,刪除記錄 boolean remove(Wrapper<T> queryWrapper); // 根據(jù) ID 刪除 boolean removeById(Serializable id); // 根據(jù) columnMap 條件土涝,刪除記錄 boolean removeByMap(Map<String, Object> columnMap); // 刪除(根據(jù)ID 批量刪除) boolean removeByIds(Collection<? extends Serializable> idList);
Update
// 根據(jù) UpdateWrapper 條件,更新記錄 需要設(shè)置sqlset boolean update(Wrapper<T> updateWrapper); // 根據(jù) whereWrapper 條件幌墓,更新記錄 boolean update(T updateEntity, Wrapper<T> whereWrapper); // 根據(jù) ID 選擇修改 boolean updateById(T entity); // 根據(jù)ID 批量更新 boolean updateBatchById(Collection<T> entityList); // 根據(jù)ID 批量更新 boolean updateBatchById(Collection<T> entityList, int batchSize);
Get
// 根據(jù) ID 查詢 T getById(Serializable id); // 根據(jù) Wrapper但壮,查詢一條記錄。結(jié)果集常侣,如果是多個會拋出異常蜡饵,隨機(jī)取一條加上限制條件 wrapper.last("LIMIT 1") T getOne(Wrapper<T> queryWrapper); // 根據(jù) Wrapper,查詢一條記錄 T getOne(Wrapper<T> queryWrapper, boolean throwEx); // 根據(jù) Wrapper胳施,查詢一條記錄 Map<String, Object> getMap(Wrapper<T> queryWrapper); // 根據(jù) Wrapper溯祸,查詢一條記錄 <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
List
// 查詢所有 List<T> list(); // 查詢列表 List<T> list(Wrapper<T> queryWrapper); // 查詢(根據(jù)ID 批量查詢) Collection<T> listByIds(Collection<? extends Serializable> idList); // 查詢(根據(jù) columnMap 條件) Collection<T> listByMap(Map<String, Object> columnMap); // 查詢所有列表 List<Map<String, Object>> listMaps(); // 查詢列表 List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper); // 查詢?nèi)坑涗?List<Object> listObjs(); // 查詢?nèi)坑涗?<V> List<V> listObjs(Function<? super Object, V> mapper); // 根據(jù) Wrapper 條件,查詢?nèi)坑涗?List<Object> listObjs(Wrapper<T> queryWrapper); // 根據(jù) Wrapper 條件,查詢?nèi)坑涗?<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
Page
// 無條件分頁查詢 IPage<T> page(IPage<T> page); // 條件分頁查詢 IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper); // 無條件分頁查詢 IPage<Map<String, Object>> pageMaps(IPage<T> page); // 條件分頁查詢 IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
-
創(chuàng)建Service接口和實(shí)現(xiàn)類
public interface UserService extends IService<User> { } @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }
-
測試查詢記錄數(shù)
@Test public void testGetCount() { long count = userService.count(); System.out.println("count = " + count); }
-
測試批量插入
@Test public void testSaveBatch() { // SQL 長度有限制焦辅,海量數(shù)據(jù)插入單條SQL無法實(shí)行 // 因此MP將批量插入放在了通用Service中實(shí)現(xiàn)博杖,而不是通用Mapper ArrayList<User> users = new ArrayList<>(); for (int i = 0; i < 5; i++ ){ User user = new User(); user.setName("abc" + 1); user.setAge(20); users.add(user); } // INSERT INTO t_user1 ( name, age ) VALUES ( ?, ? ) userService.saveBatch(users); }
4.3 條件構(gòu)造器
在BaseMapper類中的方法,大多數(shù)方法中都有帶`Wrapper`類型的形參筷登,這個`Wrapper`就是條件構(gòu)造器剃根,能夠針對SQL語句設(shè)置不同的條件。**如果沒有條件前方,則可以將該形參賦值為null狈醉,即為查詢(刪除/修改)所有數(shù)據(jù)**
Wrapper的關(guān)系樹:
-
Wrapper
:條件構(gòu)造抽閑類,最頂端父類-
AbstractWrapper
:用于查詢條件封裝惠险,生成sql的where條件-
QueryWrapper
:查詢條件封裝 -
UpdateWrapper
: 更新條件封裝 -
AbstractLambdaWrapper
:使用Lambda語法的條件封裝-
LambdaQueryWrapper
:用于Lambda語法使用的查詢Wrapper -
LambdaUpdateWrapper
:用于Lambda語法使用的更新Wrapper
-
-
-
-
QueryWrapper
-
組裝查詢條件
@Test public void test01() { // 查詢用戶名包含a,年齡在20-30之間苗傅,并且郵箱不為null的用戶信息 QueryWrapper<User> wrapper = new QueryWrapper<>(); // 構(gòu)造的sql:SELECT id,name,age,email FROM user WHERE (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL) wrapper.like("name", "a") .between("age", 20, 30) .isNotNull("email"); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } // 輸出結(jié)果 User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=zhangsan, age=20, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
-
組裝排序條件
@Test public void test02() { // 按年齡降序查詢用戶,如果年齡相同則按id升序排列 QueryWrapper<User> wrapper = new QueryWrapper<>(); // 構(gòu)造的sql:SELECT id,name,age,email FROM user ORDER BY age DESC,id ASC wrapper.orderByDesc("age") .orderByAsc("id"); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } // 輸出結(jié)果 User(id=5, name=Billie, age=24, email=test5@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=zhangsan, age=20, email=test3@baomidou.com) User(id=1, name=Jone, age=18, email=test1@baomidou.com)
-
組裝刪除條件
@Test public void test03() { // 刪除email為空的用戶 QueryWrapper<User> wrapper = new QueryWrapper<>(); // 構(gòu)造的sql:DELETE FROM user WHERE (email IS NULL) wrapper.isNull("email"); int result = userMapper.delete(wrapper); System.out.println("result = " + result); } // 輸出結(jié)果 result = 0莺匠,因?yàn)闆]有用戶email是null的
-
條件的優(yōu)先級
@Test public void test04() { // 將(年齡大于20并且用戶名中包含a)或者郵箱為null的用戶信息修改 QueryWrapper<User> wrapper = new QueryWrapper<>(); // 構(gòu)造的sql:UPDATE user SET age=?, email=? WHERE (age >= ? AND name LIKE ? OR email IS NULL) wrapper.ge("age", 20) .like("name", "a") .or() .isNull("email"); User user = new User(); user.setAge(33); user.setEmail("user@qq.com"); int update = userMapper.update(user, wrapper); System.out.println("update = " + update); } // 輸出結(jié)果 update = 3
@Test public void test05() { // 將用戶名中包含a并且(年齡大于20或者郵箱為null)的用戶信息修改 QueryWrapper<User> wrapper = new QueryWrapper<>(); // 構(gòu)造的SQL:UPDATE user SET age=?, email=? WHERE (name LIKE ? AND (age >= ? OR email IS NULL)) wrapper.like("name", "a") .and(i -> i.ge("age", 20).or().isNull("email")); User user = new User(); user.setAge(10); user.setEmail("abc@qq.com"); int update = userMapper.update(user, wrapper); System.out.println("update = " + update); }
主動調(diào)用
or
表示接著的下一個方法不是用and
連接(默認(rèn)是使用and
連接的) -
組裝select子句
@Test public void test06() { // 查詢用戶信息的name金吗,age字段 QueryWrapper<User> wrapper = new QueryWrapper<>(); // 構(gòu)造的sql:SELECT name,age FROM user wrapper.select("name", "age"); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } // 輸出結(jié)果 User(id=null, name=Jone, age=18, email=null) User(id=null, name=Jack, age=10, email=null) User(id=null, name=zhangsan, age=10, email=null) User(id=null, name=Sandy, age=10, email=null) User(id=null, name=Billie, age=24, email=null)
-
實(shí)現(xiàn)子查詢
@Test public void test07() { // 查詢id小于等于3的用戶信息 QueryWrapper<User> wrapper = new QueryWrapper<>(); // 構(gòu)建的sql:SELECT id,name,age,email FROM user WHERE (id IN (select id from user where id <= 3)) wrapper.inSql("id", "select id from user where id <= 3"); List<User> list = userMapper.selectList(wrapper); list.forEach(System.out::println); } // 輸出結(jié)果 User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=10, email=abc@qq.com) User(id=3, name=zhangsan, age=10, email=abc@qq.com)
這里只是測試效果,實(shí)際應(yīng)該不會這樣select id
-
-
LambdaQueryWrapper
上面使用
QueryWrapper
中趣竣,我們查詢時使用的是數(shù)據(jù)庫列名字符串摇庙,容易出現(xiàn)寫錯或與類的屬性名不一致的導(dǎo)致寫錯,MP提供了LambdaQueryWrapper遥缕,可以通過獲取Lambda數(shù)據(jù)庫列名卫袒。@Test public void test08() { // 查詢用戶名包含a,年齡在10-20之間,并且郵箱不為null的用戶信息 LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); // SELECT id,name,age,email FROM user WHERE (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL) wrapper.like(User::getName, "a") .between(User::getAge, 10, 20) .isNotNull(User::getEmail); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
使用LambdaQueryWrapper后单匣,不用直接寫列名字符串形式夕凝,可以通過類獲取
-
UpdateWrapper
前面的測試?yán)又幸灿杏肣ueryWrapper實(shí)現(xiàn)update的,使用
UpdateWrapper
可以使用set
方法直接設(shè)置户秤,不需要創(chuàng)建User對象,如果創(chuàng)建了User對象码秉,并且設(shè)置屬性,傳遞給了update方法鸡号,那么設(shè)置列會加入到sql中转砖。@Test public void test09() { // 將(年齡大于20或郵箱為null)并且用戶名中包含有a的用戶信息修改 UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); // UPDATE user SET age=?,email=? WHERE (name LIKE ? AND (age >= ? OR email IS NULL)) updateWrapper.set("age", 18) .set("email", "user@qq.com") .like("name", "a") .and(i -> i.ge("age", 20).or().isNull("email")); int update = userMapper.update(null, updateWrapper); System.out.println("update = " + update); }
-
LambdaUpdateWrapper
將上面的測試?yán)佑肔ambda的方式來編寫
@Test public void test09() { // 將(年齡大于20或郵箱為null)并且用戶名中包含有a的用戶信息修改 LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.set(User::getAge, 38) .set(User::getEmail, "user@qq.com") .like(User::getName, "a") .and(i -> i.ge(User::getAge, 10).or().isNull(User::getEmail)); User user = new User(); user.setName("Billiea"); int update = userMapper.update(user, updateWrapper); System.out.println("update = " + update); }
-
總結(jié)
-
QueryWrapper
、QueryChainWrapper
只能指定需要的數(shù)據(jù)庫列名 -
LambdaQueryWrapper
鲸伴、LambdaQueryChainWrapper
可以通過Lambda獲取數(shù)據(jù)庫列名 -
QueryWrapper
府蔗、LambdaQueryWrapper
不能使用鏈?zhǔn)讲樵兊姆绞剑仨毥柚鶥aseMapper來執(zhí)行 -
QueryChainWrapper
汞窗、LambdaQueryChainWrapper
可以使用鏈?zhǔn)讲樵兊姆绞叫粘啵鏻ist(),one()
-
4.4 condition
在真正開發(fā)過程中,組裝條件時仲吏,有些數(shù)據(jù)是來源于用戶輸入的不铆,是可選的蝌焚,因此在組裝條件之前是需要先判斷條件是否成立的,成立才組裝條件到SQL執(zhí)行狂男。
MP在條件構(gòu)造器的方法中提供了帶condition參數(shù)的方法
未使用帶condition參數(shù)的方法時的操作:
@Test
public void test10() {
// 定義查詢條件综看,有可能為null,假設(shè)輸入數(shù)據(jù)如下
String name = null;
Integer ageBegin = 10;
Integer ageEnd = 24;
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(name)) {
queryWrapper.like(User::getName, "a");
}
if (ageBegin != null) {
queryWrapper.ge(User::getAge, ageBegin);
}
if (ageEnd != null) {
queryWrapper.le(User::getAge, ageEnd);
}
// SELECT id,name,age,email FROM user WHERE (age >= ? AND age <= ?)
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
使用帶condition參數(shù)的方法:
@Test
public void test11() {
// 定義查詢條件,有可能為null,假設(shè)輸入數(shù)據(jù)如下
String name = null;
Integer ageBegin = 10;
Integer ageEnd = 24;
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(name), User::getName, name)
.ge(ageBegin != null, User::getAge, ageBegin)
.le(ageEnd != null, User::getAge, ageEnd);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
5. 常用注解
-
@TableName
在以上的測試中岖食,我們使用MP進(jìn)行CRUD時红碑,并沒有指定要操作的表,只是在UserMapper接口繼承BaseMapper時泡垃,設(shè)置了泛型為User析珊,但實(shí)際操作的表是User表。
因此可以得出結(jié)論蔑穴,MP在確定操作的表時忠寻,是由BaseMapper的泛型決定的,即實(shí)體類型決定存和,而且默認(rèn)操作的表名與實(shí)體類的類名一致奕剃。
@TableName
的作用是標(biāo)識實(shí)體類對應(yīng)的表,作用在實(shí)體類上捐腿。可以做如下測試:將數(shù)據(jù)庫的表user改名為t_user纵朋,運(yùn)行前面的測試程序,報錯如下
org.springframework.jdbc.BadSqlGrammarException: ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'mybatisplus.user' doesn't exist ### The error may exist in com/zzy/mybatisplus_test/mapper/UserMapper.java (best guess) ### The error may involve defaultParameterMap ### The error occurred while setting parameters ### SQL: SELECT id,name,age,email FROM user WHERE (age >= ? AND age <= ?) ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'mybatisplus.user' doesn't exist ; bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'mybatisplus.user' doesn't exist
解決方法:因?yàn)楫?dāng)前實(shí)體類為User茄袖,數(shù)據(jù)庫表為t_user,二者名字不匹配操软,因此報錯,可以在User類上添加注解
@TableName("t_user")
標(biāo)識User類與表t_user匹配@Data @NoArgsConstructor @AllArgsConstructor @TableName("t_user") public class User { private Long id; private String name; private Integer age; private String email; }
上面我們只是改了一個表宪祥,如果所有的實(shí)體類對應(yīng)的表都有固定的前綴聂薪,比如t_,我們總不能手動一個個的添加@TableName注解吧蝗羊,那得累死
為了解決這種情況藏澳,我們可以通過MP全局配置,為實(shí)體類所對應(yīng)的表名設(shè)置默認(rèn)的前綴:
# 配置Mybatis日志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: # 配置MP操作表的默認(rèn)前綴 table-prefix: t_
-
@TableId
經(jīng)過以上測試耀找,MP在實(shí)現(xiàn)CRUD時翔悠,會默認(rèn)將id作為主鍵列,并在插入數(shù)據(jù)時涯呻,默認(rèn)基于雪花算法的策略生成id
問題:如果實(shí)體類和表中表示主鍵的不是id凉驻,而是其他字段腻要,比如uid复罐,那MP還會自動識別uid為主鍵列嗎?
測試:將實(shí)體類中屬性id改為uid雄家,表中字段id也改為uid效诅,測試添加功能
### SQL: INSERT INTO t_user1 ( name, age, email ) VALUES ( ?, ?, ? ) ### Cause: java.sql.SQLException: Field 'uid' doesn't have a default value ; Field 'uid' doesn't have a default value; nested exception is java.sql.SQLException: Field 'uid' doesn't have a default value
解決方法:通過
@TableId
將uid屬性標(biāo)識為主鍵public class User { @TableId private Long uid; private String name; private Integer age; private String email; }
若實(shí)體類中主鍵對應(yīng)的屬性為id,而表中表示主鍵的字段為uid,此時如果只在屬性上添加注解
@TableId
乱投,則會拋出異常Unknown column 'id' in 'field list'
咽笼,也就是MP仍然會將id作為表的主鍵操作,而表中表示主鍵的是字段uid戚炫。此時就需要通過@TableId的value屬性了剑刑,指定表中主鍵字段:
@TableId("uid")
或@TableId(value="uid")
@TableId的type屬性
常用的主鍵策略:
-
IdType.ASSIGN_ID
(默認(rèn))基于雪花算法的策略生成數(shù)據(jù)id,與數(shù)據(jù)庫id是否設(shè)置自增無關(guān)
-
IdType.AUTO
使用數(shù)據(jù)庫的自增策略双肤,注意施掏,該類型請確保數(shù)據(jù)庫設(shè)置了id自增,否則無效
配置全局主鍵策略:
# 配置Mybatis日志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: # 配置MP操作表的默認(rèn)前綴 table-prefix: t_ # 配置MP的主鍵策略 id-type: auto
-
-
@TableField
經(jīng)過以上的測試茅糜,我們可以發(fā)現(xiàn)七芭,MP在執(zhí)行SQL語句時,要保證實(shí)體類中的屬性名和表中的字段名一致
如果實(shí)體類中的屬性名和字段名不一致蔑赘,會產(chǎn)生什么問題狸驳?
-
如果實(shí)體類中屬性使用的是駝峰命名風(fēng)格,而表中字段使用的是下劃線命名風(fēng)格缩赛,比如實(shí)體類屬性為userName,表中字段為user_name.
這種情況MP會自動將下劃線命名的風(fēng)格轉(zhuǎn)化為駝峰命名風(fēng)格
-
如果實(shí)體類中的屬性和表中的字段不滿足第一個情況耙箍,比如實(shí)體類屬性是name,表中字段為username
這種情況需要在實(shí)體類上添加注解
@TableField("username")
設(shè)置屬性對應(yīng)的字段名
-
-
@TableLogic
邏輯刪除:假刪除峦筒,就是新增一個字段用來表示刪除狀態(tài)究西,在數(shù)據(jù)庫中仍舊可以看到這條記錄,不是真正的刪除物喷,但是對于MP來說像是刪除了的卤材,無法使用CRUD再操作這條數(shù)據(jù)。
實(shí)現(xiàn)邏輯刪除:
- 數(shù)據(jù)庫中創(chuàng)建邏輯刪除狀態(tài)列峦失,設(shè)置默認(rèn)值為0
- 實(shí)體類中添加邏輯刪除屬性扇丛,添加注解@TableLogic
6.插件
-
分頁插件
MP自帶分頁插件,只需要簡單配置就可以實(shí)現(xiàn)分頁功能了尉辑。
-
添加配置類
@Configuration @MapperScan("com.zzy.mybatisplus_test.mapper") // 可以將主類中Mapper的注解移到這里 public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); // 數(shù)據(jù)類型為DbType.MYSQL mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return mybatisPlusInterceptor; } }
-
測試
@Test public void testPage() { // 設(shè)置分頁參數(shù)帆精,通過分頁參數(shù)來確定數(shù)據(jù) Page<User> page = new Page<User>(1, 3); userMapper.selectPage(page, null); // 獲取分頁數(shù)據(jù) List<User> list = page.getRecords(); list.forEach(System.out::println); System.out.println("當(dāng)前頁: " + page.getCurrent()); System.out.println("每頁顯示的條數(shù): " + page.getSize()); System.out.println("總記錄數(shù): " + page.getTotal()); System.out.println("總頁數(shù): " + page.getPages()); System.out.println("是否有上一頁:" + page.hasPrevious()); System.out.println("是否有下一頁: " + page.hasNext()); }
使用XML自定義分頁
-
UserMapper中定義接口方法
/** * 根據(jù)年齡查詢用戶列表,分頁顯示 * @param page 分頁對象隧魄,xml中可以從里面進(jìn)行取值卓练,傳遞參數(shù)Page即自動分頁,必須放在第一位 * @param age 年齡 * @return */ Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
-
UserMapper.xml中編寫SQL
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zzy.mybatisplus_test.mapper.UserMapper"> <sql id="BaseColumns">uid, name, age, email</sql> <select id="selectPageVo" resultType="User"> select <include refid="BaseColumns"/> from t_user1 where age > #{age} </select> </mapper>
-
測試
@Test public void testSelectPageVo() { // 設(shè)置分頁參數(shù) Page<User> page = new Page<>(1, 3); userMapper.selectPageVo(page, 20); // 獲取分頁數(shù)據(jù) List<User> list = page.getRecords(); list.forEach(System.out::println); System.out.println("當(dāng)前頁: " + page.getCurrent()); System.out.println("每頁顯示的條數(shù): " + page.getSize()); System.out.println("總記錄數(shù): " + page.getTotal()); System.out.println("總頁數(shù): " + page.getPages()); System.out.println("是否有上一頁:" + page.hasPrevious()); System.out.println("是否有下一頁: " + page.hasNext()); }
-
-
樂觀鎖插件
悲觀鎖與樂觀鎖的理解可以看看這個文章:https://my.oschina.net/hanchao/blog/3057429
-
樂觀鎖實(shí)現(xiàn)方式:(當(dāng)要更新一條記錄的時候购啄,希望這條記錄沒有被別人更新)
取出記錄時襟企,獲取當(dāng)前version
更新時,帶上這個version
執(zhí)行更新時狮含,set version = newVersion where version = oldVersion
如果version不對顽悼,就更新失敗
-
模擬沖突場景
有一件商品成本80元曼振,售價100元,老板先讓小李將商品價格提高50元蔚龙,小李沒有及時操作冰评,一小時后,老板覺得商品售價150元太高木羹,就又讓小王去將價格降低30元甲雅。
老板的想法是100 -> 150 -> 120
但是這時候小李和小王同時操作系統(tǒng),取出的商品價格都是100元坑填,然后小李加了50元务荆,將150存到了數(shù)據(jù)庫;小王減掉30元穷遂,將70存入數(shù)據(jù)庫.
如果沒有鎖函匕,小李的操作就會被小王覆蓋了,最終可能價格變成了70元蚪黑,導(dǎo)致老板虧錢
新增一張商品表
CREATE TABLE t_product ( id BIGINT(20) NOT NULL COMMENT '主鍵ID', NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名稱', price INT(11) DEFAULT 0 COMMENT '價格', VERSION INT(11) DEFAULT 0 COMMENT '樂觀鎖版本號', PRIMARY KEY (id) ); # 添加數(shù)據(jù) INSERT INTO t_product (id, NAME, price) VALUES (1, '筆記本', 100);
測試(未使用樂觀鎖):
@Test public void testConCurrentUpdate() { // 1. 小李 Product p1 = productMapper.selectById(1L); System.out.println("小李取出的價格:" + p1.getPrice()); // 2. 小王 Product p2 = productMapper.selectById(1L); System.out.println("小王取出的價格: " + p2.getPrice()); // 3. 小李將價格增加50元盅惜,存入了數(shù)據(jù)庫 p1.setPrice(p1.getPrice() + 50); int ret1 = productMapper.updateById(p1); System.out.println("小李修改結(jié)果:" + ret1); // 4. 小王將價格減了30元,存入了數(shù)據(jù)庫 p2.setPrice(p2.getPrice() - 30); int ret2 = productMapper.updateById(p2); System.out.println("小王修改結(jié)果:" + ret2); // 最后的結(jié)果 Product p3 = productMapper.selectById(1L); System.out.println("最后的價格是:" + p3.getPrice()); } // 最終輸出的價格變?yōu)?0
-
樂觀鎖配置:
-
配置插件
@Configuration @MapperScan("com.zzy.mybatisplus_test.mapper") public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); // 數(shù)據(jù)類型為DbType.MYSQL mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 樂觀鎖插件 mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mybatisPlusInterceptor; } }
-
實(shí)體類字段加上
@Version
注解模擬沖突:
@Data @NoArgsConstructor @AllArgsConstructor public class Product { private Integer id; private String name; private Integer price; @Version private Integer version; }
-
-
測試(使用樂觀鎖后的測試)
@Test public void testConcurrentVesionUpdate() { // 小李取數(shù)據(jù) Product p1 = productMapper.selectById(1L); // 小王取數(shù)據(jù) Product p2 = productMapper.selectById(1L); // 小李修改 + 50 p1.setPrice(p1.getPrice() + 50); int result = productMapper.updateById(p1); System.out.println("小李修改的結(jié)果:" + result); // 小王修改 - 30 p2.setPrice(p2.getPrice() - 30); int ret = productMapper.updateById(p2); System.out.println("小王修改的結(jié)果:" + ret); if (ret == 0) { // 失敗重試忌穿,重新獲取version并更新 p2 = productMapper.selectById(1L); p2.setPrice(p2.getPrice() - 30); ret = productMapper.updateById(p2); } System.out.println("小王修改重試的結(jié)果:" + ret); // 老板看價格 Product p3 = productMapper.selectById(1L); System.out.println("老板看價格:" + p3.getPrice()); }
7.通用枚舉
表中有些字段值是固定的抒寂,比如性別,只有男或女掠剑,此時我們可以用MP的通用枚舉來實(shí)現(xiàn)
-
數(shù)據(jù)庫表增加一個字段sex
-
創(chuàng)建通用枚舉類型
@Getter public enum SexEnum { MALE(1, "男"), FEMALE(2, "女"); @EnumValue private Integer sex; private String sexName; SexEnum(Integer sex, String sexName) { this.sex = sex; this.sexName = sexName; } }
-
配置掃描通用枚舉
# 配置Mybatis日志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: # 配置MP操作表的默認(rèn)前綴 table-prefix: t_ # 配置MP的主鍵策略 id-type: auto type-aliases-package: com.zzy.mybatisplus_test.pojo # 配置掃描通用枚舉 type-enums-package: com.zzy.mybatisplus_test.enums
-
測試
@Test public void testSexEnum() { User user = new User(); user.setName("Enum"); user.setAge(20); // 設(shè)置性別信息為枚舉項(xiàng)屈芜,會將@EnumValue注解所標(biāo)注的屬性值存儲到數(shù)據(jù)庫 user.setSex(SexEnum.FEMALE); userMapper.insert(user); }
8.多數(shù)據(jù)源
適用于多種場景:純粹多庫,讀寫分離朴译,一主多從井佑,混合模式等
接下來模擬一個純粹多庫的場景:
創(chuàng)建兩個庫,分別為:mybatisplus(上面的庫不動)與mybatisplus_1(新建)眠寿,將mybatis_plus庫的t_product表移動到mybatisplus_1庫躬翁,這樣每個庫一張表,通過一個測試用例分別獲取用戶數(shù)據(jù)與商品數(shù)據(jù)盯拱,如果獲取到說明多庫模擬成功盒发。
-
創(chuàng)建數(shù)據(jù)庫及表
CREATE DATABASE `mybatisplus_1`; use `mybatisplus_1`; CREATE TABLE t_product ( id BIGINT(20) NOT NULL COMMENT '主鍵ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名稱', price INT(11) DEFAULT 0 COMMENT '價格', version INT(11) DEFAULT 0 COMMENT '樂觀鎖版本號', PRIMARY KEY (id) ); INSERT INTO t_product (id, NAME, price) VALUES (1, '筆記本', 100); # 刪除mybatisplus庫的t_product表 use mybatisplus; DROP TABLE IF EXISTS t_product;
-
引入依賴
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.1</version> </dependency>
-
配置多數(shù)據(jù)源
將之前的數(shù)據(jù)庫連接配置注釋掉,添加新的配置
spring: # 配置數(shù)據(jù)源信息 datasource: dynamic: # 設(shè)置默認(rèn)的數(shù)據(jù)源或者數(shù)據(jù)源組,默認(rèn)值即為master primary: master # 嚴(yán)格匹配數(shù)據(jù)源,默認(rèn)false.true未匹配到指定數(shù)據(jù)源時拋異常,false使用默認(rèn)數(shù)據(jù)源 strict: false datasource: master: url: jdbc:mysql://localhost:3306/mybatisplus?characterEncoding=utf-8&useSSL=false driver-class-name: com.mysql.jdbc.Driver username: root password: root slave_1: url: jdbc:mysql://localhost:3306/mybatisplus_1?characterEncoding=utf-8&useSSL=false driver-class-name: com.mysql.jdbc.Driver username: root password: root
-
創(chuàng)建用戶Service
在Service上添加注解
@DS
狡逢,指定數(shù)據(jù)源public interface UserService extends IService<User> { } @Service @DS("master") public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }
-
創(chuàng)建商品Service
public interface ProductService extends IService<Product> { } @DS("slave_1") @Service public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService { }
-
測試
@Test public void testDynamicDataSource() { // User user = userService.getById(1L); User user = userMapper.selectById(2L); System.out.println(user); System.out.println(productService.getById(1L)); }
注意:
這里在測試時候遇到了一點(diǎn)bug宁舰,使用Mybatis-plus 3.5.2版本的時候,從數(shù)據(jù)庫查詢得到的枚舉不會轉(zhuǎn)換奢浑,一直報錯:
org.springframework.jdbc.UncategorizedSQLException: Error attempting to get column 'sex' from result set. Cause: java.sql.SQLException: Error
; uncategorized SQLException; SQL state [null]; error code [0]; Error; nested exception is java.sql.SQLException: Error一直查找問題蛮艰,最后查看了這個issue:https://github.com/baomidou/mybatis-plus/issues/4338,使用3.4.3,3.5.1,3.5.3版本都報錯殷费,只有將mybatis-plus版本退回3.4.2才可以正常運(yùn)行印荔。