免費(fèi)提供網(wǎng)站建設(shè)免費(fèi)二級(jí)域名分發(fā)平臺(tái)
MyBatis-plus 是一款 Mybatis 增強(qiáng)工具,用于簡(jiǎn)化開發(fā),提高效率。下文使用縮寫 mp來簡(jiǎn)化表示 MyBatis-plus,本文主要介紹 mp 整合?Spring Boot 的使用。
(5條消息) mybatis-plus用法(二)_渣娃工程師的博客-CSDN博客
1.創(chuàng)建一個(gè)Spring Boot項(xiàng)目。
2.導(dǎo)入依賴
<!-- pom.xml --> <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>mybatis-plus</artifactId> <version>0.0.1-SNAPSHOT</version> <name>mybatis-plus</name> <properties> <java.version>1.8</java.version> </properties> <dependencies> <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-configuration-processor</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
?3.配置數(shù)據(jù)庫(kù)
# application.yml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/yogurt?serverTimezone=Asia/Shanghai username: root password: root mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #開啟SQL語句打印
4.創(chuàng)建一個(gè)實(shí)體類
package com.example.mp.po; import lombok.Data; import java.time.LocalDateTime; @Data public class User { private Long id; private String name; private Integer age; private String email; private Long managerId; private LocalDateTime createTime; }
5.創(chuàng)建一個(gè)mapper接口
package com.example.mp.mappers; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.mp.po.User; public interface UserMapper extends BaseMapper<User> { }
6.在SpringBoot啟動(dòng)類上配置mapper接口的掃描路徑
package com.example.mp; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconp.SpringBootApplication; @SpringBootApplication @MapperScan("com.example.mp.mappers") public class MybatisPlusApplication { public static void main(String[] args) { SpringApplication.run(MybatisPlusApplication.class, args); } }
7.在數(shù)據(jù)庫(kù)中創(chuàng)建表
DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主鍵', name VARCHAR(30) DEFAULT NULL COMMENT '姓名', age INT(11) DEFAULT NULL COMMENT '年齡', email VARCHAR(50) DEFAULT NULL COMMENT '郵箱', manager_id BIGINT(20) DEFAULT NULL COMMENT '直屬上級(jí)id', create_time DATETIME DEFAULT NULL COMMENT '創(chuàng)建時(shí)間', CONSTRAINT manager_fk FOREIGN KEY(manager_id) REFERENCES user (id) ) ENGINE=INNODB CHARSET=UTF8; INSERT INTO user (id, name, age ,email, manager_id, create_time) VALUES (1, '大BOSS', 40, 'boss@baomidou.com', NULL, '2021-03-22 09:48:00'), (2, '李經(jīng)理', 40, 'boss@baomidou.com', 1, '2021-01-22 09:48:00'), (3, '黃主管', 40, 'boss@baomidou.com', 2, '2021-01-22 09:48:00'), (4, '吳組長(zhǎng)', 40, 'boss@baomidou.com', 2, '2021-02-22 09:48:00'), (5, '小菜', 40, 'boss@baomidou.com', 2, '2021-02-22 09:48:00')
8.編寫一個(gè)SpringBoot測(cè)試類
package com.example.mp; import com.example.mp.mappers.UserMapper; import com.example.mp.po.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; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest public class SampleTest { @Autowired private UserMapper mapper; @Test public void testSelect() { List<User> list = mapper.selectList(null); assertEquals(5, list.size()); list.forEach(System.out::println); } }
準(zhǔn)備工作完成,數(shù)據(jù)庫(kù)情況如下:
項(xiàng)目目錄如下:
?運(yùn)行測(cè)試類
?可以看到,針對(duì)單表的基本CRUD操作,只需要?jiǎng)?chuàng)建好實(shí)體類,并創(chuàng)建一個(gè)繼承自BaseMapper
的接口即可,可謂非常簡(jiǎn)潔。并且,我們注意到,User
類中的managerId
,createTime
屬性,自動(dòng)和數(shù)據(jù)庫(kù)表中的manager_id
,create_time
對(duì)應(yīng)了起來,這是因?yàn)閙p自動(dòng)做了數(shù)據(jù)庫(kù)下劃線命名,到Java類的駝峰命名之間的轉(zhuǎn)化。
注解
mp一共提供了8個(gè)注解,這些注解是用在Java的實(shí)體類上面的。
@TableName
注解在類上,指定類和數(shù)據(jù)庫(kù)表的映射關(guān)系。實(shí)體類的類名(轉(zhuǎn)成小寫后)和數(shù)據(jù)庫(kù)表名相同時(shí),可以不指定該注解。
@TableId
注解在實(shí)體類的某一字段上,表示這個(gè)字段對(duì)應(yīng)數(shù)據(jù)庫(kù)表的主鍵。當(dāng)主鍵名為id時(shí)(表中列名為id,實(shí)體類中字段名為id),無需使用該注解顯式指定主鍵,mp會(huì)自動(dòng)關(guān)聯(lián)。若類的字段名和表的列名不一致,可用value
屬性指定表的列名。另,這個(gè)注解有個(gè)重要的屬性type
,用于指定主鍵策略。
@TableField
注解在某一字段上,指定Java實(shí)體類的字段和數(shù)據(jù)庫(kù)表的列的映射關(guān)系。這個(gè)注解有如下幾個(gè)應(yīng)用場(chǎng)景。
-
排除非表字段
若Java實(shí)體類中某個(gè)字段,不對(duì)應(yīng)表中的任何列,它只是用于保存一些額外的,或組裝后的數(shù)據(jù),則可以設(shè)置
exist
屬性為false
,這樣在對(duì)實(shí)體對(duì)象進(jìn)行插入時(shí),會(huì)忽略這個(gè)字段。排除非表字段也可以通過其他方式完成,如使用static
或transient
關(guān)鍵字,但個(gè)人覺得不是很合理,不做贅述 -
字段驗(yàn)證策略
通過
insertStrategy
,updateStrategy
,whereStrategy
屬性進(jìn)行配置,可以控制在實(shí)體對(duì)象進(jìn)行插入,更新,或作為WHERE條件時(shí),對(duì)象中的字段要如何組裝到SQL語句中。 -
字段填充策略
通過
fill
屬性指定,字段為空時(shí)會(huì)進(jìn)行自動(dòng)填充
@Version
樂觀鎖注解
@EnumValue
注解在枚舉字段上
@TableLogic
邏輯刪除
KeySequence
序列主鍵策略(oracle
)
InterceptorIgnore
插件過濾規(guī)則
CRUD接口
mp封裝了一些最基礎(chǔ)的CRUD方法,只需要直接繼承mp提供的接口,無需編寫任何SQL,即可食用。mp提供了兩套接口,分別是Mapper CRUD接口和Service CRUD接口。并且mp還提供了條件構(gòu)造器Wrapper
,可以方便地組裝SQL語句中的WHERE條件。
Mapper CRUD接口
只需定義好實(shí)體類,然后創(chuàng)建一個(gè)接口,繼承mp提供的BaseMapper
,即可食用。mp會(huì)在mybatis啟動(dòng)時(shí),自動(dòng)解析實(shí)體類和表的映射關(guān)系,并注入帶有通用CRUD方法的mapper。BaseMapper
里提供的方法,部分列舉如下:
-
insert(T entity)
??插入一條記錄 -
deleteById(Serializable id)
??根據(jù)主鍵id刪除一條記錄 -
delete(Wrapper<T> wrapper)
?根據(jù)條件構(gòu)造器wrapper進(jìn)行刪除 -
selectById(Serializable id)
?根據(jù)主鍵id進(jìn)行查找 -
selectBatchIds(Collection idList)
?根據(jù)主鍵id進(jìn)行批量查找 -
selectByMap(Map<String,Object> map)
?根據(jù)map中指定的列名和列值進(jìn)行等值匹配查找 -
selectMaps(Wrapper<T> wrapper)
??根據(jù) wrapper 條件,查詢記錄,將查詢結(jié)果封裝為一個(gè)Map,Map的key為結(jié)果的列,value為值 -
selectList(Wrapper<T> wrapper)
?根據(jù)條件構(gòu)造器wrapper
進(jìn)行查詢 -
update(T entity, Wrapper<T> wrapper)
?根據(jù)條件構(gòu)造器wrapper
進(jìn)行更新 -
updateById(T entity)
-
...
下面講解幾個(gè)比較特別的方法
selectMaps
BaseMapper
接口還提供了一個(gè)selectMaps
方法,這個(gè)方法會(huì)將查詢結(jié)果封裝為一個(gè)Map,Map的key為結(jié)果的列,value為值
該方法的使用場(chǎng)景如下:
-
只查部分列
當(dāng)某個(gè)表的列特別多,而SELECT的時(shí)候只需要選取個(gè)別列,查詢出的結(jié)果也沒必要封裝成Java實(shí)體類對(duì)象時(shí)(只查部分列時(shí),封裝成實(shí)體后,實(shí)體對(duì)象中的很多屬性會(huì)是null),則可以用selectMaps
,獲取到指定的列后,再自行進(jìn)行處理即可
比如
@Test public void test3() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.select("id","name","email").likeRight("name","黃"); List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); }
進(jìn)行數(shù)據(jù)統(tǒng)計(jì)
比如
// 按照直屬上級(jí)進(jìn)行分組,查詢每組的平均年齡,最大年齡,最小年齡 /** select avg(age) avg_age ,min(age) min_age, max(age) max_age from user group by manager_id having sum(age) < 500; **/ @Test public void test3() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.select("manager_id", "avg(age) avg_age", "min(age) min_age", "max(age) max_age") .groupBy("manager_id").having("sum(age) < {0}", 500); List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); }
selectObjs
只會(huì)返回第一個(gè)字段(第一列)的值,其他字段會(huì)被舍棄
比如
@Test public void test3() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.select("id", "name").like("name", "黃"); List<Object> objects = userMapper.selectObjs(wrapper); objects.forEach(System.out::println); }
得到的結(jié)果,只封裝了第一列的id
selectCount
查詢滿足條件的總數(shù),注意,使用這個(gè)方法,不能調(diào)用QueryWrapper
的select
方法設(shè)置要查詢的列了。這個(gè)方法會(huì)自動(dòng)添加select count(1)
比如
@Test public void test3() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.like("name", "黃"); Integer count = userMapper.selectCount(wrapper); System.out.println(count); }
Service CRUD 接口
另外一套CRUD是Service層的,只需要編寫一個(gè)接口,繼承IService
,并創(chuàng)建一個(gè)接口實(shí)現(xiàn)類,即可食用。(這個(gè)接口提供的CRUD方法,和Mapper接口提供的功能大同小異,比較明顯的區(qū)別在于IService
支持了更多的批量化操作,如saveBatch
,saveOrUpdateBatch
等方法。
食用示例如下
1.首先,新建一個(gè)接口,繼承IService
package com.example.mp.service; import com.baomidou.mybatisplus.extension.service.IService; import com.example.mp.po.User; public interface UserService extends IService<User> { }
?2.創(chuàng)建這個(gè)接口的實(shí)現(xiàn)類,并繼承ServiceImpl
,最后打上@Service
注解,注冊(cè)到Spring容器中,即可食用
package com.example.mp.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.mp.mappers.UserMapper; import com.example.mp.po.User; import com.example.mp.service.UserService; import org.springframework.stereotype.Service; @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }
?3.測(cè)試代碼
package com.example.mp; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.example.mp.po.User; import com.example.mp.service.UserService; 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; @RunWith(SpringRunner.class) @SpringBootTest public class ServiceTest { @Autowired private UserService userService; @Test public void testGetOne() { LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery(); wrapper.gt(User::getAge, 28); User one = userService.getOne(wrapper, false); // 第二參數(shù)指定為false,使得在查到了多行記錄時(shí),不拋出異常,而返回第一條記錄 System.out.println(one); } }
?4.結(jié)果
?另,IService
也支持鏈?zhǔn)秸{(diào)用,代碼寫起來非常簡(jiǎn)潔,查詢示例如下
@Test public void testChain() { List<User> list = userService.lambdaQuery() .gt(User::getAge, 39) .likeRight(User::getName, "王") .list(); list.forEach(System.out::println); }
?更新示例如下
@Test public void testChain() { userService.lambdaUpdate() .gt(User::getAge, 39) .likeRight(User::getName, "王") .set(User::getEmail, "w39@baomidou.com") .update(); }
?刪除示例如下
@Test public void testChain() { userService.lambdaUpdate() .like(User::getName, "青蛙") .remove(); }
條件構(gòu)造器
mp讓我覺得極其方便的一點(diǎn)在于其提供了強(qiáng)大的條件構(gòu)造器Wrapper
,可以非常方便的構(gòu)造WHERE條件。條件構(gòu)造器主要涉及到3個(gè)類,AbstractWrapper
。QueryWrapper
,UpdateWrapper
,它們的類關(guān)系如下
在AbstractWrapper
中提供了非常多的方法用于構(gòu)建WHERE條件,而QueryWrapper
針對(duì)SELECT
語句,提供了select()
方法,可自定義需要查詢的列,而UpdateWrapper
針對(duì)UPDATE
語句,提供了set()
方法,用于構(gòu)造set
語句。條件構(gòu)造器也支持lambda表達(dá)式,寫起來非常舒爽。
下面對(duì)AbstractWrapper
中用于構(gòu)建SQL語句中的WHERE條件的方法進(jìn)行部分列舉
-
eq
:equals,等于 -
allEq
:all equals,全等于 -
ne
:not equals,不等于 -
gt
:greater than ,大于?>
-
ge
:greater than or equals,大于等于≥
-
lt
:less than,小于<
-
le
:less than or equals,小于等于≤
-
between
:相當(dāng)于SQL中的BETWEEN -
notBetween
-
like
:模糊匹配。like("name","黃")
,相當(dāng)于SQL的name like '%黃%'
-
likeRight
:模糊匹配右半邊。likeRight("name","黃")
,相當(dāng)于SQL的name like '黃%'
-
likeLeft
:模糊匹配左半邊。likeLeft("name","黃")
,相當(dāng)于SQL的name like '%黃'
-
notLike
:notLike("name","黃")
,相當(dāng)于SQL的name not like '%黃%'
-
isNull
-
isNotNull
-
in
-
and
:SQL連接符AND -
or
:SQL連接符OR -
apply
:用于拼接SQL,該方法可用于數(shù)據(jù)庫(kù)函數(shù),并可以動(dòng)態(tài)傳參 -
.......
使用示例
下面通過一些具體的案例來練習(xí)條件構(gòu)造器的使用。(使用前文創(chuàng)建的user
表)
// 案例先展示需要完成的SQL語句,后展示W(wǎng)rapper的寫法 // 1. 名字中包含佳,且年齡小于25
// SELECT * FROM user WHERE name like '%佳%' AND age < 25
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name", "佳").lt("age", 25);
List<User> users = userMapper.selectList(wrapper);
// 下面展示SQL時(shí),僅展示W(wǎng)HERE條件;展示代碼時(shí), 僅展示W(wǎng)rapper構(gòu)建部分 // 2. 姓名為黃姓,且年齡大于等于20,小于等于40,且email字段不為空
// name like '黃%' AND age BETWEEN 20 AND 40 AND email is not null
wrapper.likeRight("name","黃").between("age", 20, 40).isNotNull("email"); // 3. 姓名為黃姓,或者年齡大于等于40,按照年齡降序排列,年齡相同則按照id升序排列
// name like '黃%' OR age >= 40 order by age desc, id asc
wrapper.likeRight("name","黃").or().ge("age",40).orderByDesc("age").orderByAsc("id"); // 4.創(chuàng)建日期為2021年3月22日,并且直屬上級(jí)的名字為李姓
// date_format(create_time,'%Y-%m-%d') = '2021-03-22' AND manager_id IN (SELECT id FROM user WHERE name like '李%')
wrapper.apply("date_format(create_time, '%Y-%m-%d') = {0}", "2021-03-22") // 建議采用{index}這種方式動(dòng)態(tài)傳參, 可防止SQL注入 .inSql("manager_id", "SELECT id FROM user WHERE name like '李%'");
// 上面的apply, 也可以直接使用下面這種方式做字符串拼接,但當(dāng)這個(gè)日期是一個(gè)外部參數(shù)時(shí),這種方式有SQL注入的風(fēng)險(xiǎn)
wrapper.apply("date_format(create_time, '%Y-%m-%d') = '2021-03-22'"); // 5. 名字為王姓,并且(年齡小于40,或者郵箱不為空)
// name like '王%' AND (age < 40 OR email is not null)
wrapper.likeRight("name", "王").and(q -> q.lt("age", 40).or().isNotNull("email")); // 6. 名字為王姓,或者(年齡小于40并且年齡大于20并且郵箱不為空)
// name like '王%' OR (age < 40 AND age > 20 AND email is not null)
wrapper.likeRight("name", "王").or( q -> q.lt("age",40) .gt("age",20) .isNotNull("email") ); // 7. (年齡小于40或者郵箱不為空) 并且名字為王姓
// (age < 40 OR email is not null) AND name like '王%'
wrapper.nested(q -> q.lt("age", 40).or().isNotNull("email")) .likeRight("name", "王"); // 8. 年齡為30,31,34,35
// age IN (30,31,34,35)
wrapper.in("age", Arrays.asList(30,31,34,35));
// 或
wrapper.inSql("age","30,31,34,35"); // 9. 年齡為30,31,34,35, 返回滿足條件的第一條記錄
// age IN (30,31,34,35) LIMIT 1
wrapper.in("age", Arrays.asList(30,31,34,35)).last("LIMIT 1"); // 10. 只選出id, name 列 (QueryWrapper 特有)
// SELECT id, name FROM user;
wrapper.select("id", "name"); // 11. 選出id, name, age, email, 等同于排除 manager_id 和 create_time
// 當(dāng)列特別多, 而只需要排除個(gè)別列時(shí), 采用上面的方式可能需要寫很多個(gè)列, 可以采用重載的select方法,指定需要排除的列
wrapper.select(User.class, info -> { String columnName = info.getColumn(); return !"create_time".equals(columnName) && !"manager_id".equals(columnName); });
Condition
條件構(gòu)造器的諸多方法中,均可以指定一個(gè)boolean
類型的參數(shù)condition
,用來決定該條件是否加入最后生成的WHERE語句中,比如
String name = "黃"; // 假設(shè)name變量是一個(gè)外部傳入的參數(shù)
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like(StringUtils.hasText(name), "name", name);
// 僅當(dāng) StringUtils.hasText(name) 為 true 時(shí), 會(huì)拼接這個(gè)like語句到WHERE中
// 其實(shí)就是對(duì)下面代碼的簡(jiǎn)化
if (StringUtils.hasText(name)) { wrapper.like("name", name);
}
實(shí)體對(duì)象作為條件
調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)Wrapper
對(duì)象時(shí),可以傳入一個(gè)實(shí)體對(duì)象。后續(xù)使用這個(gè)Wrapper
時(shí),會(huì)以實(shí)體對(duì)象中的非空屬性,構(gòu)建WHERE條件(默認(rèn)構(gòu)建等值匹配的WHERE條件,這個(gè)行為可以通過實(shí)體類里各個(gè)字段上的@TableField
注解中的condition
屬性進(jìn)行改變)
示例如下
@Test public void test3() { User user = new User(); user.setName("黃主管"); user.setAge(28); QueryWrapper<User> wrapper = new QueryWrapper<>(user); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
執(zhí)行結(jié)果如下??梢钥吹?#xff0c;是根據(jù)實(shí)體對(duì)象中的非空屬性,進(jìn)行了等值匹配查詢。
若希望針對(duì)某些屬性,改變等值匹配的行為,則可以在實(shí)體類中用@TableField
注解進(jìn)行配置,示例如下
package com.example.mp.po;
import com.baomidou.mybatisplus.annotation.SqlCondition;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User { private Long id; @TableField(condition = SqlCondition.LIKE) // 配置該字段使用like進(jìn)行拼接 private String name; private Integer age; private String email; private Long managerId; private LocalDateTime createTime;
}
運(yùn)行下面的測(cè)試代碼
@Test public void test3() { User user = new User(); user.setName("黃"); QueryWrapper<User> wrapper = new QueryWrapper<>(user); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
?從下圖得到的結(jié)果來看,對(duì)于實(shí)體對(duì)象中的name
字段,采用了like
進(jìn)行拼接
?@TableField
中配置的condition
屬性實(shí)則是一個(gè)字符串,SqlCondition
類中預(yù)定義了一些字符串以供選擇
package com.baomidou.mybatisplus.annotation; public class SqlCondition { //下面的字符串中, %s 是占位符, 第一個(gè) %s 是列名, 第二個(gè) %s 是列的值 public static final String EQUAL = "%s=#{%s}"; public static final String NOT_EQUAL = "%s<>#{%s}"; public static final String LIKE = "%s LIKE CONCAT('%%',#{%s},'%%')"; public static final String LIKE_LEFT = "%s LIKE CONCAT('%%',#{%s})"; public static final String LIKE_RIGHT = "%s LIKE CONCAT(#{%s},'%%')";
}
?SqlCondition
中提供的配置比較有限,當(dāng)我們需要<
或>
等拼接方式,則需要自己定義。比如
package com.example.mp.po;
import com.baomidou.mybatisplus.annotation.SqlCondition;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User { private Long id; @TableField(condition = SqlCondition.LIKE) private String name; @TableField(condition = "%s > #{%s}") // 這里相當(dāng)于大于, 其中 > 是字符實(shí)體 private Integer age; private String email; private Long managerId; private LocalDateTime createTime;
}
?測(cè)試如下
@Test public void test3() { User user = new User(); user.setName("黃"); user.setAge(30); QueryWrapper<User> wrapper = new QueryWrapper<>(user); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
從下圖得到的結(jié)果,可以看出,name
屬性是用like
拼接的,而age
屬性是用>
拼接的
allEq方法
allEq方法傳入一個(gè)map
,用來做等值匹配
@Test public void test3() { QueryWrapper<User> wrapper = new QueryWrapper<>(); Map<String, Object> param = new HashMap<>(); param.put("age", 40); param.put("name", "黃飛飛"); wrapper.allEq(param); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
當(dāng)allEq方法傳入的Map中有value為null
的元素時(shí),默認(rèn)會(huì)設(shè)置為is null
@Test public void test3() { QueryWrapper<User> wrapper = new QueryWrapper<>(); Map<String, Object> param = new HashMap<>(); param.put("age", 40); param.put("name", null); wrapper.allEq(param); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
?若想忽略map中value為null
的元素,可以在調(diào)用allEq時(shí),設(shè)置參數(shù)boolean null2IsNull
為false
@Test public void test3() { QueryWrapper<User> wrapper = new QueryWrapper<>(); Map<String, Object> param = new HashMap<>(); param.put("age", 40); param.put("name", null); wrapper.allEq(param, false); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
若想要在執(zhí)行allEq時(shí),過濾掉Map中的某些元素,可以調(diào)用allEq的重載方法allEq(BiPredicate<R, V> filter, Map<R, V> params)
@Test public void test3() { QueryWrapper<User> wrapper = new QueryWrapper<>(); Map<String, Object> param = new HashMap<>(); param.put("age", 40); param.put("name", "黃飛飛"); wrapper.allEq((k,v) -> !"name".equals(k), param); // 過濾掉map中key為name的元素 List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
lambda條件構(gòu)造器
lambda條件構(gòu)造器,支持lambda表達(dá)式,可以不必像普通條件構(gòu)造器一樣,以字符串形式指定列名,它可以直接以實(shí)體類的方法引用來指定列。示例如下
@Test public void testLambda() { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.like(User::getName, "黃").lt(User::getAge, 30); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
像普通的條件構(gòu)造器,列名是用字符串的形式指定,無法在編譯期進(jìn)行列名合法性的檢查,這就不如lambda條件構(gòu)造器來的優(yōu)雅。
另外,還有個(gè)鏈?zhǔn)絣ambda條件構(gòu)造器,使用示例如下
@Test public void testLambda() { LambdaQueryChainWrapper<User> chainWrapper = new LambdaQueryChainWrapper<>(userMapper); List<User> users = chainWrapper.like(User::getName, "黃").gt(User::getAge, 30).list(); users.forEach(System.out::println); }
更新操作
上面介紹的都是查詢操作,現(xiàn)在來講更新和刪除操作。
BaseMapper
中提供了2個(gè)更新方法
-
updateById(T entity)
根據(jù)入?yún)?code>entity的id
(主鍵)進(jìn)行更新,對(duì)于entity
中非空的屬性,會(huì)出現(xiàn)在UPDATE語句的SET后面,即entity
中非空的屬性,會(huì)被更新到數(shù)據(jù)庫(kù),示例如下
@RunWith(SpringRunner.class) @SpringBootTest public class UpdateTest { @Autowired private UserMapper userMapper; @Test public void testUpdate() { User user = new User(); user.setId(2L); user.setAge(18); userMapper.updateById(user); } }
-
update(T entity, Wrapper<T> wrapper)
根據(jù)實(shí)體entity
和條件構(gòu)造器wrapper
進(jìn)行更新,示例如下
@Test public void testUpdate2() { User user = new User(); user.setName("王三蛋"); LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>(); wrapper.between(User::getAge, 26,31).likeRight(User::getName,"吳"); userMapper.update(user, wrapper); }
?額外演示一下,把實(shí)體對(duì)象傳入Wrapper
,即用實(shí)體對(duì)象構(gòu)造WHERE條件的案例
@Test public void testUpdate3() { User whereUser = new User(); whereUser.setAge(40); whereUser.setName("王"); LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>(whereUser); User user = new User(); user.setEmail("share@baomidou.com"); user.setManagerId(10L); userMapper.update(user, wrapper); }
注意到我們的User類中,對(duì)name
屬性和age
屬性進(jìn)行了如下的設(shè)置
@Data
public class User { private Long id; @TableField(condition = SqlCondition.LIKE) private String name; @TableField(condition = "%s > #{%s}") private Integer age; private String email; private Long managerId; private LocalDateTime createTime;
}
?執(zhí)行結(jié)果
?再額外演示一下,鏈?zhǔn)絣ambda條件構(gòu)造器的使用
@Test public void testUpdate5() { LambdaUpdateChainWrapper<User> wrapper = new LambdaUpdateChainWrapper<>(userMapper); wrapper.likeRight(User::getEmail, "share") .like(User::getName, "飛飛") .set(User::getEmail, "ff@baomidou.com") .update(); }
反思
由于BaseMapper
提供的2個(gè)更新方法都是傳入一個(gè)實(shí)體對(duì)象去執(zhí)行更新,這在需要更新的列比較多時(shí)還好,若想要更新的只有那么一列,或者兩列,則創(chuàng)建一個(gè)實(shí)體對(duì)象就顯得有點(diǎn)麻煩。針對(duì)這種情況,UpdateWrapper
提供有set
方法,可以手動(dòng)拼接SQL中的SET語句,此時(shí)可以不必傳入實(shí)體對(duì)象,示例如下
@Test public void testUpdate4() { LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>(); wrapper.likeRight(User::getEmail, "share").set(User::getManagerId, 9L); userMapper.update(null, wrapper); }
刪除操作
BaseMapper
一共提供了如下幾個(gè)用于刪除的方法
-
deleteById
??根據(jù)主鍵id進(jìn)行刪除 -
deleteBatchIds
??根據(jù)主鍵id進(jìn)行批量刪除 -
deleteByMap
??根據(jù)Map進(jìn)行刪除(Map中的key為列名,value為值,根據(jù)列和值進(jìn)行等值匹配) -
delete(Wrapper<T> wrapper)
??根據(jù)條件構(gòu)造器Wrapper
進(jìn)行刪除
與前面查詢和更新的操作大同小異,不做贅述
自定義SQL
當(dāng)mp提供的方法還不能滿足需求時(shí),則可以自定義SQL。
原生mybatis
示例如下
-
注解方式
package com.example.mp.mappers; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.mp.po.User; import org.apache.ibatis.annotations.Select; import java.util.List; /** * @Author yogurtzzz * @Date 2021/3/18 11:21 **/ public interface UserMapper extends BaseMapper<User> { @Select("select * from user") List<User> selectRaw(); }
-
xml方式
<?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.example.mp.mappers.UserMapper"> <select id="selectRaw" resultType="com.example.mp.po.User"> SELECT * FROM user </select> </mapper>
package com.example.mp.mappers; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.mp.po.User; import org.apache.ibatis.annotations.Select; import java.util.List; public interface UserMapper extends BaseMapper<User> { List<User> selectRaw(); }
使用xml時(shí),若xml文件與mapper接口文件不在同一目錄下,則需要在
application.yml
中配置mapper.xml的存放路徑mybatis-plus: mapper-locations: /mappers/*
若有多個(gè)地方存放mapper,則用數(shù)組形式進(jìn)行配置
mybatis-plus: mapper-locations: - /mappers/* - /com/example/mp/*
測(cè)試代碼如下
@Test public void testCustomRawSql() { List<User> users = userMapper.selectRaw(); users.forEach(System.out::println); }
結(jié)果
mybatis-plus
也可以使用mp提供的Wrapper條件構(gòu)造器,來自定義SQL
示例如下
-
注解方式
package com.example.mp.mappers; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.example.mp.po.User; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; public interface UserMapper extends BaseMapper<User> { // SQL中不寫WHERE關(guān)鍵字,且固定使用${ew.customSqlSegment} @Select("select * from user ${ew.customSqlSegment}") List<User> findAll(@Param(Constants.WRAPPER)Wrapper<User> wrapper); }
-
xml方式
package com.example.mp.mappers; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.mp.po.User; import java.util.List; public interface UserMapper extends BaseMapper<User> { List<User> findAll(Wrapper<User> wrapper); }
<!-- UserMapper.xml --> <?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.example.mp.mappers.UserMapper"> <select id="findAll" resultType="com.example.mp.po.User"> SELECT * FROM user ${ew.customSqlSegment} </select> </mapper>
分頁(yè)查詢
BaseMapper
中提供了2個(gè)方法進(jìn)行分頁(yè)查詢,分別是selectPage
和selectMapsPage
,前者會(huì)將查詢的結(jié)果封裝成Java實(shí)體對(duì)象,后者會(huì)封裝成Map<String,Object>
。分頁(yè)查詢的食用示例如下1. 創(chuàng)建mp的分頁(yè)攔截器,注冊(cè)到Spring容器中
package com.example.mp.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisPlusConfig { /** 新版mp **/ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } /** 舊版mp 用 PaginationInterceptor **/ }
2. 執(zhí)行分頁(yè)查詢
@Test public void testPage() { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.ge(User::getAge, 28); // 設(shè)置分頁(yè)信息, 查第3頁(yè), 每頁(yè)2條數(shù)據(jù) Page<User> page = new Page<>(3, 2); // 執(zhí)行分頁(yè)查詢 Page<User> userPage = userMapper.selectPage(page, wrapper); System.out.println("總記錄數(shù) = " + userPage.getTotal()); System.out.println("總頁(yè)數(shù) = " + userPage.getPages()); System.out.println("當(dāng)前頁(yè)碼 = " + userPage.getCurrent()); // 獲取分頁(yè)查詢結(jié)果 List<User> records = userPage.getRecords(); records.forEach(System.out::println); }
3. 結(jié)果
-
4. 其他
注意到,分頁(yè)查詢總共發(fā)出了2次SQL,一次查總記錄數(shù),一次查具體數(shù)據(jù)。若希望不查總記錄數(shù),僅查分頁(yè)結(jié)果??梢酝ㄟ^
Page
的重載構(gòu)造函數(shù),指定isSearchCount
為false
即可public?Page(long?current,?long?size,?boolean?isSearchCount)
在實(shí)際開發(fā)中,可能遇到多表聯(lián)查的場(chǎng)景,此時(shí)
BaseMapper
中提供的單表分頁(yè)查詢的方法無法滿足需求,需要自定義SQL,示例如下(使用單表查詢的SQL進(jìn)行演示,實(shí)際進(jìn)行多表聯(lián)查時(shí),修改SQL語句即可)1. 在mapper接口中定義一個(gè)函數(shù),接收一個(gè)Page對(duì)象為參數(shù),并編寫自定義SQL
//?這里采用純注解方式。當(dāng)然,若SQL比較復(fù)雜,建議還是采用XML的方式?? @Select("SELECT?*?FROM?user?${ew.customSqlSegment}")?? Page<User>?selectUserPage(Page<User>?page,?@Param(Constants.WRAPPER)?Wrapper<User>?wrapper);
2. 執(zhí)行查詢
@Test?? public?void?testPage2()?{??LambdaQueryWrapper<User>?wrapper?=?new?LambdaQueryWrapper<>();??wrapper.ge(User::getAge,?28).likeRight(User::getName,?"王");??Page<User>?page?=?new?Page<>(3,2);??Page<User>?userPage?=?userMapper.selectUserPage(page,?wrapper);??System.out.println("總記錄數(shù)?=?"?+?userPage.getTotal());??System.out.println("總頁(yè)數(shù)?=?"?+?userPage.getPages());??userPage.getRecords().forEach(System.out::println);?? }
3. 結(jié)果