前言
官網(wǎng):MyBatis-plus官方文檔 簡(jiǎn)化 MyBatis !
創(chuàng)建數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)名為mybatis_plus
創(chuàng)建表
創(chuàng)建user表
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)
);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');
注意:-- 真實(shí)開發(fā)中往往都會(huì)有這四個(gè)字段,version(樂(lè)觀鎖)、deleted(邏輯刪除)、gmt_create(創(chuàng)建時(shí)間)、gmt_modified(修改時(shí)間)
初始化項(xiàng)目
使用SpringBoot器 初始化!
導(dǎo)入依賴
<!-- 數(shù)據(jù)庫(kù)驅(qū)動(dòng) -->
<dependency>
<groupId>MySQL</groupId>
<artifactId>mysql-connector-JAVA</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis-plus -->
<!-- mybatis-plus 是自己開發(fā),并非官方的! -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
注意:盡量不要同時(shí)導(dǎo)入 mybatis 和 mybatis-plus!避免版本的差異造成無(wú)法預(yù)知的問(wèn)題。
連接數(shù)據(jù)庫(kù)
創(chuàng)建Application.yml
spring:
profiles:
active: dev
datasource:
# 驅(qū)動(dòng)不同 mysql 5 com.mysql.jdbc.Driver
# mysql 8 com.mysql.cj.jdbc.Driver、需要增加時(shí)區(qū)的配置serverTimezone=GMT%2B8
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
業(yè)務(wù)代碼
實(shí)體類
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
mapper接口
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.kuang.pojo.User;
import org.springframework.stereotype.Repository;
// 在對(duì)應(yīng)的Mapper上面繼承基本的類 BaseMapper
@Repository // 代表持久層
public interface UserMapper extends BaseMapper<User> {
// 所有的CRUD操作都已經(jīng)編寫完成了
}
注意點(diǎn),我們需要在主啟動(dòng)類上去掃描我們的mapper包下的所有接口
@MapperScan(“com.kwhua.mapper”)
測(cè)試
@SpringBootTest
class MybatisPlusApplicationTests { // 繼承了BaseMapper,所有的方法都來(lái)自己父類
// 我們也可以編寫自己的擴(kuò)展方法!
@Autowired private UserMapper userMapper; @Test void contextLoads() { // 參數(shù)是一個(gè) Wrapper ,條件構(gòu)造器,這里我們先設(shè)置條件為空,查詢所有。
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println); }}
所有數(shù)據(jù)輸出
配置日志
我們所有的sql現(xiàn)在是不可見(jiàn)的,我們希望知道它是怎么執(zhí)行的,所有我們要配置日志的輸出
application.yml文件添加日志配置
#配置日志
mybatis-plus:
configuration:
log-impl: org.Apache.ibatis.logging.stdout.StdOutImpl
查看執(zhí)行sql的日志信息
三.Mybatis-plus的CRUD
1.插入操作
// 測(cè)試插入
@Test public void testInsert(){ User user = new User(); user.setName("kwhua_mybatis-plus_insertTest");
user.setAge(15);
user.setEmail("310697723@qq.com");
int result = userMapper.insert(user); // 幫我們自動(dòng)生成id
System.out.println(result); // 受影響的行數(shù)
System.out.println(user); // 看到id會(huì)自動(dòng)填充。 }
看到id會(huì)自動(dòng)填充。數(shù)據(jù)庫(kù)插入的id的默認(rèn)值為:全局的唯一id
主鍵生成策略
1)主鍵自增
1、實(shí)體類字段上 @TableId(type = IdType.AUTO)
2、數(shù)據(jù)庫(kù)id字段設(shè)置為自增!
3、再次測(cè)試(可以看到id值比上次插入的大1)
id的生成策略源碼解釋
public enum IdType {
AUTO(0), // 數(shù)據(jù)庫(kù)id自增
NONE(1), // 未設(shè)置主鍵
INPUT(2), // 手動(dòng)輸入
ID_WORKER(3), // 默認(rèn)的方式,全局唯一id
UUID(4), // 全局唯一id uuid
ID_WORKER_STR(5); //ID_WORKER 字符串表示法
}
以上不再逐一測(cè)試。
更新操作
@Test
public void testUpdate(){
User user = new User();
// 通過(guò)條件自動(dòng)拼接動(dòng)態(tài)sql
user.setId(1302223874217295874L);
user.setName("kwhua_mybatis-plus_updateTest");
user.setAge(20);
// 注意:updateById 但是參數(shù)是一個(gè)對(duì)象!
int i = userMapper.updateById(user);
System.out.println(i);
}
自動(dòng)填充
創(chuàng)建時(shí)間、修改時(shí)間!這兩個(gè)字段操作都是自動(dòng)化完成的,我們不希望手動(dòng)更新!
阿里巴巴開發(fā)手冊(cè):所有的數(shù)據(jù)庫(kù)表都要配置上gmt_create、gmt_modified!而且需要自動(dòng)化!
方式一:數(shù)據(jù)庫(kù)級(jí)別(工作中一般不用)
1、在表中新增字段 gmt_create, gmt_modified
2、把實(shí)體類同步
private Date gmtCreate;
private Date gmtModified;
3、再次查看
方式二:代碼級(jí)別
1、刪除數(shù)據(jù)庫(kù)的默認(rèn)值、更新操作!
[圖片上傳失敗...(image-eb7082-1599457281465)]
2、實(shí)體類字段屬性上需要增加注解
// 字段添加填充內(nèi)容
@TableField(fill = FieldFill.INSERT) private Date gmt_create;
@TableField(fill = FieldFill.INSERT_UPDATE) private Date gmt_modified;
3、編寫處理器來(lái)處理這個(gè)注解即可!
@Slf4j
@Component // 一定不要忘記把處理器加到IOC容器中!
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入時(shí)的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill.....");
// setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject
this.setFieldValByName("gmt_create",new Date(),metaObject);
this.setFieldValByName("gmt_modified",new Date(),metaObject);
}
// 更新時(shí)的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill.....");
this.setFieldValByName("gmt_modified",new Date(),metaObject);
}
}
4、測(cè)試插入和更新,檢查時(shí)間變化。
樂(lè)觀鎖
樂(lè)觀鎖 : 顧名思義,十分樂(lè)觀,它總是認(rèn)為不會(huì)出現(xiàn)問(wèn)題,無(wú)論干什么不去上鎖!如果出現(xiàn)了問(wèn)題,
再次更新值測(cè)試
悲觀鎖:顧名思義,十分悲觀,它總是認(rèn)為總是出現(xiàn)問(wèn)題,無(wú)論干什么都會(huì)上鎖!再去操作!
樂(lè)觀鎖實(shí)現(xiàn)方式:
取出記錄時(shí),獲取當(dāng)前version
更新時(shí),帶上這個(gè)version
執(zhí)行更新時(shí), set version = newVersion where version = oldVersion
如果version不對(duì),就更新失敗
樂(lè)觀鎖:1、先查詢,獲得版本號(hào) version = 1
-- Aupdate user set name = "kwhua", version = version + 1
where id = 2 and version = 1
-- B 線程搶先完成,這個(gè)時(shí)候 version = 2,會(huì)導(dǎo)致 A 修改失敗!
update user set name = "kwhua", version = version + 1
where id = 2 and version = 1
樂(lè)觀鎖測(cè)試
1、給數(shù)據(jù)庫(kù)中增加version字段!
2、實(shí)體類加對(duì)應(yīng)的字段
@Version //樂(lè)觀鎖Version注解
private Integer version;
3、注冊(cè)組件
// 掃描我們的 mapper 文件夾
@MapperScan("com.kwhua.mapper")
@EnableTransactionManagement@Configuration // 配置類
public class MyBatisPlusConfig {
// 注冊(cè)樂(lè)觀鎖插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
4、測(cè)試
// 測(cè)試樂(lè)觀鎖成功!
@Test public void testOptimisticLocker(){ // 1、查詢用戶信息
User user = userMapper.selectById(1L);
// 2、修改用戶信息
user.setName("kwhua");
user.setEmail("123456@qq.com");
// 3、執(zhí)行更新操作
userMapper.updateById(user); }
version字段已經(jīng)由1變成了2
// 測(cè)試樂(lè)觀鎖失敗!多線程下
@Test public void testOptimisticLocker2(){ // 線程 1
User user = userMapper.selectById(1L);
user.setName("kwhua111");
user.setEmail("123456@qq.com");
// 模擬另外一個(gè)線程執(zhí)行了插隊(duì)操作
User user2 = userMapper.selectById(1L);
user2.setName("kwhua222");
user2.setEmail("123456@qq.com");
userMapper.updateById(user2); // 自旋鎖來(lái)多次嘗試提交!
userMapper.updateById(user); // 如果沒(méi)有樂(lè)觀鎖就會(huì)覆蓋插隊(duì)線程的值!
}
可以看到線程1執(zhí)行更新失敗
查詢操作
// 測(cè)試查詢
@Test public void testSelectById(){ User user = userMapper.selectById(1L);
System.out.println(user); } // 測(cè)試批量查詢! @Test public void testSelectByBatchId(){ List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println); } // 按條件查詢之一使用map操作
@Test public void testSelectByBatchIds(){ HashMap<String, Object> map = new HashMap<>();
// 自定義要查詢
map.put("name","kwhua");
map.put("age",15);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println); }
6.1分頁(yè)查詢
1、配置攔截器組件
// 分頁(yè)插件
@Beanpublic PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor();
}
2、直接使用Page對(duì)象即可!
// 測(cè)試分頁(yè)查詢
@Testpublic void testPage(){ // 參數(shù)一:當(dāng)前頁(yè)
// 參數(shù)二:頁(yè)面大小
Page<User> page = new Page<>(2,5);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println); System.out.println(page.getTotal());}
物理刪除
// 測(cè)試刪除
@Test public void testDeleteById(){ userMapper.deleteById(1L);
} // 通過(guò)id批量刪除 @Test public void testDeleteBatchId(){ userMapper.deleteBatchIds(Arrays.asList(2L,3L));
} // 通過(guò)map刪除
@Test public void testDeleteMap(){ HashMap<String, Object> map = new HashMap<>();
map.put("name","kwhua");
userMapper.deleteByMap(map);
}
邏輯刪除
物理刪除 :從數(shù)據(jù)庫(kù)中直接移除
邏輯刪除 :在數(shù)據(jù)庫(kù)中沒(méi)有被移除,而是通過(guò)一個(gè)變量來(lái)讓它失效! deleted = 0 => deleted = 1
管理員可以查看被刪除的記錄!防止數(shù)據(jù)的丟失,類似于回收站!
1、在數(shù)據(jù)表中增加一個(gè) deleted 字段
2、實(shí)體類中增加屬性
@TableLogic //邏輯刪除
private Integer deleted;
3、配置
// 邏輯刪除組件!
@Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector();
}
配置文件配置
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
4、測(cè)試
測(cè)試刪除
字段值也從0修改成了1
測(cè)試查詢
性能分析插件
作用:性能分析攔截器,用于輸出每條 SQL 語(yǔ)句及其執(zhí)行時(shí)間
MP也提供性能分析插件,如果超過(guò)這個(gè)時(shí)間就停止運(yùn)行!
1、導(dǎo)入插件
/**
* SQL執(zhí)行效率插件
*/
@Bean
@Profile({"dev","test"})// 設(shè)置 dev test 環(huán)境開啟,保證我們的效率
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100); //ms 設(shè)置sql執(zhí)行的最大時(shí)間,如果超過(guò)了則不執(zhí)行
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
條件構(gòu)造器(Wrapper)
.isNotNull .gt
@Test
void contextLoads() { // 查詢name不為空的用戶,并且郵箱不為空的用戶,年齡大于等于12
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper .isNotNull("name") //不為空
.isNotNull("email")
.ge("age",18);
userMapper.selectList(wrapper).forEach(System.out::println); // 和我們剛才學(xué)習(xí)的map對(duì)比一下
}
.eq
@Test
void test2(){ // 查詢名字kwhua
QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("name","kwhua");
User user = userMapper.selectOne(wrapper); // 查詢一個(gè)數(shù)據(jù)用selectOne,查詢多個(gè)結(jié)果使用List 或者 Map
System.out.println(user); }
.between
@Test
void test3(){ // 查詢年齡在 20 ~ 30 歲之間的用戶
QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.between("age",20,30); // 區(qū)間
Integer count = userMapper.selectCount(wrapper);// 查詢結(jié)果數(shù)
System.out.println(count); }
.like
// 模糊查詢
@Test void test4(){ // 查詢名字中不帶e且 郵箱以t開頭的數(shù)據(jù)
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 左 likelift %t ,右 likeRight t%
wrapper .notLike("name","e")
.likeRight("email","t");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); }
.insql
// 模糊查詢
@Test void test5(){ QueryWrapper<User> wrapper = new QueryWrapper<>();
// id 在子查詢中查出來(lái)
wrapper.inSql("id","select id from user where id<3");
List<Object> objects = userMapper.selectObjs(wrapper); objects.forEach(System.out::println); }
.orderByAsc
//測(cè)試六
@Test void test6(){ QueryWrapper<User> wrapper = new QueryWrapper<>();
// 通過(guò)id進(jìn)行排序
wrapper.orderByAsc("id");
List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
代碼自動(dòng)生成器
可以在test文件夾在創(chuàng)建一個(gè)java類
// 代碼自動(dòng)生成器
public class generateCode { public static void main(String[] args) { // 需要構(gòu)建一個(gè) 代碼自動(dòng)生成器 對(duì)象
AutoGenerator mpg = new AutoGenerator(); // 配置策略
// 1、全局配置
GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath+"/src/main/java");
gc.setAuthor("kwhua");//作者名稱
gc.setOpen(false); gc.setFileOverride(false); // 是否覆蓋
gc.setIdType(IdType.ID_WORKER); gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true);//實(shí)體屬性 Swagger2 注解
// 自定義文件命名,注意 %s 會(huì)自動(dòng)填充表實(shí)體屬性! gc.setServiceName("%sService");
gc.setControllerName("%sController");
gc.setServiceName("%sService");
gc.setServiceImplName("%sServiceImpl");
gc.setMapperName("%sMapper");
gc.setXmlName("%sMapper");
mpg.setGlobalConfig(gc); //2、設(shè)置數(shù)據(jù)源
DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/kwhua_test?
useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
// dsc.setDriverName("com.mysql.jdbc.Driver"); //mysql5.6以下的驅(qū)動(dòng)
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); //3、包的配置
PackageConfig pc = new PackageConfig(); pc.setParent("com.kwhua"); //包名
pc.setModuleName("model"); //模塊名
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc); //4、策略配置
StrategyConfig strategy = new StrategyConfig(); strategy.setInclude("user","course"); // 設(shè)置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); // 自動(dòng)lombok;
strategy.setLogicDeleteFieldName("deleted");
// 自動(dòng)填充配置
TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmtModified = new TableFill("gmt_modified",FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>(); tableFills.add(gmtCreate); tableFills.add(gmtModified); strategy.setTableFillList(tableFills); // 樂(lè)觀鎖
strategy.setVersionFieldName("version");
//根據(jù)你的表名來(lái)建對(duì)應(yīng)的類名,如果你的表名沒(méi)有下劃線,比如test,那么你就可以取消這一步
strategy.setTablePrefix("t_");
strategy.setRestControllerStyle(true); //rest請(qǐng)求
//自動(dòng)轉(zhuǎn)下劃線,比如localhost:8080/hello_id_2
strategy.setControllerMappingHyphenStyle(true); mpg.setStrategy(strategy); mpg.execute(); //執(zhí)行
}}
執(zhí)行主方法即可生成對(duì)應(yīng)代碼
最后
感謝你看到這里,文章有什么不足還請(qǐng)指正,覺(jué)得文章對(duì)你有幫助的話記得給我點(diǎn)個(gè)贊,每天都會(huì)分享java相關(guān)技術(shù)文章或行業(yè)資訊,歡迎大家關(guān)注和轉(zhuǎn)發(fā)文章!