mybatis-plus使用教程
歡迎關注博主公眾號「Java大師」, 專注於分享Java領域干貨文章http://www.javaman.cn/jszw/mybatis-plus
什么是Mybatis-Plus
MyBatis-Plus (opens new window)(簡稱 MP)是一個 MyBatis (opens new window)的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。
特性
- 無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
- 損耗小:啟動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作
- 強大的 CRUD 操作:內置通用 Mapper、通用 Service,僅僅通過少量配置即可實現單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求
- 支持 Lambda 形式調用:通過 Lambda 表達式,方便的編寫各類查詢條件,無需再擔心字段寫錯
- 支持主鍵自動生成:支持多達 4 種主鍵策略(內含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問題
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式調用,實體類只需繼承 Model 類即可進行強大的 CRUD 操作
- 支持自定義全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 內置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,支持模板引擎,更有超多自定義配置等您來使用
- 內置分頁插件:基於 MyBatis 物理分頁,開發者無需關心具體操作,配置好插件之后,寫分頁等同於普通 List 查詢
- 分頁插件支持多種數據庫:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多種數據庫
- 內置性能分析插件:可輸出 SQL 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢
- 內置全局攔截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規則,預防誤操作
快速入門
步驟
1、創建數據庫和數據庫表
(來自官網的例子)
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主鍵ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年齡',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',
PRIMARY KEY (id)
);
2、插入表數據
DELETE FROM user;
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');
3、新建springboot工程,引入mubatis-plus依賴
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
4、application.yml配置mysql數據源
# DataSource Config
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost/mp_plus?serverTimezone=GMT%2B8&characterEncoding=UTF-8&allowMultiQueries=true
username: root
password: 123456
5、使用mybatis-plus
-
entity
@Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type = IdType.AUTO) //采用數據庫自增 private long id; private String name; private int age; private String email; }
-
mapper接口
// 在對應的Mapper上面繼承基本的類 BaseMapper
@Repository
public interface UserMapper extends BaseMapper<User> {
// 所有的CRUD操作都已經編寫完成了
}
-
springboot測試啟動類增加@MapperScan掃描注解
@MapperScan("com.mpstudy.mp.mapper")
-
測試
@MapperScan("com.mpstudy.mp.mapper")
@SpringBootTest
class MpApplicationTests {
// 繼承了BaseMapper,所有的方法都來自己父類
// 我們也可以編寫自己的擴展方法!
@Autowired
UserMapper userMapper;
@Test
void contextLoads() {
List<User> users = userMapper.selectList(null); //條件構造器先不用
users.forEach(System.out::println);
}
}
- 測試結果
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@75023c53]
User(id=1, name=Jone, age=18, email=test1@baomidou.com, createTime=null, updateTime=null)
User(id=2, name=Jack, age=20, email=test2@baomidou.com, createTime=null, updateTime=null)
User(id=3, name=Tom, age=28, email=test3@baomidou.com, createTime=null, updateTime=null)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com, createTime=null, updateTime=null)
User(id=5, name=Billie, age=24, email=test5@baomidou.com, createTime=null, updateTime=null)
日志配置
將我們執行的sql打印出來,方便調試
1、在application.yml中新增配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl //標准輸出
2、日志配置完成后,在控制台就會生成相應的日志
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@75023c53] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@688197093 wrapping com.mysql.jdbc.JDBC4Connection@3610f277] will not be managed by Spring
==> Preparing: SELECT id,name,age,email,create_time,update_time FROM user
==> Parameters:
<== Columns: id, name, age, email, create_time, update_time
<== Row: 1, Jone, 18, test1@baomidou.com, null, null
<== Row: 2, Jack, 20, test2@baomidou.com, null, null
<== Row: 3, Tom, 28, test3@baomidou.com, null, null
<== Row: 4, Sandy, 21, test4@baomidou.com, null, null
<== Row: 5, Billie, 24, test5@baomidou.com, null, null
CRUD詳解
1、插入操作
@Test
void insert(){
User user = new User();
user.setName("java大師1");
user.setAge(11);
user.setEmail("111000@qq.com");
int insert = userMapper.insert(user);
}
上面的的例子中沒有插入用戶id,在User類中已經設置了自動,也就是跟着數據庫的配置走(數據庫中已經設置了自增)
//mybatis-plus支持的主鍵策略 public enum IdType { AUTO(0), // 數據庫id自增 NONE(1), // 未設置主鍵 INPUT(2), // 手動輸入 ID_WORKER(3), // 默認的全局唯一id UUID(4), // 全局唯一id uuid ID_WORKER_STR(5); //ID_WORKER 字符串表示法
2、更新操作
@Test
void update(){
User user = new User();
user.setId(6);
user.setAge(38);
int i = userMapper.updateById(user); //傳入的是個對象
System.out.println(i);
}
3、時間戳自動填充
在實際開發中,我們希望所有的創建時間(create_time)和更新時間(update_time)都是自動完成,不需要人為去修改和維護
實現方法有如下兩種:
方法一:數據庫級別(數據庫增加default字段)
方法二:代碼級別
-
編寫時間戳自動填充注解
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.util.Date; @Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("開始填充"); this.setFieldValByName("createTime",new Date(),metaObject); this.setFieldValByName("updateTime",new Date(),metaObject); } @Override public void updateFill(MetaObject metaObject) { log.info("更新填充"); this.setFieldValByName("updateTime",new Date(),metaObject); } }
-
在實體類的字段上增加TableField注解
@Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type = IdType.AUTO) private long id; private String name; private int age; private String email; //創建時間注解 @TableField(fill = FieldFill.INSERT) private Date createTime; //修改時間注解 @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; }
-
測試
@Test void insert(){ User user = new User(); user.setName("公眾號:java大師"); user.setAge(2); user.setEmail("javadashi@qq.com"); int insert = userMapper.insert(user); }
4、刪除操作
// 測試刪除
@Test
public void testDeleteById(){
userMapper.deleteById(1);
}
// 通過id批量刪除
@Test
public void testDeleteBatchId(){
userMapper.deleteBatchIds(Arrays.asList(2,3,4));
}
// 通過map刪除
@Test
public void testDeleteMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","java大師");
userMapper.deleteByMap(map);
5、邏輯刪除
相對於物理刪除來說,我們需要對數據進行留檔,所以需要對刪除的數據也要保留,這就要用到邏輯刪除
物理刪除 :從數據庫中直接移除
邏輯刪除 :再數據庫中沒有被移除,而是通過一個變量來讓他失效! sfyx = 0 => sfyx = 1
-
在數據庫表中增加sfyx(是否有效)字段
-
實體類增加屬性
@TableLogic //邏輯刪除 private Integer sfyx;
-
配置邏輯刪除組件並被springboot所管理
// 邏輯刪除組件! @Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector(); }
-
修改application.yml增加配置
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-value: 0 //已刪除 logic-not-delete-value: 1 //未刪除
-
測試(看到雖然執行的的delete方法,但是實際的sql是update語句)
@Test void testDelete(){ int i = userMapper.deleteById(2); }
Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@456bcb74] was not registered for synchronization because synchronization is not active JDBC Connection [HikariProxyConnection@66747889 wrapping com.mysql.jdbc.JDBC4Connection@4dd90166] will not be managed by Spring ==> Preparing: UPDATE user SET sfyx=0 WHERE id=? AND sfyx=1 ==> Parameters: 2(Integer) <== Updates: 1
6、分頁查詢
-
配置分頁攔截器
@Bean public PaginationInterceptor paginationInterceptor(){ return new PaginationInterceptor(); }
-
使用Page對象
@Test void testPage(){ Page<User> page = new Page<>(1,3); userMapper.selectPage(page, null); page.getRecords().forEach(System.out::println); System.out.println(page.getTotal()); }
7、多表查詢
-
創建VO對象
import com.mpstudy.mp.entity.User; import lombok.Data; @Data public class UserClassVo extends User { private String className; }
-
UserMapper中增加getAllUsers方法,通過select注解編寫sql
@Repository
public interface UserMapper extends BaseMapper<User> {
@Select("select a.*,b.name as class_name from user a,class b,user_class c where a.id=c.user_id and b.id=c.class_id ")
List<UserClassVo> getAllUsers();
}
-
測試
@Test void testGetAllUsers(){ List<UserClassVo> allUsers = userMapper.getAllUsers(); allUsers.forEach(System.out::println); }
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@47f04e4d] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@843410864 wrapping com.mysql.jdbc.JDBC4Connection@5f5827d0] will not be managed by Spring
==> Preparing: select a.*,b.name as class_name from user a,class b,user_class c where a.id=c.user_id and b.id=c.class_id
==> Parameters:
<== Columns: id, name, age, email, create_time, update_time, sfyx, class_name
<== Row: 2, Jack, 20, test2@baomidou.com, null, 2021-09-20 18:05:06.0, 0, 小二班
<== Row: 3, Tom, 28, test3@baomidou.com, null, 2021-09-20 18:04:27.0, 1, 大一班
8、多表分頁查詢
-
創建VO對象
import com.mpstudy.mp.entity.User; import lombok.Data; @Data public class UserClassVo extends User { private String className; }
-
UserMapper中增加getUsersByPage方法,通過select注解編寫sql
@Repository public interface UserMapper extends BaseMapper<User> { //傳入IPage對象和QueryWrapper條件構造器 //sql通過${ew.customSqlSegment}去解析構造器內容 @Select("select a.*,c.name as class_name from user a left join user_class b on a.id=b.user_id left join class c on b.class_id = c.id " + "${ew.customSqlSegment} ") IPage<UserClassVo> getUsersByPage(IPage<UserClassVo> page,@Param(Constants.WRAPPER) QueryWrapper wrapper); }
-
測試
@Test void testGetUsersByPage(){ Page<UserClassVo> page = new Page<>(2,2); QueryWrapper<UserClassVo> wrapper = new QueryWrapper<>(); wrapper.likeRight("a.name","java"); userMapper.getUsersByPage(page,wrapper); page.getRecords().forEach(System.out::println); }
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f438904] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@766089249 wrapping com.mysql.jdbc.JDBC4Connection@62cba181] will not be managed by Spring
JsqlParserCountOptimize sql=select a.*,c.name as class_name from user a left join user_class b on a.id=b.user_id left join class c on b.class_id = c.id WHERE a.name LIKE ?
==> Preparing: SELECT COUNT(1) FROM user a LEFT JOIN user_class b ON a.id = b.user_id LEFT JOIN class c ON b.class_id = c.id WHERE a.name LIKE ?
==> Parameters: java%(String)
<== Columns: COUNT(1)
<== Row: 3
==> Preparing: select a.*,c.name as class_name from user a left join user_class b on a.id=b.user_id left join class c on b.class_id = c.id WHERE a.name LIKE ? LIMIT ?,?
==> Parameters: java%(String), 0(Long), 2(Long)
<== Columns: id, name, age, email, create_time, update_time, sfyx, class_name
<== Row: 6, java大師, 38, javadashi@qq.com, null, 2021-09-20 18:04:29.0, 1, 小二班
<== Row: 7, java大師, 11, javadashi@qq.com, null, 2021-09-20 18:04:29.0, 1, 大一班
<== Total: 2
10、條件構造器
說明:
QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父類
用於生成 sql 的 where 條件, entity 屬性也用於生成 sql 的 where 條件
注意: entity 生成的 where 條件與 使用各個 api 生成的 where 條件沒有任何關聯行為
支持:allEq,eq,isNull等等,具體見:條件構造器 | MyBatis-Plus (baomidou.com)
- 測試
@Test
void testWrapper01(){
// 查詢name不為空的用戶,並且郵箱不為空的用戶,年齡大於等於12記錄
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name").isNotNull("create_time").gt("age",12);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
@Test
void testWrapper02(){
// 查詢年齡18到24之間的記錄
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",18,24);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
@Test
void testWrapper03(){
// 名字中帶java的記錄
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.likeRight("name","java");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
11、性能分析插件
實際工作中,可能會遇到sql慢的一些情況,通過性能分析插件,可以定位到慢的sql和其運行時長
-
導入插件
@Bean @Profile({"dev"}) //開發環境運行 public PerformanceInterceptor performanceInterceptor(){ PerformanceInterceptor interceptor = new PerformanceInterceptor(); interceptor.setFormat(true); //格式化sql interceptor.setMaxTime(20); //設置超時時長,單位毫秒 return interceptor; }
-
application.xml設置為dev開發模式
# DataSource Config spring: profiles: active: dev
-
測試(日志最后1行顯示超時報錯)
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: The SQL execution time is too large, please optimize ! ### The error may exist in com/mpstudy/mp/mapper/UserMapper.java (best guess) ### The error may involve com.mpstudy.mp.mapper.UserMapper.getUsersByPage ### The error occurred while handling results ### SQL: select a.*,c.name as class_name from user a left join user_class b on a.id=b.user_id left join class c on b.class_id = c.id WHERE a.name LIKE ? LIMIT ?,? ### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: The SQL execution time is too large, please optimize !
12、代碼自動生成器
dao、entity、service、controller自動生成
AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、
Mapper、Mapper XML、Service、Controller 等各個模塊的代碼,極大的提升了開發效率。
-
導入依賴
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.1.0</version> </dependency>
-
代碼生成
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.InjectionConfig; 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; import java.util.HashMap; import java.util.Map; //代碼自動生成器 public class CodeGenerator { public static void main(String[] args) { //構建一個 代碼自動生成器 對象 AutoGenerator autoGenerator = new AutoGenerator(); //1、全局配置 GlobalConfig gc = new GlobalConfig(); String oPath = System.getProperty("user.dir");//得到當前項目的路徑 gc.setOutputDir(oPath + "/src/main/java"); //生成文件輸出根目錄 gc.setOpen(false);//生成完成后不彈出文件框 gc.setFileOverride(true); //文件覆蓋 gc.setAuthor("ryan");// 作者 gc.setServiceName("%sService"); //去除Service的I前綴 gc.setIdType(IdType.ID_WORKER); gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true); autoGenerator.setGlobalConfig(gc); ///2、數據源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setDbType(DbType.MYSQL); //設置數據庫類型 dsc.setUrl("jdbc:mysql://localhost:3306/mp_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8"); //指定數據庫 dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("root"); autoGenerator.setDataSource(dsc); //3、包的配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("mp"); pc.setParent("com.mpstudy"); pc.setEntity("pojo"); pc.setMapper("mapper"); pc.setService("service"); pc.setController("controller"); autoGenerator.setPackageInfo(pc); // 4、策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setInclude("user","user_class","class"); // 設置要映射的表名 strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); // 自動lombok; strategy.setLogicDeleteFieldName("deleted"); strategy.setTablePrefix("tb_"); //去掉表的前綴 // 自動填充配置 TableFill gmtCreate = new TableFill("create_time", FieldFill.INSERT); TableFill gmtModified = new TableFill("update_time", FieldFill.INSERT_UPDATE); ArrayList<TableFill> tableFills = new ArrayList<>(); tableFills.add(gmtCreate); tableFills.add(gmtModified); strategy.setTableFillList(tableFills); // 樂觀鎖 strategy.setVersionFieldName("version");strategy.setRestControllerStyle(true); strategy.setControllerMappingHyphenStyle(true); autoGenerator.setStrategy(strategy); //不加這個會報空指針異常 InjectionConfig injectionConfig = new InjectionConfig() { //自定義屬性注入:abc //在.ftl(或者是.vm)模板中,通過${cfg.abc}獲取屬性 @Override public void initMap() { Map<String, Object> map = new HashMap<>(); map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp"); this.setMap(map); } }; //自定義配置 autoGenerator.setCfg(injectionConfig); // 執行生成 autoGenerator.execute(); } }