Hike News
Hike News

mybatisplus入门

Mybatis是一种关系型数据库的ORM持久化框架,封装了JDBC和sqlmap,使java代码和DAO分离,提高了开发效率。

Mybatis和Hibernate对比

Hibernate和JPA:操作简便,开发效率高,不容易优化,反射操作太多影响性能。

Mybatis:轻量级,性能出色,开发效率稍低。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>

在resource文件夹下创建mybatis-config.xml配置文件。

—–表—–实体类——mapper接口—–映射文件–

java=数据库概念关系:实体类=表,类属性=字段,对象=记录/行,保持一致。xml映射文件的namespace命名域需要与mapper接口的全类名一致,sql语句的id要与mapper接口中的方法名一致。通过调用接口的方法来执行对应sql语句!

mapper接口的名字和mapper映射文件的名字保存一致,所在包包名保存一致,最好为mapper。

mybatis提供一个操作数据库的对象SqlSession,通过SqlSession.getMap获取mapper接口实现类的对象。

1
2
select * from user where username like "%"#{username}"%";
select * from user where username like '%${username}%';

在核心配置文件中mybaitis-plus配置:

1
2
3
4
5
6
7
mybatis-plus:
#控制台输出日志
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
type-aliases-package: com.xxx.xxx.entity
mapper-locations: classpath:mapper/*.xml

使用步骤:

1、导入依赖

2、配置依赖

3、创建pojo

4、继承BaseMapper,创建mapper接口。

5、在spring应用程序上添加@MapperScan("xxx")注解或在mapper接口上添加@Mapper注解。

6、在service层中继承实现类ServiceImpl<BaseMapper, Object>,实现自定义接口。

1
2
3
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product>
//业务层帮我们创建mapper对象

主键自增策略

mysql默认插入数据主键自增,可以在数据库中查看。为了弥补数据库默认自增的不足,比如会出现单点故障。MyBatis-Plus默认的主键策略是:ASSIGN_ID (使用了雪花算法)。mybaitis-plus需要在实体类属性上加上主键自增注解@TableId(type=IdType.AUTO)

1
@TableId(value = "product_id",type = IdType.AUTO)

type: AUTO自增、ID_WORKER默认唯一id、UUID全局唯一id。

字段主键@TableField(exist = false) :该字段在数据库中不存在。

自动填充和乐观锁

自动填充

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作。

实体上增加字段并添加自动填充注解

1
2
3
4
5
6
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//create_time
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
//update_time

实现元对象处理器接口

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//mp执行添加操作,这个方法执行
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject); }
//mp执行修改操作,这个方法执行
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject); }
}
乐观锁

当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新。

取出记录时,获取当前version更新时,带上这个version执行更新时, set version

修改实体类,添加@Version注解

1
2
@Version
private Integer version;

创建包config,创建文件MybatisPlusConfig.java, 此时可以删除主类中的 @MapperScan扫描注解。

1
2
3
4
5
6
7
@Configuration
@MapperScan("com.xxx.mapper")
public class MpConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor(); }
}

${}和#{}获取参数值

字段名和属性名一致:ResultType

字段名和属性名不一致:给字段起别名,别名为属性名;在mybatis中配置驼峰命名map-underscore-to-camel-case: true;定义一个resultMap,通过property和column字段绑定

一对多

collection集合

1
2
3
4
5
6
7
8
9
10
11
<resultMap id="DeptAndEmpResultMap" type="Dept" autoMapping="true">
<id column="dept_id" property="deptId"/>
<!--属性emps集合中元素的类型-->
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</collection>
</resultMap>

分步查询:

select:设置分步查询的唯一标识,mapper接口的全类名和方法名。

column:查询的字段。

ofType:查询中的集合内的数据类型。

多对一映射

resultMap级联属性赋值;

<association>标签

1
2
3
4
5
6
7
<resultMap id="MinMap" type="Moneyin" autoMapping="true">
<id column="in_id" property="inId"/>
<!--将查询出的账户的列数据和账户实体映射起来-->
<association property="zhanghu" javaType="Zhanghu" autoMapping="true"></association>
<!--将查询出的收入分类的列数据和收入分类实体映射起来-->
<association property="categoryin" javaType="Categoryin" autoMapping="true"></association>
</resultMap>

分步查询

优点:实现了延迟加载,避免执行过多的sql语句。配置文件配置如下:

1
2
3
lazyLoadingEnabled=true
#开启时所有关联对象都延迟加载
aggressiveLazyLoading=false

当开启全局延迟加载后,查询可通过fetchType属性实现eager立即加载或lazy延迟加载。

动态SQL

尽管mybaitis-plus帮我们省去了动态sql的编写,但仍必要了解什么是动态sql。

本质:标签

作用:动态拼接sql语句

if标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="getEmp" resultMap="Emp" >
select * from t_emp where 1=1
<if test="ename!=null and ename!=''">
ename = #{ename}
</if>
<if test="age!=null and age!=''">
and age = #{age}
</if>
<if test="email !=null and email!=''">
and email = #{email}
</if>
<!--引用定义好的sql语句-->
<!-- <include refid="minSql"></include> -->
</select>

where标签

根据标签中的内容自动生成where关键字,并将条件内容前多余的and、or关键字去掉,后面的关键字无法忽略,如果没有内容则不会生成where。

trim标签

prefix|suffix,在标签内容的前后添加内容

choose、when标签

choose…when….otherwise =switch….case… default

foreach标签

​ collection:需要循环的数组和集合

​ item:表示数组和集合中的每一个元素

​ separator:循环之间的分割符

​批量删除

1
2
3
4
5
6
7
<delete id="deleteMore">
delete from t_emp where eid in(
<foreach collection="eids" item="eid" separator=",">
#{ eid }
</foreach>
)
</delete>

​批量增加

1
2
3
4
5
6
<insert id="insertMore">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{ emp.empName },#{ emp.age },#{ emp.sex },#{ emp.email },null)
</foreach>
</select>

sql标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--定义可以被复用的查询sql-->
<sql id="minSql">
select
mi.in_id, mi.single_in, mi.zhanghu_id, mi.categoryin_id, mi.date, mi.beizhu,
z.zhanghu_name,
z.zhanghu_money,
z.create_time,
cin.categoryin_name
from moneyin mi
join zhanghu z
on mi.zhanghu_id=z.zhanghu_id
join categoryin cin
on mi.categoryin_id=cin.categoryin_id
</sql>
<!--引用定义好的sql语句-->
<include refid="minSql"></include>

缓存

针对查询,将数据暂时保存起来以备下一次查询。 cache hit ratio

一级缓存

默认开启,sqlSession级别,对于同一的sqlSession的查询,会从缓存中取对象,如果是同一查询则能够成功取出对象,若两次查询之间进行了一次增删改操作则会失效(相当于清除缓存),clearCache()手动清除缓存。

二级缓存

sqlSeesionFactory级别,通过同一sqlSeesionFactory创建的sqlSeesion查询的结果会被缓存,此后再执行相同的查询就会从缓存中取。

二级缓存开启条件:在核心配置文件中配置cacheEnabled=”true”;在映射文件中设置cache标签,在sqlSeesion关闭或提交后有效;查询的实体类型必须实现序列化接口。

查询顺序:先查二级缓存,没有命中再查一级缓存,最后查数据库 ,sqlSession关闭或提交后一级缓存保存到二级。

第三方缓存

代替二级缓存。

1)添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!--在pom.xml中添加-->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.11</version>
</dependency>
<!--SLF4J是Simple Logging Facade for Java的简写,即Java简单日志门面,用来服务于各种各样的日志框架,比如java.util.logging、logback和log4j-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>

2)导入jar包

slf4j-api,mybatis-ehcache,ehcache,logback-classic。

3)配置第三方缓存配置文件ehchche.xml

4)设置二级缓存的类型

1
2
<!--在映射文件中添加-->
<cache type="第三方缓存全类名">

pagehelper分页插件

1、添加依赖

1
2
3
4
5
6
 <!--分页-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.3</version>
</dependency>

2、在核心配置文件中添加插件配置

1
2
3
4
5
6
7
#配置分页
#pagehelper
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql

分页插件的使用:

1
PageInfo<Product> listByPage(Integer page, Integer limit, String searchProductName);
1
2
limit 0,10
--limit 起始下标index,pageSize页大小

index当前页起始索引,pageSize每页显示条数,pageNum当前页码,total总记录数,pages页数,prePage上一页页码,nextPage下一页页码,navigatePages导航页码数,navigatepageNums导航分页页码。

1
2
3
4
5
6
7
8
9
10
11
12
//使用PageHelper插件,设置分页参数
PageHelper.startPage(page,limit);
//构建查询条件包装器
QueryWrapper qw = new QueryWrapper();
if(!Objects.isNull(searchProductName)) {
qw.like("pro_name", searchProductName);
}
//实现条件查询
List products = this.list(qw);
//使用分页对象封装查询结果
PageInfo<Product> pi = new PageInfo<>(products);
return pi;

pageinfo对象:

1
2
3
4
5
PageInfo{pageNum=1, pageSize=5, size=2, startRow=1, endRow=2, total=2, pages=1, 
list=Page{count=true, pageNum=1, pageSize=5, startRow=0, endRow=5, total=2, pages=1, reasonable=true, pageSizeZero=false}[
Product(productId=1, proName=股票, proMoney=1000.00, description=),
Product(productId=2, proName=黄金, proMoney=1000.00, description=)
], prePage=0, nextPage=0, isFirstPage=true, isLastPage=true, hasPreviousPage=false, hasNextPage=false, navigatePages=8, navigateFirstPage=1, navigateLastPage=1, navigatepageNums=[1]}

查询

1)通过多个 id 批量查询

完成了动态sql的foreach的功能

1
2
3
4
5
6
7
//多个id批量查询
@Test
public void testSelect1() {
List<User> users=userMapper.selectBatchIds(
Arrays.asList(1, 2, 3));
System.out.println(users);
}

2)简单的条件查询

通过map封装查询条件

注意:map中的key对应数据库中的列名。如:数据库user_id,实体类是userId,这时map的key需要填写user_id

1
2
3
4
5
6
7
8
9
//简单条件查询
@Test
public void testSelect2() {
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("name","Jack");
columnMap.put("age",20);
List<User> users = userMapper.selectByMap(columnMap);
System.out.println(users);
}

PaginationInterceptor分页

MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能。

添加分页插件

配置类中添加@Bean配置

1
2
3
/** * 分页插件 */
@Bean
public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor();}
测试 selectPage 分页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//分页查询
@Test
public void testSelectPage() {
Page<User> page = new Page(1,3);
Page<User> userPage = userMapper.selectPage(page, null); //返回对象得到分页所有数据
long pages = userPage.getPages();
//总页数
long current = userPage.getCurrent();
//当前页
List<User> records = userPage.getRecords();
//查询数据集合
long total = userPage.getTotal();
//总记录数
boolean hasNext = userPage.hasNext();
//下一页
boolean hasPrevious = userPage.hasPrevious();
//上一页
System.out.println(pages);
System.out.println(current);
System.out.println(records);
System.out.println(total);
System.out.println(hasNext);
System.out.println(hasPrevious);
}
测试 selecMapsPage 分页

当指定了特定的查询列时,希望分页结果列表只返回被查询的列,而不是很多null值。

测试selectMapsPage分页,结果集是Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testSelectMapsPage() {
//Page不需要泛型
Page<Map<String, Object>> page = newPage<>(1, 5);
Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, null);
List<Map<String, Object>> records = pageParam.getRecords();
records.forEach(System.out::println);
System.out.println(pageParam.getCurrent());
System.out.println(pageParam.getPages());
System.out.println(pageParam.getSize());
System.out.println(pageParam.getTotal());
System.out.println(pageParam.hasNext());
System.out.println(pageParam.hasPrevious());
}

性能分析插件

性能分析拦截器,用于输出每条SQL语句及其执行时间,可以设置最大执行时间,超过时间会抛出异常,该插件只用于开发环境,不建议生产环境使用。在MP中提供了对SQL执行的分析的插件,可用作阻断全表更新、删除的操作。

注意:该插件仅适用于开发环境,不适用于生产环境。

1、配置springboot:SQL分析插件

1
2
3
4
5
6
7
8
<configuration>
<plugins>
<plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor">
<property name="maxTime" value="100"/>
<property name="format" value="true"/>
</plugin>
</plugins>
</configuration>

2、测试

1
2
3
4
5
6
7
@Test
public void testSelectById(){
User user = new User();
user.setId(2L);
User user1 = user.selectById();
System.out.println(user1);
}

条件构造器

Wrapper : 条件构造抽象类,最顶端父类

AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件

QueryWrapper : 查询条件封装

UpdateWrapper : Update 条件封装

AbstractLambdaWrapper : 使用Lambda 语法

LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper

LambdaUpdateWrapper : Lambda 更新封装Wrapper

1
2
3
4
5
6
7
8
9
QueryWrapper<user> wrapper = new QueryWrapper<>();
wrapper.notLike("name","e").likeRight("email","t");
List<Map<String,Object>> maps=userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
//id排序
QueryWrapper<user> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
List<User> users = userWrapper.selectList(wrapper);
users.forEach(System.out::println);

MBG逆向工程

正向工程:先创建java实体类,框架负责根据实体类生成数据库表。

逆向工程:先创建数据库表,框架根据表生成java实体类,mapper接口和映射文件。

代码生成器

1、依赖导入

1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>最新版本</version>
</dependency>

2、引入相应包

1
2
3
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.
GlobalConfig

3、创建一个代码自动生成器对象

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
AutoGenerator mpg = new AutoGenerator();
//全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath+"/src/main/java");
gc.setAuthor("xxx");
gc.setFileOverride(false);
gc.setOpen(false);
gc.setServiceName("%sService");//去前缀I
gc.getIdType(IdType.ID_WORKER);
gc.setDateType(Date.ONLY_DATE);
mpg.setGlobalConfig(gc);
//设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/xxx?
useSSL=false&useUnicode=utf8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.password("xxxxxx");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
//java源码包
PackageConfig pc = new PackageConfig();
pc.setModuleName("xxx");
pc.setParentName("com.xxx");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
//设置策略
StrateryConfig sc = new StrateryConfig();
sc.setInclude("xxx","xxx","xxx");//要映射的表名
sc.setNaming(NamingStratery.underline_to_camel);
sc.setColumnNaming(NamingStratery.underline_to_camel);
sc.setEntityLombokModel(true);//使用lombok
sc.setLogicDeleteFieldName("deleted");
//配置自动填充
TableFill xxCreate = new TableFill("xx_create",FiledFill.INSERT);
TableFill xxModified = new TableFill("xx_modified",FiledFill.INSERT);
ArrayList<TableFill> tfs = new ArrayList<>();
tfs.add(xxCreate);
tfs.add(xxModified);
sc.setTableFillList(tfs);
//乐观锁
sc.setVersionFieldName("version");
sc.setRestControllerStyle(true);//rest风格
//sc.setControllerMappingHyphenStyle(true);
mpg.setStratery(sc);
mpg.execute();

注意:以上为旧版本,mybatis-plus-generator 3.5.1 及其以上版本对历史版本不兼容!3.5.1 以上的请参考 代码生成器新相关配置

目前支持两套生成的方式,一套使用SQL查询的方式是兼容旧的代码生成器核心逻辑使用,另一套使用驱动规范来读取元数据的方式,默认的使用元数据查询方式来生成代码,

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
FastAutoGenerator.create("url", "username", "password")
.globalConfig(builder -> {
builder.author("xxx") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://"); // 指定输出目录
})
.dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
int typeCode = metaInfo.getJdbcType().TYPE_CODE;
if (typeCode == Types.SMALLINT) {
// 自定义类型转换
return DbColumnType.INTEGER;
}
return typeRegistry.getColumnType(metaInfo);

}))
.packageConfig(builder -> {
builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_xxx") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();