Mybatis plus
概序
原先写 crud,需要在 xml 配置或注解中写 sql 语句,用了 MyBatisPlus 后,对单表进行 crud 无需再写 sql 语句
简单的增删改查还行,不支持多表操作,一般不在公司里使用,适合个人快速开发和偷懒使用
简介:简化 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
引入依赖
在 Spring Boot 引入
<!--mybatis-plus的spring boot支持-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--mysql的spring boot支持-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>配置日志
我们所有的 sql 现在是不可见的,我们希望知道他是怎么执行的,所以我们必须要看日志!
# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl实体类注解
@TableName
指定实体类对应数据库表名,如果实体类和数据库表名不一致,就开启注解进行配置,两者一致可以不开启
放在实体类的上方
@TableId
指定实体类的属性对应数据库的 ID,放在属性上方
属性值:
- value:字段值(驼峰命名方式,该值可无)
- type:主键 ID 类型(用法:@TableId(type = IdType.AUTO))
- AUTO:数据库 ID 自增(常用)
- NONE:该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
- INPUT:用户输入 ID,该类型可以通过自己注册自动填充插件进行填充
- 其他
@TableFiled
属性值进行除 ID 外的其他设置
解决问题:
对象中的属性名和字段名不一致的问题(非驼峰)
对象中的属性名和字段名不一致的问题(非驼峰)
属性值:
- exits:是否为数据库表字段,实体类存在但是数据库不存在的属性值,默认 true 存在,false 不存在
- select:是否进行 select 查询,可设置为 false 不加入 select 查询范围
- fill:填充,当属性指定为 null 时,配合 FieldFill 枚举类,可自动填充指定默认值,具体看大纲
- condition:字段 where 实体查询比较条件 默认
=等值 - 其他
@Version
当要更新一条记录的时候,希望这条记录没有被别人更新,通过进行 version 判断
具体看大纲 的
@TableLogic
删除数据,通过该注解标识的属性值对应的字段值被标记为删除,而并非真正的物理删除
具体看大纲
注解例子:
@TableName("tb_user")
public class User extends Model<User> { // ActiveRecord
// 开启ID 自增,否则mybatis-plus自动创建随机ID插入数据库
@TableId(type = IdType.AUTO)
private Long id;
//映射数据库字段,解决字段名不一致
@TableField("user_name")
private String userName;
// 该注解:查询时候返回null,防止获得该字段信息
@TableField(select = false,fill = FieldFill.INSERT)
private String password;
private String name;
private Integer age;
private String email;
//该注解:如果该属性在数据库不存在,所以需要开启注解,否则插入报错
@TableField(exist = false)
private String phone;
@Version
private Integer Version;
@TableLogic
private Integer deleted;
}通用 CRUD
所有方法用到的一个类,该类继承了 MybatisPlus 提供的一个基础类,并且绑定实体类,可以通过该类实现 sql 语句
@Repository
@Mapper
public interface UserMapper extends BaseMapper<User> {
}@Autowired
private UserMapper userMapper;插入操作
insert():参数为一个实体类
需求
插入 User 数据
javapublic void insert(){ User user = new User(); user.setAge(20); user.setEmail("test@itcast.cn"); user.setName("可乐"); user.setUserName("kele"); user.setPassword("123456"); int result = userMapper.insert(user); //返回受影响的行数 System.out.println("插入了:" + result + "条数据"); System.out.println("插入后返回的自增ID:" + user.getId()); //自增的id会返回到User对象里 }
删除操作
deleteById():参数为 ID 值,删除失败不会报错
需求
删除 ID 为 8 的数据
javapublic void deleteById(){ int result = userMapper.deleteById(8L); System.out.println("处理行数:" + result); }deleteMap():参数为 Map 类型,key 为字段名,value 为字段对应的值,删除失败不会报错
需求
用 Map 方式删除用户名为可乐,年龄为 20 的数据
javapublic void dleteByMap(){ HashMap<String,Object> columnMap = new HashMap<>(); // key为字段名,value为判断的值 columnMap.put("name", "可乐"); columnMap.put("age", 20); int result = userMapper.deleteByMap(columnMap); // 删除用户名为可乐,年龄为20的数据 System.out.println("处理行数:" + result); }delete():参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等
需求
用实体类方式删除用户名为可乐,年龄为 20 的数据
javapublic void deleteByEntry(){ User user = new User(); user.setName("可乐"); user.setAge(20); //将实体对象进行包装,包装为操作条件 QueryWrapper<User> wrapper = new QueryWrapper<>(user); int result = userMapper.delete(wrapper); // 删除用户名为可乐,年龄为20的数据 System.out.println("处理行数:" + result); }deleteBatchIds:参数为 ID 的集合,根据 ID 集合批量删除数据
需求
批量删除 ID 为 10,11,12 的数据
javapublic void deleteByBatch(){ int result = userMapper.deleteBatchIds(Arrays.asList(10L,11L,12L));//删除ID为10,11,12的数据 System.out.println("处理行数:" + result); }
修改操作
updateById():参数为实体类,实体类内部需要封装好 ID 值,以及修改的信息内容
需求
把 ID 为 6 的密码修改为 1111
javapublic void updateById(){ User user = new User(); user.setId(6L); user.setPassword("1111"); int result = userMapper.updateById(user);// 修改ID为6的密码为1111 System.out.println("处理行数:" + result); }update():参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等
需求
以 QueryWrapper 方式把 ID 为 6 的年龄修改为 18
javapublic void updateByQueryWrapper(){ User user = new User(); user.setAge(18); QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("id", 6); int result = userMapper.update(user, wrapper); // 修改ID为6的年龄为18 System.out.println("处理行数:" + result); }update():参数为 UpdateWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等
- 与 QueryWrapper<>区别:不需要指定实体类,直接在对象后面加入修改的字段名和值
需求
以 UpdateWrapper 方式把 ID 为 6 的年龄修改为 18
javapublic void updateByUpdateWrapper(){ UpdateWrapper<User> wrapper = new UpdateWrapper<>(); wrapper.eq("id", 6).set("age", 19); int result = userMapper.update(null, wrapper); System.out.println("处理行数:" + result); }
查询操作
selectById():参数为 ID 值,通过 ID 查询数据,返回一条数据
需求
查询 ID 为 6 的数据
javapublic void selectById(){ User user = userMapper.selectById(6L); System.out.println(user); }selectOne():参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等,返回一条数据
需求
查询名字为赵六的数据
javapublic void selectOne(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("name", "赵六"); User user = userMapper.selectOne(wrapper); System.out.println(user); }selectList():参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等,返回多条数据
需求
查询年龄大于 20 的多条数据
javapublic void selectList(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.gt("age",20); List<User> users = userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } }selectCount():参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等,返回数据总数
需求
查询年龄大于 20 的数据总数
javapublic void selectCount(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.gt("age", 20); Integer count = userMapper.selectCount(wrapper); System.out.println("数据库数据总数为:" + count); }selectBatchIds():参数为 ID 的集合,根据 ID 批量查询数据,只返回存在的 ID 数据
需求
批量查询 ID 为 1,2,6,22 的数据
javapublic void selectBatchIds(){ List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 6L,22L)); for (User user : users) { System.out.println(user); } }selectPages():分页查询,参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等
需要使用 MybatisPlus 的分页插件 Pages,并且需要开启该插件
java@Configuration public class MybatisPlusPageConfig { /** * 分页插件配置 */ @Bean public PaginationInterceptor paginationInterceptor(){ return new PaginationInterceptor(); } }
需求
查询年龄大于 20 的第一页的一条数据
javapublic void selectPages(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.gt("age", 20); Page<User> page = new Page<>(); page.setCurrent(1); page.setSize(1); IPage<User> iPage = userMapper.selectPage(page, wrapper); System.out.println("数据总条数:" + iPage.getTotal()); System.out.println("总页数:" + iPage.getPages()); List<User> users = iPage.getRecords(); for (User user : users) { System.out.println("分页查询的数据:" + user); } }
基本配置
configLocation
MyBatis 配置文件位置,如果您有单独的 MyBatis 配置,请将其路径配置到 configLocation 中。 MyBatis Configuration 的具体内容请参考 MyBatis 官方文档
Spring Boot:
xmlmybatis-plus.config-location = classpath:mybatis-config.xmlSpring MVC:
xml<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean>
mapperLocations
MyBatis Mapper 所对应的 XML 文件位置,如果您在 Mapper 中有自定义方法(XML 中有自定义实现),需要进行 该配置,告诉 Mapper 所对应的 XML 文件位置。
Spring Boot:
xmlmybatis-plus.mapper-locations = classpath*:mybatis/*.xmlSpring MVC:
xml<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"> <property name="mapperLocations" value="classpath*:mybatis/*.xml"/> </bean>
typeAliasesPackage
MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件中可以直接使 用类名,而不用使用全限定的类名(即 XML 中调用的时候不用包含包名)。
Spring Boot:
xmlmybatis-plus.type-aliases-package = com.eight.entrySpring MVC:
xml<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"> <property name="typeAliasesPackage" value="com.baomidou.mybatisplus.samples.quickstart.entity"/> </bean>
进阶配置
mapUnderscoreToCamelCase
- 类型:boolean
- 默认值:true
是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属 性名 aColumn(驼峰命名) 的类似映射。
注意:
- 此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body 如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名
- 该参数不能和 mybatis-plus.config-location 同时存在,如果两者都有,需要把该配置放入 mybatis-plus.config-location 里
# 关闭自动驼峰映射,该参数不能和mybatis-plus.config-location同时存在
mybatis-plus.configuration.map-underscore-to-camel-case=falsecacheEnabled
- 类型:boolean
- 默认值:true
全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为 true。
mybatis-plus.configuration.cache-enabled=falseDB 策略配置
idType
- 类型:
com.baomidou.mybatisplus.annotation.IdType - 默认值:
ID_WORKER
全局默认主键类型,设置后,即可省略实体对象中的@TableId(type = IdType.AUTO)配置。
Spring Boot:
mybatis-plus.global-config.db-config.id-type=autoSpring MVC:
!--这里使用MP提供的sqlSessionFactory,完成了Spring与MP的整合-->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="globalConfig">
<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
<property name="dbConfig">
<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
<property name="idType" value="AUTO"/>
</bean>
</property>
</bean>
</property>
</bean>tablePrefix
类型:String
默认值:null
表名前缀,全局配置后可省略@TableName()配置
Spring Boot:
ybatis-plus.global-config.db-config.table-prefix=tb_ #自定义实体类的前缀,尽量统一规则,匹配数据库表Spring MVC:
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="globalConfig">
<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
<property name="dbConfig">
<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
<property name="idType" value="AUTO"/>
<property name="tablePrefix" value="tb_"/>
</bean>
</property>
</bean>
</property>
</bean>QueryWrapper 使用
条件构造器
allEq
API:
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull) 说明:
- params:Map 类型,key 为数据库字段名,value 为字段值
- null2IsNull:true 时,params 传入 null 的时候,调用 xxx is null,false 时,忽略 value 为 null 的数据
API:
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull) 说明:
filter:过滤函数,是否允许字段传入比对条件中 params
虽然传入的 params 有很多 key,但是只想以一两个 key 作为查询条件,用这个过滤
例(假设 params 里有 name、age、password,只需要查询 name 即可,即 where 后面只有 name = ?):
wrapper.allEq((k, v) -> (k.equals("name")),params);
例子:
public void testWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//设置条件
Map<String,Object> params = new HashMap<>();
params.put("name", "曹操");
params.put("age", "20");
params.put("password", null);
// SELECT * FROM tb_user WHERE password IS NULL AND name = ? AND age = ?
wrapper.allEq(params);
// SELECT * FROM tb_user WHERE name = ? AND age = ?(没有password is NULL)
wrapper.allEq(params,false);
// SELECT * FROM tb_user WHERE name = ? AND age = ?
wrapper.allEq((k, v) -> (k.equals("name") || k.equals("age")),params);
List<User> users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}基本比较操作
- eq:等于 =
- ne:不等于 <>
- gt:大于 >
- ge:大于等于 >=
- lt:小于 <
- le:小于或等于 <=
- between:between b AND c,在 b 和 c 之间的数据
- notBetween:notBetween b AND c,不在 b 和 c 之间的数据
- in: IN (key,value1,value2,...),查询 key 分别等于 value1 和 value2 的数据
- notIn:NOT IN (key,value1,value2,...),查询 key 分别不等于 value1 和 value2 的数据
public void testEq() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//SELECT id,user_name,password,name,age,email FROM tb_user WHERE password = ? AND age >= ? AND name IN (?,?,?)
wrapper.eq("password", "123456")
.ge("age", 20)
.in("name", "李四", "王五", "赵六"); // 查询name 分别等于 李四,王玉,赵六的数据
List<User> users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}模糊查询
like
- LIKE '%值%'
- 例:
like('name',"王"),执行的 SQL:name like '%王%'
notLike
- NOT LIKE '%值%'
- 例:
notLike('name',"王"),执行的 SQL:name not like '%王%'
likeLeft
- LIKE '%值'
- 例:
likeLeft('name',"王"),执行的 SQL:name not like '%王'
likeRight
- LIKE '值%'
- 例:
likeRight('name',"王"),执行的 SQL:name not like '王%'
javapublic void testWrapper() { QueryWrapper<User> wrapper = new QueryWrapper<>(); // SELECT id,user_name,password,name,age,email FROM tb_user WHERE name LIKE ? // Parameters: %曹%(String) wrapper.like("name", "曹"); List<User> users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } }
排序
orderBy(ASC 排序)
- 例:
orderBy(true, true, "id", "name"),执行的 SQL:order by id ASC,name ASC
- 例:
orderByAsc
- 例:
orderByAscc("id", "name"),执行的 SQL:order by id ASC,name ASC
- 例:
orderByDesc
- 例:
orderByDesc("id", "name"),执行的 SQL:order by id DESC,name DESC
javapublic void testWrapper() { QueryWrapper<User> wrapper = new QueryWrapper<>(); //SELECT id,user_name,password,name,age,email FROM tb_user ORDER BY age DESC wrapper.orderByDesc("age"); List<User> users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } }- 例:
逻辑查询
or
- 拼接 OR
- 主动调用 or 表示紧接着下一个方法不是用 and 连接!(不调用 or 则默认为使用 and 连接)
and
- AND 嵌套
- 默认使用 and
javapublic void testWrapper() { QueryWrapper<User> wrapper = new QueryWrapper<>(); //SELECT id,user_name,password,name,age,email FROM tb_user WHERE name = ? OR age = ? wrapper.eq("name","李四").or().eq("age", 24); List<User> users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } }
select
在 MP 查询中,默认查询所有的字段,如果有需要也可以通过 select 方法进行指定字段。
需要在 QueryWrapper 对象使用
public void testWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//SELECT id,name,age FROM tb_user WHERE name = ? OR age = ?
wrapper.eq("name", "李四")
.or()
.eq("age", 24)
.select("id", "name", "age"); // 只查询id,name,age字段的数据
List<User> users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}ActiveRecord
什么是 ActiveRecord
ActiveRecord 也属于 ORM(对象关系映射)层,由 Rails 最早提出,遵循标准的 ORM 模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。
主要思想
每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录;通常表的每个字段 在类中都有相应的 Field; ActiveRecord 同时负责把自己持久化,在 ActiveRecord 中封装了对数据库的访问,即 CURD;; ActiveRecord 是一种领域模型(Domain Model),封装了部分业务逻辑;
AR 之旅
让实体类继承 Model,并传入该实体类
public class User extends Model<User>{
// ......
// 指定 ID
@TableId(type = IdType.AUTO)
private Long id;
// ......
} 和通用 CRUD 方法一样,唯一区别就是不需要使用 mapper 类调用方法,直接使用实体类调用方法,但是实体类关联 mapper 类去掉,其实底层还是会去对应的 mapper 类调用 CRUD
selectById():根据主键查询数据
javapublic void testAR() { User user = new User(); user.setId(2L); User user2 = user.selectById(); System.out.println(user2); }insert():新增数据,成功返回 true,失败返回 false
javapublic void testAR() { User user = new User(); user.setName("刘备"); user.setAge(30); user.setPassword("123456"); user.setUserName("liubei"); user.setEmail("liubei@itcast.cn"); boolean insert = user.insert(); //成功返回true,失败返回false System.out.println(insert); }updateById():数据更新,成功返回 true,失败返回 false
javapublic void testAR() { User user = new User(); user.setId(8L); user.setAge(35); boolean update = user.updateById(); System.out.println(update); }deleteById():数据删除,成功返回 true,失败返回 false
javapublic void testAR() { User user = new User(); user.setId(7L); boolean delete = user.deleteById(); System.out.println(delete); }selectList():多条数据查询
javapublic void testAR() { User user = new User(); QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.le("age","20"); // 小于或者等于 List<User> users = user.selectList(userQueryWrapper); for (User user1 : users) { System.out.println(user1); }其他方法请看,mapper 类只需换成实体类即可
Oracle 主键 Sequence
Spring Boot 框架下
创建序列
--创建序列
CREATE SEQUENCE SEQ_USER START WITH 1 INCREMENT BY 1驱动包
由于版权原因,不能直接通过 maven 的中央仓库下载 oracle 数据库的 jdbc 驱动包,所以需要将驱动包安装到本地 maven 仓库,需要将驱动包放在 C 盘以外的其他盘的文件夹里,并且打开 cmd 进入当前文件夹操作
DgroupId:maven 的 groupId
DartifactId:maven 的 artifactId
Dversion:maven 的 version
Dfile:当前 cmd 目录下的 jar 包名
mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=12.1.0.1 -
Dpackaging=jar -Dfile=ojdbc6.jar引用坐标
dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>12.1.0.1</version>
</dependency>配置文件策略
Oracle 的数据库连接配置
开启 ID 生成策略(和 Mysql 不同,需要把 auto 改为 input)
- 原理:通过配置寻找配置好的序列(下方),input 代表主动把配置好的序列匹配到表的 ID,实现 ID 自增
properties#数据库连接配置 spring.datasource.driver-class-name=oracle.jdbc.OracleDriver spring.datasource.url=jdbc:oracle:thin:@192.168.31.81:1521:kele spring.datasource.username=system spring.datasource.password=oracle #id生成策略 mybatis-plus.global-config.db-config.id-type=input
配置序列
在配置类里进行序列配置,添加 Bean 注解,自动放入容器中
Oracle 序列配置的代码:
java@Configuration public class MybatisPlusConfig { /** * 分页插件配置 */ @Bean public PaginationInterceptor paginationInterceptor(){ return new PaginationInterceptor(); } /** * Oracle序列配置 */ @Bean public OracleKeyGenerator oracleKeyGenerator(){ return new OracleKeyGenerator(); } // .... 其他插件配置的位置 }在实体对象中指定序列的名称:
- value:对应数据库的序列名字
- clazz:自增的 ID 类型
java@KeySequence(value = "SEQ_USER", clazz = Long.class) public class User{ // ...... }
插件
插件机制
作用
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。
操作
默认情况下,MyBatis 允许使用插件来拦截的方法调用包括
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
拦截执行器(SQL)的方法,如 update,query,commit,rollback 等方法,还有其他接口的 一些方法等。
ParameterHandler (getParameterObject, setParameters)
拦截参数的处理
ResultSetHandler (handleResultSets, handleOutputParameters)
拦截结果集的处理 4. 拦截 Sql 语法构建的处
StatementHandler (prepare, parameterize, batch, update, query)
拦截 Sql 语法构建的处理
插件配置
自定义拦截器(插件)继承 Mybatis 的 Interceptor 类
流程先经过 intercept 方法,然后经历四次 plugin 方法(该方法内部执行上方的四次方法),最后完成退出该类
记得使用注解@Configuration 或者方法上使用@Bean 把该类的对象注入到 spring 容器
我们看到了可以拦截 Executor 接口的部分方法,比如 update,query,commit,rollback 等方法,还有其他接口的 一些方法等。如下只拦截了 update 方法,可以加入业务逻辑,如逻辑删除 version 的处理,如果仅使用插件,可不使用,源码已经有实现插件使用
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//拦截方法,具体业务逻辑编写的位置
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
if (invocation.getArgs()[1] instanceof Pojo) {
Pojo parameter = (Pojo) invocation.getArgs()[1];
if (SqlCommandType.INSERT == sqlCommandType) {
// 初始化主键ID
Method initializeUUID = BeanUtils.findDeclaredMethod(parameter.getClass(),"initializeUUID");
if (null != initializeUUID) {
initializeUUID.invoke(parameter);
}
if(null==parameter.getCreator()){
parameter.setCreator(SessionHelper.getId());
}
parameter.setUpdater(SessionHelper.getId());
parameter.setCreatedDate(new Date());
parameter.setUpdatedDate(parameter.getCreatedDate());
parameter.setVersion(1);
} else if (SqlCommandType.UPDATE == sqlCommandType) {
parameter.setUpdater(SessionHelper.getId());
parameter.setUpdatedDate(new Date());
parameter.setVersion(parameter.getVersion() + 1);
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
//创建target对象的代理对象,目的是将当前拦截器加入到该对象中
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
//属性设置
if (null != properties && !properties.isEmpty()){
props = properties;
}
}
}在自定义配置类里注入自定义的拦截器(插件)到 spring 容器
public class MybatisPlusConfig {
// 把自己注入到spring容器
@Bean
public MyInterceptor myInterceptor(){
return new MyInterceptor();
}
}或者通过 xml 配置,mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<plugin interceptor="cn.itcast.mp.plugins.MyInterceptor"></plugin>
</plugins>
</configuration>执行分析插件
作用
在 MP 中提供了对 SQL 执行的分析的插件,可用作,注意:该插件仅适用于开发环境,不适用于生产环境。
操作
SpringBoot 配置:(可以放到如上面的自定义配置类(MybatisPlusConfig)里,然后加入@Bean 注解放入 Spring 容器)
- 创建 SqlExplainInterceptor 对象,该对象需要 ISqlParser 的集合
- 创建 sqlParserList 集合,该集合需要解析器或解析链
- 创建解析器或解析链(
new BlockAttackSqlParser()),放入集合 - 将 sqlParserList 集合放入 SqlExplainInterceptor 对象,并返回 spring 容器
@Bean
public SqlExplainInterceptor sqlExplainInterceptor(){
SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
// 攻击 SQL 阻断解析器、加入解析链
sqlParserList.add(new BlockAttackSqlParser());
sqlExplainInterceptor.setSqlParserList(sqlParserList);
return sqlExplainInterceptor;
}性能分析插件
作用
性能分析拦截器,用于输出每条 SQL 语句及其执行时间,可以设置最大执行时间(毫秒),超过时间会抛出异常。也可以指定 SQL 是否格式化 ,默认 false,不格式化。(该插件存在于 3.2.0 版本之前,3.2.0 版本之后被移出)
操作
SpringBoot 配置:(可以放到如上面的自定义配置类(MybatisPlusConfig)里,然后加入@Bean 注解放入 Spring 容器)
@Bean
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
// maxTime 指的是 sql 最大执行时长
performanceInterceptor.setMaxTime(100);
// SQL是否格式化 默认false
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}mybtais.config.xml 文件配置:
?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<!-- SQL 执行性能分析,开发环境使用,线上不推荐 -->
<plugin
interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor">
<!-- maxTime 指的是 sql 最大执行时长 -->
<property name="maxTime" value="100" />
<!-- SQL是否格式化 默认false -->
<property name="format" value="true" />
</plugin>
</plugins>
</configuration>乐观锁插件
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
取出记录时,获取当前 version
更新时,带上这个 version
执行更新时, set version = newVersion where version = oldVersion
如果 version 不对,就更新失败
spring 配置:
<bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/>spring boot 配置:(可以放到如上面的自定义配置类(MybatisPlusConfig)里,然后加入@Bean 注解放入 Spring 容器)
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}实体字段 Version
需要为实体字段添加@Version 注解。且数据库存有 version 字段
原理
数据更新后,version 值都会自动加 1,
每次更新数据时,先从数据库拿到字段 version 值,然后进行更新,如果拿到的字段 version 不是最新的(拿到 version 后别人修改数据了,version 值已经自动加 1),因为拿到的 version 不是最新的,所以修改失败,
操作
为表添加 version 字段,并且设置初始值为 1
为 User 实体对象添加 version 字段,并且添加@Version 注解
java// ...... @Version private Integer version; // ......使用
javapublic void testUpdate(){ User user = new User(); user.setAge(30); user.setId(2L); User user1 = user.selectById(); user.setVersion(user1.getVerson); //获取到version为1 int result = this.userMapper.updateById(user); System.out.println("result = " + result); }
特别说明
支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
整数类型下 newVersion = oldVersion + 1
newVersion(新的 version 值) 会回写到 entity 中
仅支持 updateById(id) 与 update(entity, wrapper) 方法
在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
Sql 注入器
问题:如果 BaseMapper 的方法不符合需求,需要扩充自定义方法,如何实现?
需求:扩充自定义方法 queryAllUser()
自定义接口 MyBaseMapper
编写自定义接口,继承 BaseMapper,写需要的扩充自定义方法 queryAllUser()或者其他方法
@Repository
@Mapper
public interface MyBaseMapper extends BaseMapper<User> {
List<User> queryAllUser();
}自定义类 MySqlInjector
编写自定义类,继承 DefaultSqlInjector,重写 getMethodList 方法进行扩展
DefaultSqlInjector 是 AbstractSqlInjector 的子类,为什么自定义类不直接继承 AbstractSqlInjector 呢
如果自定义类直接继承 AbstractSqlInjector,原有的 BaseMapper 中的方法将失效,只能使用自定义的方法
创建 AbstractMethod 类的集合,先把父类(DefaultSqlInjector)的所有方法(框架自带的方法)加入,再把自定义类 queryAllUser 加入
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> abstractMethods =super.getMethodList(mapperClass);
abstractMethods.add(new QueryAllUser());
return abstractMethods;
}
}自定义类 queryAllUser
- 该类需要实现需求的自定义方法的逻辑和 SQL 语句
- 继承 AbstractMethod 类,重写 injectMappedStatement 方法,该方法就是实现逻辑和 SQL 语句,最后 return 出去
- MySqlInjector 类添加该类,获取该类的方法,实现需求的扩充自定义方法 queryAllUser()
- 注意:return 的
addSelectMappedStatementForTable是 select 语句,其他方法为addXXXStatement,其中 XXX 就是增删改
public class QueryAllUser extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sqlMethod = "queryAllUser";
String sql = "select * from " + tableInfo.getTableName();
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return addSelectMappedStatementForTable(mapperClass, sqlMethod, sqlSource, tableInfo);
}
}注册到 Spring 容器
- 将自定义类 MySqlInjector 注册到 Spring 容器
- 方法一:通过@Component 注解直接注册
- 方法二:自定义一个配置类,该类创建一个方法,返回 MySqlInjector 的对象,记得方法上放添加@Bean 注解实现注册
@Configuration
public class MybatisPlusPageConfig {
// ....其他插件
@Bean
public MySqlInjector sqlInjector(){
return new MySqlInjector();
}
// ....其他插件
}自动填充功能
插入或者更新数据时,希望有些字段可以自动填充数据,比如密码、version 等。在 MP 中提供了这样的功能,可以实现自动填充。
就是类似于数据库的 default:默认值
添加@TableField 注解
为 password 添加自动填充功能,在新增数据时有效。
@TableField(fill = FieldFill.INSERT) //插入数据时进行填充
private String password;FieldFill 多种选择:
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入时填充字段
*/
INSERT,
/**
* 更新时填充字段
*/
UPDATE,
/**
* 插入和更新时填充字段
*/
INSERT_UPDATE
}自定义类 MyMetaObjectHandle
自定义类继承 MetaObjectHandler,重写 insertFill 和 updateFill 方法
insertFill():实体类添加注解的属性,在插入时执行该方法
updateFill():实体类添加注解的属性,在更新时执行该方法
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入时填充
@Override
public void insertFill(MetaObject metaObject) {
Object password = getFieldValByName("password", metaObject);
if(null == password){
//字段为空,可以进行填充
setFieldValByName("password", "123456", metaObject);
}
}
// 更新时填充
@Override
public void updateFill(MetaObject metaObject) {
}
}逻辑删除
开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,所谓逻辑删除就是将数据标记为删除,而并非真正的物理删除(非 DELETE 操作),查询时需要携带状态条件,确保被标记的数据不被查询到。这样做的目的就是避免 数据被真正的删除。
为表添加 deleted 字段,用于表示数据是否被删除,1 代表删除,0 代表未删除。
实体类增加 deleted 属性并且添加@TableLogic 注解
java@TableLogic private Integer deleted;
Spring Boot 配置:
# 逻辑已删除值(默认为 1)
mybatis-plus.global-config.db-config.logic-delete-value=1
# 逻辑未删除值(默认为 0)
mybatis-plus.global-config.db-config.logic-not-delete-value=0 当删除某数据时,只是把该数据的 deleted 字段值改为 1,不是真的删除数据
当查询某数据时,数据字段 deleted 为 1 的数据不会查询出来
通用枚举
如性别在数据库值为 1 或者 2 代表男女,使用通用枚举,解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!
定义枚举类,继承 IEnum 类,重写 getValue 和 toString 方法
javapublic enum SexEnum implements IEnum<Integer> { MAN(1,"男"), WOMAN(2,"女"); private int value; private String desc; SexEnum(int value, String desc) { this.value = value; this.desc = desc; } @Override public Integer getValue() { return this.value; } @Override public String toString() { return this.desc; } }Spring Boot 配置
properties# 枚举包扫描 mybatis-plus.type-enums-package=com.eight.enums操作(插入、查询、条件查询)
java// 插入 public void testInsert(){ User user = new User(); user.setName("貂蝉"); user.setUserName("diaochan"); user.setAge(20); user.setEmail("diaochan@itast.cn"); user.setVersion(1); user.setSex(SexEnum.WOMAN); // 枚举类 int result = this.userMapper.insert(user); // 把枚举类的key传入数据库 System.out.println("result = " + result); } // 查询 public void testSelectById(){ User user = this.userMapper.selectById(2L); System.out.println(user); } // 条件查询 public void testSelectBySex() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("sex", SexEnum.WOMAN); List<User> users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } }插入时,把枚举类的 key 作为值传给数据库
查询时,先获取数据库的值,根据值(key)获取对应的 value,再返回
代码生成器
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
引入依赖
xml<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.1.1</version> </dependency>例子
javaimport com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.StringUtils; 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.FileOutConfig; 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.TemplateConfig; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import java.util.ArrayList; import java.util.List; import java.util.Scanner; /** * @Author Kele-Bing * @Create 2021/8/30 14:34 * @Version 1.0 * @Describe mysql 代码生成器演示例子 */ public class MysqlGenerator { /** * 读取控制台内容 */ 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.isNotBlank(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } /** * main函数 */ public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); // 获取用户路径 String projectPath = System.getProperty("user.dir"); // 设置生成文件的位置 gc.setOutputDir(projectPath + "/src/main/java"); // 设置作者 gc.setAuthor("kele-Bing"); // 是否打开资源管理器 gc.setOpen(false); // 是否覆盖原来的文件 gc.setFileOverride(false); // 设置日期类型 gc.setDateType(DateType.ONLY_DATE); // 将配置放到自动生成器中 mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/kele?useSSL-=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); // 设置数据库类型 dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName(scanner("模块名")); pc.setParent("com.eight.generator"); // 设置文件位置,默认值为下面,可以不写 pc.setEntity("entity"); pc.setMapper("mapper"); pc.setService("service"); pc.setController("controller"); mpg.setPackageInfo(pc); // 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; List<FileOutConfig> focList = new ArrayList<>(); focList.add(new FileOutConfig("/templates/mapper.xml.ftl") { @Override public String outputFile(TableInfo tableInfo) { /* * 自定义输入文件名称 * /项目名/src/main/resources/xml文件的包名/ */ return projectPath + "/mybatis-plus/src/main/resources/mapper/" + pc.getModuleName() +"/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); mpg.setTemplate(new TemplateConfig().setXml(null)); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); // strategy.setSuperEntityClass("com.baomidou.mybatisplus.samples.generator.common.BaseEntity"); // 是否使用Lombok注解 strategy.setEntityLombokModel(true); // strategy.setSuperControllerClass("com.baomidou.mybatisplus.samples.generator.common.B aseController"); // 需要映射的表名 strategy.setInclude(scanner("表名")); strategy.setSuperEntityColumns("id"); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(pc.getModuleName() + "_"); // 设置驼峰命名法 strategy.setRestControllerStyle(true); mpg.setStrategy(strategy); // 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有! mpg.setTemplateEngine(new FreemarkerTemplateEngine()); //最终执行 mpg.execute(); } }
总结
配置多个自定义类,放到 spring 容器的两者方法
- 直接在类上方加入@Component 注解(分批处理,快捷)
- 创建一个核心配置类(上方加入@Component 注解),然后在类里创建不同的方法,不同的方法 return 不同的自定义类对象,记得再方法上加入@Bean 注解。(统一处理,代码阅读性强)