简介
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
实现方式:
- 基于 Mybatis需要编写 XXXMapper 接口,并手动编写 CRUD 方法提供 XXXMapper.xml 映射文件,并手动编写每个方法对应的 SQL 语句
- 基于 MP只需要创建 XXXMapper 接口, 并继承 BaseMapper
- 使用案例:
- applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 数据源 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 事务管理器 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 基于注解的事务管理 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
<!-- 配置 SqlSessionFactoryBean
Mybatis提供的:org.mybatis.spring.SqlSessionFactoryBean
MP提供的:com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean
-->
<bean id="sqlSessionFactoryBean" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!-- 别名处理 -->
<property name="typeAliasesPackage" value="com.zhou.beans"></property>
</bean>
<!-- 配置 mybatis 扫描 mapper 接口的路径 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.zhou.mapper"></property>
</bean>
</beans>
- Users 实体类:
@Data
@ToString
@AllArgsConstructor
@TableName("users")
public class Users {
/**
* @TableId:
* value: 指定表中的主键列的列名,如果实体属性名与列名一致,可以省略不指定
* type:指定主键策略
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
@TableField("email")
private String email;
}
- UsersMapper 接口:
/**
* Mapper接口
*基于Mybatis:在Mapper接口中编写CRUD相关的方法,提供Mapper接口所对应的SQL映射文件以及方法对应的SQL语句
*
* 基于MP:让UsersMapper接口继承BaseMapper接口即可。
* BaseMapper<T>:泛型指定的就是当前Mapper接口所操作的实体类类型
*/
public interface UsersMapper extends BaseMapper<Users> {
}
- 测试类:
public class MybatisPlusTest {
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
private UsersMapper usersMapper = ioc.getBean("usersMapper",UsersMapper.class);
@Test
public void saveUser(){
Users users = new Users(null,"Tommey周",10,"2523@qq.com");
int result = usersMapper.insert(users);
System.out.println("影响的行数:" + result);
}
xxxMapper 继承了 BaseMapper, BaseMapper 中提供了通用的 CRUD 方法,方法来源于 BaseMapper,有方法就必须有 SQL,因为 MyBatis 最终还是需要通过SQL 语句操作数据
通过现象看到本质
①,usersMapper 的本质 org.apache.ibatis.binding.MapperProxy
②,MapperProxy 中 sqlSession —> SqlSessionFactory
③, SqlSessionFacotry 中 —> Configuration→ MappedStatements
每一个 mappedStatement 都表示 Mapper 接口中的一个方法与 Mapper 映射文件中的一个 SQL。MP 在启动就会挨个分析 xxxMapper 中的方法,并且将对应的 SQL 语句处理好,保存到 configuration 对象中的 mappedStatements 中
条件构造器 AbstractWrapper
AbstractWrapper 的简介:
QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件,Mybatis-Plus 通过 AbstractWrapper(简称 EW,MP 封装的一个查询条件构造器) 来让用户自由的构建查询条件,简单便捷。
注意:使用的是数据库字段,不是 Java 属性!
条件参数说明:
查询方式 | 说明 |
---|---|
allEq | 基于map内容 = |
eq | 等于 = |
ne | 不等于 <> |
gt | 大于 > |
ge | 大于等于 >= |
lt | 小于 < |
le | 小于等于 <= |
between | BETWEEN 值1 AND 值2 |
notBetween | NOT BETWEEN 值1 AND 值2 |
like | 模糊查询like |
notLike | 模糊查询notLike |
likeLeft | LIKE ‘%值’ |
likeRight | LIKE ‘值%’ |
isNull | 字段 IS NULL |
isNotNull | 字段 IS NOT NULL |
in | 字段 IN (value.get(0), value.get(1), …),字段 IN (v0, v1, …) |
notIn | 字段 NOT IN (value.get(0), value.get(1), …),字段 NOT IN (v0, v1, …) |
inSql | 字段 IN ( sql语句 ) |
notInSql | 字段 NOT IN ( sql语句 ) |
groupBy | 分组:GROUP BY 字段, … |
orderByAsc | 排序:ORDER BY 字段, … ASC |
orderByDesc | 排序:ORDER BY 字段, … DESC |
orderBy | 排序:ORDER BY 字段, … |
having | HAVING ( sql语句 ) |
or | 拼接 OR |
and | AND 嵌套 |
nested | 正常嵌套 不带 AND 或者 OR |
apply | 拼接 sql,该方法可用于数据库函数 动态入参的params对应前面applySql内部的{index}部分.无sql注入风险的 |
last | 无视优化规则直接拼接到 sql 的最后,只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用 |
exists | 拼接 EXISTS ( sql语句 ) |
notExists | 拼接 NOT EXISTS ( sql语句 ) |
- 示例代码:
/**
* 条件构造器 查询操作
*/
@Test
public void entityWrapperSelectTest() {
//分页查询Users表中,年龄eq("name","Tommey周")在1-30之间的且姓名为Tommey周的
Page<Users> usersPage = usersMapper.selectPage(new Page<Users>(1, 1),
new QueryWrapper<Users>().eq("name", "Tommey周").between("age",1,30)
.or(i -> i.eq("age",18).ne("email","2523@qq.com")));
List<Users> users = usersPage.getRecords();
System.out.println(users);
}
ActiveRecord(活动记录)
Active Record(活动记录),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。
ActiveRecord 一直广受动态语言( PHP 、 Ruby 等)的喜爱,而 Java 作为准静态语言,对于 ActiveRecord 往往只能感叹其优雅,所以 MP 也在 AR 道路上进行了一定的探索
如何使用 AR ?
仅仅需要让实体类继承 Model 类且实现主键指定方法,即可开启 AR 之旅
/**
* MyBatisPlus 会默认使用实体类的类名到数据库中找对应的表
*/
@TableName("users")
public class Users extends Model<Users> {
/**
* @TableId:
* value: 指定表中的主键列的列名,如果实体属性名与列名一致,可以省略不指定
* type:指定主键策略
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
@TableField("email")
private String email;
......
@Override
protected Serializable pkVal() {
return this.id;
}
- AR 基本 CRUD
- 示例代码如下:
/**
* AR 插入操作
*/
@Test
public void insertUsersARTest(){
Users users = new Users(null,"Tommey周",10,"2523@qq.com");
boolean flag = users.insert();
System.out.println("是否插入成功:" + flag);
}
/**
* AR查询操作
*/
@Test
public void insertUsersARTest01(){
new Users().selectList(new QueryWrapper<Users>().like("name","Tom")).forEach(System.out::println);
}
- AR 小结:
- AR 模式提供了一种更加便捷的方式实现 CRUD 操作,其本质还是调用的 Mybatis 对应的方法,类似于语法糖
- 语法糖是指计算机语言中添加的某种语法,这种语法对原本语言的功能并没有影响,可以更方便开发者使用,可以避免出错的机会,让程序可读性更好
- 到此,我们简单领略了 Mybatis-Plus 的魅力与高效率,值得注意的一点是:我们提供了强大的代码生成器,可以快速生成各类代码,真正做到了即开即用
- AR 模式提供了一种更加便捷的方式实现 CRUD 操作,其本质还是调用的 Mybatis 对应的方法,类似于语法糖
代码生成器
public class CodeGeneratorTest {
/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 1,全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/MyBatisPlusDemo/src/main/java");//生成路径
gc.setAuthor("Tommey周"); //作者
gc.setActiveRecord(true); //是否支持AR
gc.setBaseColumnList(true);
gc.setBaseResultMap(true);
gc.setOpen(false);
gc.setIdType(IdType.AUTO); //主键策略
gc.setFileOverride(true); //文件覆盖
mpg.setGlobalConfig(gc);
//2,数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL);
dsc.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false&characterEncoding=utf8");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);
//3,包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("模块名"));
pc.setParent("com.zhou");
mpg.setPackageInfo(pc);
// 4,自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
// String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/MyBatisPlusDemo/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 5,配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
//6,策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);//数据表映射到实体的命名策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel); //数据库表列名
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new VelocityTemplateEngine());
mpg.execute();
}
}
插件扩展
插件机制:
Mybatis 通过插件(Interceptor) 可以做到拦截四大对象相关方法的执行,根据需求,完成相关数据的动态改变。
Executor
StatementHandler
ParameterHandler
ResultSetHandler插件原理
四大对象的每个对象在创建时,都会执行 interceptorChain.pluginAll(),会经过每个插件的 plugin()方法,目的是为当前的四大对象创建代理。代理对象就可以拦截到四大对象相关方法的执行,因为要执行四大对象的方法需要经过代理
com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor
applicationContext.xml配置中修改配置
<!-- 配置 SqlSessionFactoryBean
Mybatis提供的:org.mybatis.spring.SqlSessionFactoryBean
MP提供的:com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean
-->
<bean id="sqlSessionFactoryBean" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!-- 别名处理 -->
<property name="typeAliasesPackage" value="com.zhou.sys.entity"></property>
<property name="plugins">
<array>
<!-- 注册分页插件 -->
<bean class="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor">
<property name="countSqlParser" ref="countSqlParser"></property>
</bean>
</array>
</property>
</bean>
<bean id="countSqlParser" class="com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize">
<!-- 设置为 true 可以优化部分 left join 的sql -->
<property name="optimizeJoin" value="true"/>
</bean>
- 测试类:
/**
* 分页插件测试
*/
@Test
public void testPage(){
Page<Users> page = new Page<>(1,1);
Page<Users> users = usersMapper.selectPage(page,null);
System.out.println(users.getRecords());
System.out.println("=============获取分页相关的一些信息=============");
System.out.println("总页数"+page.getTotal());
System.out.println("当前页码"+page.getCurrent());
System.out.println("总页码"+page.getPages());
System.out.println("每页显示的条数"+page.getSize());
System.out.println("是否有上一页"+page.hasPrevious());
System.out.println("是否有下一页"+page.hasNext());
}
乐观锁:顾名思义十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题,再次更新值测试
悲观锁:顾名思义十分悲观,它总是认为总会出现问题,无论干什么都会上锁,再去操作
乐观锁主要适用场景
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败
配置spring中配置文件applicationContext.xml:
<bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/>
- 实体类中添加:
@Version
private Integer version;
- 测试:
@Test
public void testOptimisticLocker(){
//更新操作
Users users = new Users();
users.setId(6l);
users.setVersion(1);
users.setName("周");
usersMapper.updateById(users);
Users user = new Users();
user.setName("周周");
user.setEmail("aa@qq.com");
users.setVersion(1);
usersMapper.updateById(user);
}
我们在平时的开发中,会遇到一些慢sql。测试!
作用:性能分析拦截器,用于输出每条 SQL 语句及其执行时间MP也提供性能分析插件,如果超过这个时间就停止运行!
SpringBoot环境下导入插件(要在SpringBoot中配置环境为dev或者 test 环境):
/**
* SQL执行效率插件
*/
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启,保证我们的效率
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100); // ms设置sql执行的最大时间,如果超过了则不执行
performanceInterceptor.setFormat(true); // 是否格式化代码
return performanceInterceptor;
}
说明:只对自动注入的sql起效
插入: 不作限制
查找:追加where条件过滤掉已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
更新:追加where条件防止更新到已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
删除: 转变为 更新
示例代码:
配置:
com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig
application.yml:
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
- 实体类:
@TableLogic
private Integer deleted;
- 测试类:
/**
* 测试逻辑删除
*/
@Test
public void logicDeleteTest(){
Integer result = usersMapper.deleteById(1l);
System.out.println(result);
Users users = usersMapper.selectById(1l);
System.out.println(users);
}
metaobject:元对象. 是 Mybatis 提供的一个用于更加方便,更加优雅的访问对象的属性,给对象的属性设置值 的一个对象, 还会用于包装对象,支持对 Object 、Map、Collection等对象进行包装本质上 metaObject 获取对象的属性值或者是给对象的属性设置值,最终是要通过 Reflector 获取到属性的对应方法的 Invoker, 最终 invoke
示例代码:
实现元对象处理器接口:
com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
注解填充字段:
public class User {
// 注意!这里需要标记为填充字段
@TableField(.. fill = FieldFill.INSERT)
private String fillField;
....
}
/**
* 自定义公共字段填充处理器
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入操作 自动填充
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("insert,{ }", "开始了");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
/**
* 修改操作,自动填充
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("update,{ }", "开始了");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
IDEA 快速开发插件
MybatisX 辅助 idea 快速开发插件,为效率而生,可以实现 java 与 xml 跳转,根据 Mapper 接口中的方法自动生成 xml 结构
官方安装: File -> Settings -> Plugins -> Browse Repositories 输入 mybatisx 安装下载