Mybatis plus
# 概序
原先写crud,需要在xml配置或注解中写sql语句,用了MyBatisPlus后,对单表进行crud无需再写sql语句
简单的增删改查还行,不支持多表操作,一般不在公司里使用,适合个人快速开发和偷懒使用
官网:https://baomidou.com/
简介:简化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>
2
3
4
5
6
7
8
9
10
11
12
# 配置日志
我们所有的sql现在是不可见的,我们希望知道他是怎么执行的,所以我们必须要看日志!
# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2
# 实体类注解
# @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;
}
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
# 通用CRUD
所有方法用到的一个类,该类继承了MybatisPlus提供的一个基础类,并且绑定实体类,可以通过该类实现sql语句
@Repository
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
2
3
4
@Autowired
private UserMapper userMapper;
2
# 插入操作
insert():参数为一个实体类
需求
插入User数据
public 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对象里 }
1
2
3
4
5
6
7
8
9
10
11
# 删除操作
deleteById():参数为ID值,删除失败不会报错
需求
删除ID为8的数据
public void deleteById(){ int result = userMapper.deleteById(8L); System.out.println("处理行数:" + result); }
1
2
3
4deleteMap():参数为Map类型,key为字段名,value为字段对应的值,删除失败不会报错
需求
用Map方式删除用户名为可乐,年龄为20的数据
public 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); }
1
2
3
4
5
6
7
8
9delete():参数为QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等
需求
用实体类方式删除用户名为可乐,年龄为20的数据
public 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); }
1
2
3
4
5
6
7
8
9
10deleteBatchIds:参数为ID的集合,根据ID集合批量删除数据
需求
批量删除ID为10,11,12的数据
public void deleteByBatch(){ int result = userMapper.deleteBatchIds(Arrays.asList(10L,11L,12L));//删除ID为10,11,12的数据 System.out.println("处理行数:" + result); }
1
2
3
4
# 修改操作
updateById():参数为实体类,实体类内部需要封装好ID值,以及修改的信息内容
需求
把ID为6的密码修改为1111
public void updateById(){ User user = new User(); user.setId(6L); user.setPassword("1111"); int result = userMapper.updateById(user);// 修改ID为6的密码为1111 System.out.println("处理行数:" + result); }
1
2
3
4
5
6
7
8update():参数为QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等
需求
以QueryWrapper方式把ID为6的年龄修改为18
public 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); }
1
2
3
4
5
6
7
8
9
10update():参数为UpdateWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等
- 与QueryWrapper<>区别:不需要指定实体类,直接在对象后面加入修改的字段名和值
需求
以UpdateWrapper方式把ID为6的年龄修改为18
public void updateByUpdateWrapper(){ UpdateWrapper<User> wrapper = new UpdateWrapper<>(); wrapper.eq("id", 6).set("age", 19); int result = userMapper.update(null, wrapper); System.out.println("处理行数:" + result); }
1
2
3
4
5
6
7
# 查询操作
selectById():参数为ID值,通过ID查询数据,返回一条数据
需求
查询ID为6的数据
public void selectById(){ User user = userMapper.selectById(6L); System.out.println(user); }
1
2
3
4selectOne():参数为QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等,返回一条数据
需求
查询名字为赵六的数据
public void selectOne(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("name", "赵六"); User user = userMapper.selectOne(wrapper); System.out.println(user); }
1
2
3
4
5
6selectList():参数为QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等,返回多条数据
需求
查询年龄大于20的多条数据
public 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); } }
1
2
3
4
5
6
7
8selectCount():参数为QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等,返回数据总数
需求
查询年龄大于20的数据总数
public void selectCount(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.gt("age", 20); Integer count = userMapper.selectCount(wrapper); System.out.println("数据库数据总数为:" + count); }
1
2
3
4
5
6
7selectBatchIds():参数为ID的集合,根据ID批量查询数据,只返回存在的ID数据
需求
批量查询ID为1,2,6,22的数据
public void selectBatchIds(){ List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 6L,22L)); for (User user : users) { System.out.println(user); } }
1
2
3
4
5
6selectPages():分页查询,参数为QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等
需要使用MybatisPlus的分页插件Pages,并且需要开启该插件
@Configuration public class MybatisPlusPageConfig { /** * 分页插件配置 */ @Bean public PaginationInterceptor paginationInterceptor(){ return new PaginationInterceptor(); } }
1
2
3
4
5
6
7
8
9
10
11
需求
查询年龄大于20的第一页的一条数据
public 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); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 基本配置
# configLocation
MyBatis 配置文件位置,如果您有单独的 MyBatis 配置,请将其路径配置到 configLocation 中。 MyBatis Configuration 的具体内容请参考MyBatis 官方文档
Spring Boot:
mybatis-plus.config-location = classpath:mybatis-config.xml
1Spring MVC:
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean>
1
2
3
4
# mapperLocations
MyBatis Mapper 所对应的 XML 文件位置,如果您在 Mapper 中有自定义方法(XML 中有自定义实现),需要进行 该配置,告诉 Mapper 所对应的 XML 文件位置。
Spring Boot:
mybatis-plus.mapper-locations = classpath*:mybatis/*.xml
1Spring MVC:
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"> <property name="mapperLocations" value="classpath*:mybatis/*.xml"/> </bean>
1
2
3
# typeAliasesPackage
MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件中可以直接使 用类名,而不用使用全限定的类名(即 XML 中调用的时候不用包含包名)。
Spring Boot:
mybatis-plus.type-aliases-package = com.eight.entry
1Spring MVC:
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"> <property name="typeAliasesPackage" value="com.baomidou.mybatisplus.samples.quickstart.entity"/> </bean>
1
2
3
4
# 进阶配置
# 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=false
2
# cacheEnabled
- 类型:boolean
- 默认值:true
全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为 true。
mybatis-plus.configuration.cache-enabled=false
# DB 策略配置
# idType
- 类型:
com.baomidou.mybatisplus.annotation.IdType
- 默认值:
ID_WORKER
全局默认主键类型,设置后,即可省略实体对象中的@TableId(type = IdType.AUTO)配置。
Spring Boot:
mybatis-plus.global-config.db-config.id-type=auto
Spring 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>
2
3
4
5
6
7
8
9
10
11
12
13
# 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>
2
3
4
5
6
7
8
9
10
11
12
13
# QueryWrapper使用
# 条件构造器
# allEq
API:
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
2
3
说明:
1. params:Map类型,key为数据库字段名,value为字段值
2. 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)
2
3
说明:
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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 基本比较操作
- 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);
}
}
2
3
4
5
6
7
8
9
10
11
12
# 模糊查询
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 '王%'
public 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); } }
1
2
3
4
5
6
7
8
9
10
# 排序
1. orderBy(ASC排序)
1. 例:`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
public 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); } }
1
2
3
4
5
6
7
8
9
10- 例:
# 逻辑查询
or
- 拼接OR
- 主动调用 or 表示紧接着下一个方法不是用 and 连接!(不调用 or 则默认为使用 and 连接)
and
- AND 嵌套
- 默认使用and
public 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); } }
1
2
3
4
5
6
7
8
9
# 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);
}
}
2
3
4
5
6
7
8
9
10
11
12
# 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;
// ......
}
2
3
4
5
6
7
和通用CRUD方法一样,唯一区别就是不需要使用mapper类调用方法,直接使用实体类调用方法,但是实体类关联mapper类不能去掉,其实底层还是会去对应的mapper类调用CRUD
selectById():根据主键查询数据
public void testAR() { User user = new User(); user.setId(2L); User user2 = user.selectById(); System.out.println(user2); }
1
2
3
4
5
6insert():新增数据,成功返回true,失败返回false
public 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); }
1
2
3
4
5
6
7
8
9
10updateById():数据更新,成功返回true,失败返回false
public void testAR() { User user = new User(); user.setId(8L); user.setAge(35); boolean update = user.updateById(); System.out.println(update); }
1
2
3
4
5
6
7deleteById():数据删除,成功返回true,失败返回false
public void testAR() { User user = new User(); user.setId(7L); boolean delete = user.deleteById(); System.out.println(delete); }
1
2
3
4
5
6selectList():多条数据查询
public 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); }
1
2
3
4
5
6
7
8其他方法请看通用CRUD,mapper类只需换成实体类即可
# Oracle主键Sequence
Spring Boot框架下
# 创建序列
--创建序列
CREATE SEQUENCE SEQ_USER START WITH 1 INCREMENT BY 1
2
# 驱动包
由于版权原因,不能直接通过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
2
引用坐标
dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>12.1.0.1</version>
</dependency>
2
3
4
5
# 配置文件策略
Oracle的数据库连接配置
开启ID生成策略(和Mysql不同,需要把auto改为input)
- 原理:通过配置寻找配置好的序列(下方),input代表主动把配置好的序列匹配到表的ID,实现ID自增
#数据库连接配置 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
1
2
3
4
5
6
7
# 配置序列
在配置类里进行序列配置,添加Bean注解,自动放入容器中
Oracle序列配置的代码:
@Configuration public class MybatisPlusConfig { /** * 分页插件配置 */ @Bean public PaginationInterceptor paginationInterceptor(){ return new PaginationInterceptor(); } /** * Oracle序列配置 */ @Bean public OracleKeyGenerator oracleKeyGenerator(){ return new OracleKeyGenerator(); } // .... 其他插件配置的位置 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21在实体对象中指定序列的名称:
- value:对应数据库的序列名字
- clazz:自增的ID类型
@KeySequence(value = "SEQ_USER", clazz = Long.class) public class User{ // ...... }
1
2
3
4
# 插件
# 插件机制
# 作用
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;
}
}
}
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
在自定义配置类里注入自定义的拦截器(插件)到spring容器
public class MybatisPlusConfig {
// 把自己注入到spring容器
@Bean
public MyInterceptor myInterceptor(){
return new MyInterceptor();
}
}
2
3
4
5
6
7
或者通过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>
2
3
4
5
6
7
8
9
# 执行分析插件
# 作用
在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;
}
2
3
4
5
6
7
8
9
# 性能分析插件
# 作用
性能分析拦截器,用于输出每条 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;
}
2
3
4
5
6
7
8
9
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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 乐观锁插件
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
取出记录时,获取当前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();
}
2
3
4
# 实体字段Version
需要为实体字段添加@Version注解。且数据库存有version字段
# 原理
数据更新后,version值都会自动加1,
每次更新数据时,先从数据库拿到字段version值,然后进行更新,如果拿到的字段version不是最新的(拿到version后别人修改数据了,version值已经自动加1),因为拿到的version不是最新的,所以修改失败,
# 操作
为表添加version字段,并且设置初始值为1
为User实体对象添加version字段,并且添加@Version注解
// ...... @Version private Integer version; // ......
1
2
3
4使用
public 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); }
1
2
3
4
5
6
7
8
9
10
11
# 特别说明
支持的数据类型只有: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();
}
2
3
4
5
# 自定义类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;
}
}
2
3
4
5
6
7
8
9
10
11
12
# 自定义类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);
}
}
2
3
4
5
6
7
8
9
10
# 注册到Spring容器
- 将自定义类MySqlInjector注册到Spring容器
- 方法一:通过@Component注解直接注册
- 方法二:自定义一个配置类,该类创建一个方法,返回MySqlInjector的对象,记得方法上放添加@Bean注解实现注册
@Configuration
public class MybatisPlusPageConfig {
// ....其他插件
@Bean
public MySqlInjector sqlInjector(){
return new MySqlInjector();
}
// ....其他插件
}
2
3
4
5
6
7
8
9
# 自动填充功能
插入或者更新数据时,希望有些字段可以自动填充数据,比如密码、version 等。在MP中提供了这样的功能,可以实现自动填充。
就是类似于数据库的default:默认值
# 添加@TableField注解
为password添加自动填充功能,在新增数据时有效。
@TableField(fill = FieldFill.INSERT) //插入数据时进行填充
private String password;
2
FieldFill多种选择:
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入时填充字段
*/
INSERT,
/**
* 更新时填充字段
*/
UPDATE,
/**
* 插入和更新时填充字段
*/
INSERT_UPDATE
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 自定义类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) {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 逻辑删除
开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,所谓逻辑删除就是将数据标记为删除,而并非真正的物理删除(非DELETE操作),查询时需要携带状态条件,确保被标记的数据不被查询到。这样做的目的就是避免 数据被真正的删除。
为表添加deleted字段,用于表示数据是否被删除,1代表删除,0代表未删除。
实体类增加deleted属性并且添加@TableLogic注解
@TableLogic private Integer deleted;
1
2
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
2
3
4
当删除某数据时,只是把该数据的deleted字段值改为1,不是真的删除数据
当查询某数据时,数据字段deleted为1的数据不会查询出来
# 通用枚举
如性别在数据库值为1或者2代表男女,使用通用枚举,解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!
定义枚举类,继承IEnum类,重写getValue和toString方法
public 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; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Spring Boot配置
# 枚举包扫描 mybatis-plus.type-enums-package=com.eight.enums
1
2操作(插入、查询、条件查询)
// 插入 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); } }
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插入时,把枚举类的key作为值传给数据库
查询时,先获取数据库的值,根据值(key)获取对应的value,再返回
# 代码生成器
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
引入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.1.1</version> </dependency>
1
2
3
4
5例子
import 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(); } }
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
# 总结
配置多个自定义类,放到spring容器的两者方法
- 直接在类上方加入@Component 注解(分批处理,快捷)
- 创建一个核心配置类(上方加入@Component 注解),然后在类里创建不同的方法,不同的方法return 不同的自定义类对象,记得再方法上加入@Bean注解。(统一处理,代码阅读性强)