mybatisplus 和 springboot 的集成
导入依赖
<!--spring-boot-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
配置文件
## web ##
server:
servlet:
context-path: /
port: 80
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.100.66:3306/hl?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Hongkong&useSSL=false&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: mapper/*.xml #定义xml文件的位置
type-aliases-package: com.lomi.entity #定义实体文件的包
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
dao从层
GoodsService层
GoodsServiceImpl
GoodsMapper.xml可以不存在
用法和建议
mybatisPlus对dao层和Service都有一些默认封装,可以选择只用dao层,也可以选择dao和service层一起使用。
service的批量操作效率比拼装 foreach方式更高
mybatisPlus对dao层的封装
增删改查,分页,列表,查询数量,查询是否存在等接口
@Override
public void t1() {
LambdaQueryWrapper<Goods> qw = Wrappers.lambdaQuery();
Goods goods = Goods.randomGoods();
Page page = new Page(1,4);
//查询
goodsExMapper.selectById(1668801996867579904L);
//多个会抛出异常
try{
goodsExMapper.selectOne(qw);
}catch (Exception e){
log.warn("不止一个");
}
boolean exists = goodsExMapper.exists(qw);
log.warn("是否存在{}", exists );
Long count = goodsExMapper.selectCount(qw);
log.warn("数量{}", count );
List<Goods> list = goodsExMapper.selectList(qw);
log.warn("不分页结果{}", JSONUtil.toJsonStr( list ));
IPage selectPage = goodsExMapper.selectPage(page, qw);
log.warn("分页结果{}", JSONUtil.toJsonStr( selectPage ));
//添加
goodsExMapper.insert(goods);
//更新
goodsExMapper.updateById( goods );
log.warn("完成更新");
//批量
//批量查询(用的in,真批量,建议使用)
List<Goods> list1 = goodsExMapper.selectBatchIds(Arrays.asList(1668805297105350656L, 1668805300435628032L));
log.warn("批量查询{}",list1);
//批量删除(用的in,真批量,建议使用)
int i = goodsExMapper.deleteBatchIds(Arrays.asList(1668805297105350656L, 1668805300435628032L));
log.warn("批量删除{}",i);
//删除
goodsExMapper.deleteById(1L);
log.warn("删除Id是1的数据");
//条件删除
//goodsExMapper.delete(qw);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
mybatisPlus对service的封装
对比dao层的封装,提供里更多批量接口,提供了saveOrUpdate之类的接口,后面会体现提供的批量接口比forearch更加省内存,但是内存充足的时候不如forearch快
@Override
public void t1() {
t2();
add();
get();
up();
delete();
batch();
}
@Override
@Transactional
public void batchAdd(List<Goods> list) {
saveBatch(list);
}
/**
* 获取当前的mapper
*/
private void t2() {
GoodsMapper goodsMapper = getBaseMapper();
log.debug( "goodsMapper:" + goodsMapper );
}
/**
* 添加
*/
private void add() {
add( Goods.randomGoods() );
save(Goods.randomGoods());
}
/**
* 查询
*/
private void get() {
//用过Id查询
Goods goods = getById(id);
LambdaQueryWrapper<Goods> qw = Wrappers.lambdaQuery();
qw.gt( Goods::getId,1L);
//查询单个,多个会抛出异常
//getOne(qw);
//查询单个不抛出异常,得到第一个
Goods one = getOne(qw, false);
log.debug( "查询单个:" + one );
//获取任意对象
HashMap<String, Object> obj = (HashMap<String, Object>) getObj(qw, item -> {
//item.get
return BeanUtil.beanToMap( item );
});
log.debug( "查询对象封装:" + obj );
//查询数量
count();
long count = count(qw);
log.debug( "查询数量:" + count );
//不分页查询列表
list();
list(qw);
//分页查询1
Page page = new Page(1,10);
IPage pageList1 = page(page, qw);
log.debug( "分页查询1:" + pageList1 );
//分页查询2
List<Goods> pageList2 = goodsExMapper.pageList(page);
log.debug( "分页查询2:" + pageList2.getClass() );
}
Long id = 1668822727982657536L;
/**
* 修改
*/
private void up() {
Goods goods = getById(id);
goods.setName("改");
//添加或者更新
saveOrUpdate(goods);
//通过Id更新
updateById( goods );
//查询条件更新
LambdaUpdateWrapper<Goods> uw = Wrappers.lambdaUpdate(Goods.randomGoods());
uw.eq( Goods::getId,1L);
uw.set(Goods::getName,"uw名字");
update(uw);
//查询条件更新2
LambdaQueryWrapper<Goods> qw = Wrappers.lambdaQuery();
qw.eq( Goods::getId,1L);
update(goods,qw);
}
/**
* 删除
*/
private void delete() {
removeById(1L);
}
/**
* 批量接口
*/
private void batch() {
//批量查询(这个用的底层的in批量查询)
listByIds(Arrays.asList( 1L ) );
//批量插入,比foreach省内存(内存比较低的情况下,1000条数据时,约比foreach快60倍,这时候频繁GC,不具有参考意义,只能得出更加省内存)
//内存充足的情况下,foreach 约是mybatisplus 批量快2倍,10W条数据 1000批量,plus约12秒,foreach约6.5秒
saveBatch(Arrays.asList( Goods.randomGoods(),Goods.randomGoods() ));
log.warn("批量插入----------------");
//批量添加或者更新
saveOrUpdateBatch(Arrays.asList( Goods.randomGoods(),Goods.randomGoods() ));
log.warn("批量插入或者更新----------------");
//批量删除
removeByIds( Arrays.asList( 1L,2L ) );
log.warn("批量删除----------------");
//批量更新
updateBatchById( Arrays.asList( Goods.randomGoods(),Goods.randomGoods() ) );
log.warn("批量更新----------------");
updateBatchById( Arrays.asList( Goods.randomGoods(),Goods.randomGoods() ),1000 );
log.warn("批量更新2----------------");
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
mybatisplus不同版本的分页
不同版本启用分页插件
/**
* mybatisPlus 3.5以后 用这种用法
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
/*
*
* mybatis 3.3-3.4 这样用
*
* */
/* @Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setDbType(DbType.MYSQL);
return paginationInterceptor;
}*/
/*
*
* mybatis 3.2以前使用这种用法
*
* */
/*@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor().setDialectType(DbType.MYSQL.getDb());
}*/
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
使用分页的常见两种方式
//直接使用mybatisplus的selectPage接口
Page page = new Page(1,4);
IPage selectPage = goodsExMapper.selectPage(page, qw);
log.warn("分页结果{},{}", JSONUtil.toJsonStr( selectPage ),page.getTotal());
//请求参数中带有Page对象就会分页
Page page2 = new Page(1,4);
List<Goods> list2 = goodsExMapper.pageList(page2);
log.warn("分页结果2{},{}", JSONUtil.toJsonStr( list2 ),page2.getTotal());
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
批量service层批量接口和在sql上拼接foreach效率差异
结论service层批量插入约是sql上凭借foreach的60倍
foreach方式
<insert id="addBatch">
insert into goods (id,
name,
stock,
des,
des2,
data,
create_date)
values
<foreach collection="list" item="item" index="index" separator=",">
(#{item.id,jdbcType=BIGINT},
#{item.name,jdbcType=VARCHAR},
#{item.stock,jdbcType=INTEGER},
#{item.des,jdbcType=VARCHAR},
#{item.des2,jdbcType=VARCHAR},
#{item.data,jdbcType=LONGVARCHAR},
#{item.createDate,jdbcType=TIMESTAMP})
</foreach>
</insert>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
使用mybatisplus service封装的方式
记录运行时间的测试代码(需要观察 GC情况,很多时候效率差异是GC导致的,idea 2023.3 设置启动类 -Xms 和 -Xmx 好像都无效,内存上不去,频繁GC,放到控制台运行指定jvm 参数才能得到准确的结果)
/**
* 批量测试 结果,mybatisplus 批量更加省内存,内存低内存不足的时候很少GC,forearch有大量GC,内存不足的时候,甚至比 forearch快60倍,注意是内存不足的情况下
* 内存充足额情况下10W条,1000条一次的批量 mybatisplus 耗时 12秒左右,foreach 耗时 6.5秒左右
*
*
* @return
* @throws Exception
*/
@RequestMapping(value="batchTest", method= {RequestMethod.GET})
public String batchTest() throws Exception{
Long time1 = System.currentTimeMillis();
for(int i=0;i<100;i++ ){
List<Goods> list = new ArrayList<>();
Stream.generate(Goods::randomGoods ).limit(1000).forEach( item-> list.add( item ) );
goodsService.batchAdd(list);
}
Long time2 = System.currentTimeMillis();
log.warn("mybatis批量:{}", time2-time1);
return "OK";
}
/**
* 批量测试
* @return
* @throws Exception
*/
@RequestMapping(value="batchTestForeach", method= {RequestMethod.GET})
public String batchTestForeach() throws Exception{
Long time1 = System.currentTimeMillis();
for(int i=0;i<100;i++ ){
List<Goods> list = new ArrayList<>();
Stream.generate(Goods::randomGoods ).limit(1000).forEach( item-> list.add( item ) );
iGoodsService.batchAdd(list);
}
Long time2 = System.currentTimeMillis();
log.warn("dao层foreach批量:{}", time2-time1);
return "OK";
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
运行的结果分别是
mybatisplus乐观锁
/**
* mybatisPlus 3.5以后 用这种用法
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//乐观锁插件
interceptor.addInnerInterceptor( new OptimisticLockerInnerInterceptor());
return interceptor;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
在实体上标记version
然后再更新的时候就会带上 version的条件,如果version是null,那么乐观锁不会生效
@Override
public void updatebyVersion() {
Goods goods = getById(1668873830610841600L);
goods.setName("改1");
log.warn("打印1,{},{}",goods.getVersion(),goods.getName());
boolean rt1 = updateById( goods );
log.warn("打印2,{},{}",goods.getVersion(),goods.getName());
log.warn("更新结果{}",rt1);
goods.setName("改2");
log.warn("打印3,{},{}",goods.getVersion(),goods.getName());
boolean rt2 = updateById(goods);
log.warn("打印4,{},{}",goods.getVersion(),goods.getName());
log.warn("更新结果{}",rt2);
//删除的时候不会带上乐观锁
removeById(goods);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
注解方式的sql
这不是mybatisplus的,是mybatis的语法,并且个人不推荐用注解形式的sql,xml 格式的sql 比 注解方式的sql 可读性搞很多,尽量写到xml里面去,对齐格式,方便阅读,个人建议优先使用 xml方式的sql,可以少量使用QueryWrapper,UpdateWrapper这种面向对象的方式,namedsql方式建议少使用
//使用#{}获取参数
@Select("select * from goods where id = #{id}")
Goods querById_nameSql(@Param("id") Long id);
@Update("update goods set name = #{param.name} where id = #{param.id}")
int update_namedsql(@Param("param") Goods param);
//参数有 Page对象就会分页
@Select("select * from goods")
List<Goods> queryPage_namedsql(@Param("name") String name,Page page);
/**
* ${ew.sqlSelect} :查询字段的部分的sql,来自接口 Query 的getSqlSelect方法,查询语句才能用
* ${ew.sqlSet} set 部分的sql ,来自接口 Update的getSqlSet方法
* ${ew.sqlSegment} where后面的 sql ,来自接口 ISqlSegment的getSqlSegment方法
* Constants.WRAPPER里面的参数都是可以通过 ${}方式取到
*/
@Update("update goods set ${ew.sqlSet} where ${ew.sqlSegment}")
int updateParam_namedsql(@Param(Constants.WRAPPER) Wrapper<Goods> wp, Page page);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
QueryWrapper and和or查询嵌套规律
不建议使用复制的对象嵌套查询,很难读,应该写到xml里面去,简单可以用
/**
* QueryWrapper 对象查询语句嵌套规律
*
* 1. 默认都是 and 连接,如果调用了 or 下一个连接符就是or
* 2. 调用and or 以返回值是原对象
* 3. 外部QueryWrapper 或者说原始的 QueryWrapper 条件都是同一级别的
* 4. 调用 and or以后 内部QueryWrapper会产生一个子条件,并且用括号括起来,属于更高级别的
*/
@Override
public void orQuery() {
//WHERE (name = ? OR des = ? AND des2 = ? OR stock = ?)
LambdaQueryWrapper<Goods> query1 = Wrappers.lambdaQuery();
query1.eq( Goods::getName,"name1" );
LambdaQueryWrapper<Goods> query12 = query1.or();
//and 和or 返回值 和原对象是相等的,这里是链式编程的返回值类型
log.warn("是否相等1:{}", query1 == query12);
//默认连接符是and,调用了or以后下一个连接符是or(这里在上一步已经调用过了)
query1.eq(Goods::getDes,"name2");
query1.eq(Goods::getDes2,"name3");
query1.or().eq(Goods::getStock,2 );
List<Goods> list = list(query1);
// WHERE (name = ? AND (id = ? AND des = ? OR (id = ? AND des = ?) AND stock = ? AND des2 = ?))
//外面的条件
LambdaQueryWrapper<Goods> query2 = Wrappers.lambdaQuery();
query2.eq( Goods::getName,"name1" );
//and和or内部的QueryWrapper 会加括号,相当于多了一个子层
query2.and(qwInnerLv2->{
//子条件1
qwInnerLv2.eq( Goods::getId,1 );
qwInnerLv2.eq( Goods::getDes,"des1" );
//子条件2(这是一个有嵌套的子条件)
qwInnerLv2.or(qwInnerLv3 -> {
qwInnerLv3.eq(Goods::getId, 2);
qwInnerLv3.eq(Goods::getDes, "des2");
});
//子条件3
qwInnerLv2.eq( Goods::getStock,1 );
qwInnerLv2.eq( Goods::getDes2,1 );
});
List<Goods> list2 = list(query2);
// WHERE (name = ? AND ((id = ? AND des = ?) OR (id = ? AND des = ?)))
//外面的条件
LambdaQueryWrapper<Goods> query3 = Wrappers.lambdaQuery();
query3.eq( Goods::getName,"name1" );
LambdaQueryWrapper<Goods> and3 = query3.and(qwLv2->{
//2层条件1
qwLv2.eq(Goods::getStock,1);
//2层条件2
qwLv2.and( qwlv3->{
//3层子条件1
qwlv3.and(qwlv4->{
qwlv4.eq( Goods::getId,1 );
qwlv4.eq( Goods::getDes,"des1" );
});
//3层子条件2
qwlv3.or(qwlv4->{
qwlv4.eq( Goods::getId,2 );
qwlv4.eq( Goods::getDes,"des2" );
});
} );
});
List<Goods> list3 = list(query3);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.