Mybatis plus
官网:https://baomidou.com/
介绍:
MyBatis-Plus是mybatis的一个增强工具,在Mybatis的基础上只做增强不做改变,为简化开发,提高效率而生。MyBatis-Plus提供了通用的mapper和service,可以在不编写任何sql语句的情况下,快速的实现对表单的CRUD,批量、逻辑删除、分页等操作。
**注意:MyBatis-plus在进行插入的时候默认雪花算法生成id,所以id比较长,数据库表id字段使用bigInt类型。
快速使用
1、创建springboot项目
2、导入依赖
- mysql驱动的依赖
- mbatis-plus的依赖
- lomboku依赖
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!--用于简化实体类开发-->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
3、lombok插件的使用
安装插件:
4、配置文件设置
#设置数据源
#数据源类型 com.zaxxer.hikari.HikariDataSource是springboot的默认数据源
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
#数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#链接地址的url,注意mysql的版本有关系,一些版本的配置会不同
#设置当前连接数据库,操作数据库的编码格式characterEncoding=utf-8 --使用mysql版本在5.7,5的版本
#mysql在8的版本时,需要添加时区的配置serverTimezone=GMT
spring.datasource.url=jdbc:mysql://localhost:3306/fileup?characterEncoding=utf-8&userSSL=false
spring.datasource.username=root
spring.datasource.password=root
5、lombok的使用
-
注解生成方法
@NoArgsConstructor //生成无参构造方法 @AllArgsConstructor //生成有参构造方法 @Getter //生成get方法 @Setter //生成set方法 @EqualsAndHashCode public class User { private int id; private String name; } //由于注解太多,所以可以使用@Data注解来创建,效果同上 @Data public class User { private int id; private String name; }
6、mapper的创建
@Autowired注解参数报红色波浪线
愿意:
IOC容器中只能存在类所对应的bean,不能存在接口所对应的bean
在mapper接口上添加
@Repository
意思:将一个类或一个接口标识为持久层
加入日志功能
只需要在配置文件中加入配置就好
#加入日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
BaseMapper接口中的方法
1、insert
// 插入一条记录
int insert(T entity);
实现
public void insertUser()
{
User user = new User();
user.setName("维嘉");
int result = userMapper.insert(user);
System.out.println("result:" + result);
//可以获取的新增数据的表中自动递增的主键
System.out.println("id:" + user.getId());
}
如上,可以获取到
新添加数据主键自增的值
注意:
mybatis新增主键自增值是经过雪花算法的,所以数据库中主键类型也该设置为bigint
要获取到新增主键值还需要将java中对应表的java类中主键参数设置为Long(long的引用类型)
2、delete
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
public void testDelete()
{
//通过id删除用户信息
/*
==> Preparing: DELETE FROM user WHERE id=?
==> Parameters: 1615289713650360321(Long)
*/
int result = userMapper.deleteById(1615289713650360321L);
/*
根据map集合中所设置的条件删除用户信息
==> Preparing: DELETE FROM user WHERE name = ? AND id = ?
==> Parameters: 可乐(String), 1234567892(Integer)
*/
Map<String, Object> map = new HashMap<>();
map.put("name","可乐");
map.put("id",1234567892);
int result = userMapper.deleteByMap(map);
/*
通过多个id来实现批量删除
==> Preparing: DELETE FROM user WHERE id IN ( ? , ? , ? )
==> Parameters: 1615289713650360322(Long), 1615289713650360323(Long), 1615290575823101953(Long)
*/
List<Long> longs =
Arrays.asList(1615289713650360322L, 1615289713650360323L, 1615290575823101953L);
int result = userMapper.deleteBatchIds(longs);
System.out.println("result:"+ result);
}
3、Update
// 根据 whereEntity 条件,更新记录
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);
4、select
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
public void testSelect()
{
/*
通过id查询用户信息
==> Preparing: SELECT id,name FROM user WHERE id=?
==> Parameters: 224305001(Long)
User user = userMapper.selectById(224305001L);
System.out.println(user.toString());
*/
/*
根据多个id查询多个用户信息
==> Preparing: SELECT id,name FROM user WHERE id IN ( ? , ? )
==> Parameters: 224305001(Long), 224305002(Long)
List<Long> list = Arrays.asList(224305001L,224305002l);
List<User> users = userMapper.selectBatchIds(list);
users.forEach(System.out::println);
*/
/*
根据map集合中的条件来查询用户信息
==> Preparing: SELECT id,name FROM user WHERE name = ? AND id = ?
==> Parameters: 李伟佳(String), 2019240402(Long)
Map<String,Object> map = new HashMap<>();
map.put("name","李伟佳");
map.put("id",2019240402L);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
*/
/*
参数伟条件构造器,如果没有条件则设置为null
==> Preparing: SELECT id,name FROM user
==> Parameters:
*/
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
自定义一个sql
baseMapper中的方法,无法满足sql需求时,可以自定义一个sql
1、创建映射文件
默认地址为:类路劲下mapper下的任意目录下的xml都是映射文件
mybatis-plus的通用service方法
说明:
- 通用service CRUD封装IService接口,进一步封装CRUD采用get擦互相你单行,remove删除,list查询集合、page分页、前缀命名方式区分mapper层,避免混淆
- 泛型T为任意实体对象
- 建议如果存在自定义通用Service方法的可能,请创建自己的IBaseService继承Mybatis-Plus提供的基类
- 官网地址:https://baomidou.com/pages/49cc81/#service-crud-%E6%8E%A5%E5%8F%A3
- https://baomidou.com/pages/49cc81/#service-crud-%E6%8E%A5%E5%8F%A3
a>IService
Mybatis-Plus中有一个接口IService和其实现类ServiceImpl,封装到了常见的业务逻辑层
b>创建Service接口和实现类
c>service的使用
由于mybatis-plus提供的业务不足,还会需要添加自己的业务,此时就需要自定义service,而自定义的service想调用mybatis-plus所需要的service时,则实现IService
将service标记成一个组件,在service的实现类上添加
@Service
d>service的方法
- 查询总条数
- 批量添加数据
public void testCount()
{
/*
查询总记录数
==> Preparing: SELECT COUNT( * ) FROM user
==> Parameters:
long count = userService.count();
System.out.println(count);
*/
/*
批量添加数据
==> Preparing: INSERT INTO user ( id, name ) VALUES ( ?, ? )
由此可知,是通过单个添加,进行循环实现的
==> Parameters: 1615637924315439105(Long), 大哥1(String)
==> Parameters: 1615637924390936577(Long), 大哥2(String)
==> Parameters: 1615637924390936578(Long), 大哥3(String)
*/
List<User> users = new ArrayList<>();
for(int i = 1;i <=3;i++)
{
//id默认使用mybatis-plus的雪花算法
User user = new User();
user.setName("大哥" + i);
users.add(user);
}
Boolean temp = userService.saveBatch(users);
System.out.println(temp);
}
service接口中的方法有很多,可以在mybatis-plus的官网中查看到
https://baomidou.com/pages/49cc81/#activerecord-%E6%A8%A1%E5%BC%8F
MyBatis-Plus提供的注解
a> @TableName
当数据库中表和实体类表名称不一样时:
方法1:
@TableName("数据库表名");
public void 类名(){
}
方法2:
通过配置文件做到全局配置
#来设置实体类与对应表的统一前缀
global-config.db-config.table-profix="设置数据库中表相同的前缀"
通过配置文件做到设置前缀功能,实现实体类和数据库表的对应
b> @TableId
b.a> 当实体类对象主键命名方式不为id时
MyBatis-plus默认将实体类的id作为主键,则当数据库表中主键id和实体类主键id命名不同时
public class 类名()
{
/*
当表字段主键为id,由于类名(对应的表)有前缀,为了区分,在id前加上u,此时由于mybatis-plus默认将id为主键,uid无法识别,此时会出现错误。
此时通过@TableId 来解决会出现的错误
*/
@Table
private int uid;
}
b.b> @Table注解的value属性
当表字段主键命名方式不为id且实体类对象主键命名方式不为id时
public class 类名()
{
/*
当表字段主键和实体类主键id不相同时
此时通过@TableId的value属性来解决会出现的错误
*/
@Table(value="表主键的字段")
//或
@Table("表主键的字段")
private int id;
}
b.c>@TableId注解的type属性
type作用:
表示当前主键生成策略(mybatis-plus主键id的默认生成为雪花算法)
当用户不需要通过雪花算法来生成id时,则可以使用type标签
当用户不使用mybatis-plus默认主键雪花算法生成算法时:
- 首先设置表中主键id字段为自增
- 设置@TableId注解的type属性
public void 类名(){
//@TableId注解的Type属性来定义主键策略
@TableId(type=TdType.AUTO) //设置id字段自增
private Long id;
}
通过配置文件来设置主键策略
global-config.db-config.id.type=设置值
雪花算法
- 背景
需要选择核实的方案去应对数据规模的增长,以应对渐增长的访问压力和数据量
数据库的扩展方式主要包括:业务分库、主从复制、数据库分表。
- 数据库分表
将不同业务数据分散存储到不同的数据库服务器,能够支持百万甚至千万用户规模的业务,但如果业务持续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对但表数据进行拆分。
单表数据拆分的格式有两种:垂直拆分和水平拆分。示意图如下:
- 垂直分表
垂直拆分适合将表中某些不常用且占了大量空间的列拆分出去
- 水平分表
水平分表适合表行数特别大的表,有点公司要求单表行数超过5000万就必须分表,这个数字可以作为参考,单并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过1000万就要分表了,而对一些简单的表,即使存储超过1亿行,也可以不分表。
但不管怎么样,当看到表的数据量达到千万级别时,作为架构师就要警觉起来,因为这很可能是架构的性能瓶颈或者隐患。
水平分表相比于垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理。
以下为分表的方式:
主键自增
取模
雪花算法
c> @TableField()
实体类属性和表字段名不相同时
注意:mybatis-plus有一个默认规范,忽略下划线,字母大小写的区别
例如,表字段u_name实体类name,
作用:将实体类中属性和数据库表中字段帮定
public void 类名()
{
private int id;
@TableField("u_name")
private String name;
}
d> @TableLogic
d.a>逻辑删除
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后再数据库中仍旧能看到此条数据记录
- 使用场景:可以进行数据恢复
d.c>实现逻辑删除
-
数据库创建表,其中表添加一列,用于标记是否删除,其中1表删除,0表未删除。
-
创建实体类,实体类与表相对应
public void 类名()
{
private 类型 属性名;
private 类型 属性名;
....
@TableLogic
private Integer isDeleted; //表中对应用于是否删除的判断列
}
条件构造器
官方文档:https://baomidou.com/pages/10c804/#abstractwrapper
1、wrapper介绍
- Wrapper:条件构造器,最顶端父类
- AbstractWrapper:用于查询条件封装,生成sql的where条件
- QueryWrapper:查询条件封装
- UpdateWrapper:Update条件封装
- AbstractLamdaWrapper:使用Lambda语法
- lambdaQueryWrapper:用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper:Lambda更新封装Wrapper
- AbstractWrapper:用于查询条件封装,生成sql的where条件
删除同查询(QueryWrapper,lambdaQueryWrapper)
2、wrapper测试
public void TestWrapper()
{
//场景:查询用户名包含a,年龄再20~30之间,邮箱信息不为null的用户信息
QueryWrapper<User> wrapper = new QueryWrapper<>();
// //数据库中表的字段名 , 条件值
wrapper.like("name",'a')
.between("age",20,30)
.isNotNull("email");
List<User> users = userMapper.selectList(wrapper);
}
其中like、between、isNotNull都是QueryWrapper的方法,其中第一个参数为表中列的字段名,后面的参数为条件值。
注意:当使用了逻辑删除,所有的查询语句都会添加where语句,后面会有自定义的逻辑删除判断
public void SortTestWrapper()
{
//场景:查询用户信息,按照年龄的降序排序,若年龄相同,则按照id升序排序
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("age")
.orderByAsc("id");
List<User> users = userMapper.selectList(wrapper);
}
删除测试(注意删除同查询使用相同的wrapper
public void deleteTestWrapper()
{
//删除邮箱地址为null的用户信息
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNull("email");
int result = userMapper.delete(wrapper);
}
修改测试 UpdateWrapper
public void updateWrapper()
{
QueryWrapper<User> wrapper = new QueryWrapper<>();
//将年龄大于20并且用户名中包含李的用户信息修改,或学号为2019240423的人
wrapper.gt("age",20)
.like("name","李")
.or()
.like("id",2019240423L);
//第一个参数设置要修改的内容,第二参数设置修改的条件,都可以为null
User user = new User();
user.setAge(34);
user.setName("李弟弟");
/*
==> Preparing: UPDATE user SET name=?, age=? WHERE (age > ? AND name LIKE ? OR id LIKE ?)
==> Parameters: 李弟弟(String), 34(Integer), 20(Integer), %李%(String), %2019240423%(String)
<== Updates: 2
*/
int update = userMapper.update(user, wrapper);
System.out.println(update);
}
3、wrapper的方法集
https://www.jianshu.com/p/c5537559ae3a
【比较大小: ( =, <>, >, >=, <, <= )】
eq(R column, Object val); // 等价于 =,例: eq("name", "老王") ---> name = '老王'
ne(R column, Object val); // 等价于 <>,例: ne("name", "老王") ---> name <> '老王'
gt(R column, Object val); // 等价于 >,例: gt("name", "老王") ---> name > '老王'
ge(R column, Object val); // 等价于 >=,例: ge("name", "老王") ---> name >= '老王'
lt(R column, Object val); // 等价于 <,例: lt("name", "老王") ---> name < '老王'
le(R column, Object val); // 等价于 <=,例: le("name", "老王") ---> name <= '老王'
【范围:(between、not between、in、not in)】
between(R column, Object val1, Object val2); // 等价于 between a and b, 例: between("age", 18, 30) ---> age between 18 and 30
notBetween(R column, Object val1, Object val2); // 等价于 not between a and b, 例: notBetween("age", 18, 30) ---> age not between 18 and 30
in(R column, Object... values); // 等价于 字段 IN (v0, v1, ...),例: in("age",{1,2,3}) ---> age in (1,2,3)
notIn(R column, Object... values); // 等价于 字段 NOT IN (v0, v1, ...), 例: notIn("age",{1,2,3}) ---> age not in (1,2,3)
inSql(R column, Object... values); // 等价于 字段 IN (sql 语句), 例: inSql("id", "select id from table where id < 3") ---> id in (select id from table where id < 3)
notInSql(R column, Object... values); // 等价于 字段 NOT IN (sql 语句)
【模糊匹配:(like)】
like(R column, Object val); // 等价于 LIKE '%值%',例: like("name", "王") ---> name like '%王%'
notLike(R column, Object val); // 等价于 NOT LIKE '%值%',例: notLike("name", "王") ---> name not like '%王%'
likeLeft(R column, Object val); // 等价于 LIKE '%值',例: likeLeft("name", "王") ---> name like '%王'
likeRight(R column, Object val); // 等价于 LIKE '值%',例: likeRight("name", "王") ---> name like '王%'
【空值比较:(isNull、isNotNull)】
isNull(R column); // 等价于 IS NULL,例: isNull("name") ---> name is null
isNotNull(R column); // 等价于 IS NOT NULL,例: isNotNull("name") ---> name is not null
【分组、排序:(group、having、order)】
groupBy(R... columns); // 等价于 GROUP BY 字段, ..., 例: groupBy("id", "name") ---> group by id,name
orderByAsc(R... columns); // 等价于 ORDER BY 字段, ... ASC, 例: orderByAsc("id", "name") ---> order by id ASC,name ASC
orderByDesc(R... columns); // 等价于 ORDER BY 字段, ... DESC, 例: orderByDesc("id", "name") ---> order by id DESC,name DESC
having(String sqlHaving, Object... params); // 等价于 HAVING ( sql语句 ), 例: having("sum(age) > {0}", 11) ---> having sum(age) > 11
【拼接、嵌套 sql:(or、and、nested、apply)】
or(); // 等价于 a or b, 例:eq("id",1).or().eq("name","老王") ---> id = 1 or name = '老王'
or(Consumer<Param> consumer); // 等价于 or(a or/and b),or 嵌套。例: or(i -> i.eq("name", "李白").ne("status", "活着")) ---> or (name = '李白' and status <> '活着')
and(Consumer<Param> consumer); // 等价于 and(a or/and b),and 嵌套。例: and(i -> i.eq("name", "李白").ne("status", "活着")) ---> and (name = '李白' and status <> '活着')
nested(Consumer<Param> consumer); // 等价于 (a or/and b),普通嵌套。例: nested(i -> i.eq("name", "李白").ne("status", "活着")) ---> (name = '李白' and status <> '活着')
apply(String applySql, Object... params); // 拼接sql(若不使用 params 参数,可能存在 sql 注入),例: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08") ---> date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
last(String lastSql); // 无视优化规则直接拼接到 sql 的最后,可能存若在 sql 注入。
exists(String existsSql); // 拼接 exists 语句。例: exists("select id from table where age = 1") ---> exists (select id from table where age = 1)
4、条件的优先级
其中and的第一个参数使用了lambda的表达式
@Test
//条件的优先级
public void testYXJ()
{
//场景:将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
//解决方法:lambda中的条件优先执行
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("user_name","a")
.and(i->i.gt("age",20).or().isNull("email"));
//执行后的sql语句
/*
update user set user_name=?,email=?where id_delete=0 AND(user_name like > AND (age > ? OR email IS NULL) )
*/
}
/*
注意这是and和or的源码
Consumer是lambda表达式中的消费者接口
Param是泛型,是具体需要运行函数的类(也是wrapper的子类)
*/
default Children and(Consumer<Param> consumer) {
return this.and(true, consumer);
}
default Children or(Consumer<Param> consumer) {
return this.or(true, consumer);
}
5、指定查询的字段
@Test
//指定查询的字段
public void testZI()
{
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("user_name","age","email");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
}
//sql语句
select user_name,age,email from user where is_deleted=0
6、组装子查询
@Test
public void testZCX()
{
//使用子查询语句查询id小于10的用户信息
//SELECT * FROM USER WHERE id IN (SELECT id FROM USER WHERE id < 224305007)
QueryWrapper<User> wrapper = new QueryWrapper<>();
//inSql方法,第一个参数子查询判断字段,第二个参数,判断的数据
wrapper.inSql("id","SELECT id FROM USER WHERE id < 224305007");
List<User> users = userMapper.selectList(wrapper);
for (User u:users
) {
System.out.println(u);
}
}
/*
==> Preparing: SELECT id AS uid,name,age FROM user WHERE (id IN (SELECT id FROM USER WHERE id < 224305007))
==> Parameters:
<== Columns: uid, name, age
*/
7、UpdateWrapper
string类型判断的小技巧
//isNotBlank判断某个字符串是否不为空字符串,不为null,不为空白符
StringUtils.isNotBlank();
8、Condition组装条件
场景:在从前端from表单中接受数据,需要做出判断,是否需要在sql语句中添加
容易出现字段名错误的问题,可以使用lambdaQueryWrapper和lambdaUpdateWrapper
9、lambdaQueryWrapper
SFunction函数式接口
可以直接通过SFunction找个函数就可以直接访问到当前实体类中属性所对应的字段名
10、9、lambdaUpdateWrapper
六、插件
1、分页插件
1、设置插件的配置类
package com.wz.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
//扫描mapper接口的所在的包
@MapperScan("com.wz.mapper")
public class MyBatisPlusConfig {
//配置mybatis_plus的插件(分页查询)
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor()
{
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//在插件对象中添加一个内部插件(具体需要使用的插件)
//由于数据库的分页操作并不是全部一样的,所以需要设置数据库的类型 DbType.MYSQL
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
2、测试类使用
package com.wz;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wz.mapper.UserMapper;
import com.wz.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MyBatisPlusPlugTest {
@Autowired
private UserMapper userMapper;
@Test
public void testPage()
{
/*
查询当前分页数据
<P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param("ew") Wrapper<T> queryWrapper);
P page :分页对象
Wrapper<T> queryWrapper : 条件构造器
limit关键字: index , pageSix ; 当前查询起始 ,每页显示的条数
*/
//page(a,b)的构造函数,第一个为第几页(1,0为从第一个开始),第二个为每页数条数
//原理:先查询到总条数,第一个参数为第几页开始,limit关键字的第一个参数经过 (a-1)*3
Page<User> page = new Page<>(1,3);
userMapper.selectPage(page,null);
System.out.println(page);
System.out.println("有无下一页:"+page.hasNext());
System.out.println("有无上一页:"+page.hasPrevious());
System.out.println("总记录数:"+page.getTotal());
System.out.println("总页数:"+page.getPages());
System.out.println("获取记录:" + page.getRecords());
System.out.println("获取当前页页码:" + page.getCurrent() + "获取每页显示的条数:" + page.getSize());
System.out.println(page.maxLimit());
}
}
/*输出结果
==> Preparing: SELECT COUNT(*) AS total FROM user
==> Parameters:
<== Columns: total
<== Row: 30
<== Total: 1
==> Preparing: SELECT id AS uid,name,age FROM user LIMIT ?
==> Parameters: 3(Long)
<== Columns: uid, name, age
<== Row: 224305001, 马静怡, 12
<== Row: 224305002, 张泽慧, 16
<== Row: 224305003, 杨紫晴, 17
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6002e944]
com.baomidou.mybatisplus.extension.plugins.pagination.Page@2a5abd3c
有无下一页:true
有无上一页:false
总记录数:30
总页数:10
获取记录:[User(uid=224305001, name=马静怡, age=12), User(uid=224305002, name=张泽慧, age=16), User(uid=224305003, name=杨紫晴, age=17)]
获取当前页页码:1获取每页显示的条数:3
null
*/
2、自定义分页功能
场景:在自定义的sql语句中,通过分页插件,实现分页功能
1、mapper类中添加方法
/*
通过id查询用户信息并分页
@param page MyBatis-plus所提供 的分页对象,必须位于第一个参数的位置
*/
Page<User> selectPageVo(@Param("page")Page<User> page, @Param("uid")Long uid);
/*
参照mybatis-plus 的selectPage
<P extends IPage<T>> P selectPage(P page, @Param("ew") Wrapper<T> queryWrapper);
该方法返回值为page对象,第一个参数也为page
*/
2、mapper配置文件设置sql语句
<!--Page<User> selectPageVo(@Param("page")Page<User> page, @Param("id")Integer id);-->
<select id="selectPageVo" resultType="User">
select * from user where id > #{uid}
</select>
3、测试类测试使用
@Test
public void testVo()
{
Page<User> userPage = new Page<>(1,3);
userMapper.selectPageVo(userPage,224305007L);
System.out.println(userPage);
}
结果:
==> Preparing: SELECT COUNT(*) AS total FROM user WHERE id > ?
==> Parameters: 224305007(Long)
<== Columns: total
<== Row: 24
<== Total: 1
==> Preparing: select * from user where id > ? LIMIT ?
==> Parameters: 224305007(Long), 3(Long)
<== Columns: id, name, age
<== Row: 224305008, 彭赛, 12
<== Row: 224305009, 吴焱桦, 23
<== Row: 224305010, 张锌江, 34
<== Total: 3
3、乐观锁
a>场景
b>乐观锁与悲观锁
悲观锁:就是当前数据库是正在被操作中,其他人无法操作。即小李在使用数据库时,小王不能使用。
乐观锁:通过版本号实现,即获取到数据库中某个数据的版本,当有人在操作该数据库时,结果会使数据库版本提升,其他人再操作的话,版本不对,无法成功
c>模拟修改冲突
1、创建表
2、添加数据
3、添加实体类
import lombok.Data;
import java.util.logging.Logger;
@Data
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
4、添加mapper接口
public interface ProductMapper extends BaseMapper<Product> {
}
5、演示
//乐观锁的演示
@Test
public void testProduct()
{
System.out.println("小李查询商品价格");
Product productLi = productMapper.selectById(1);
System.out.println("小李查询到的商品价格为:" + productLi.getPrice());
System.out.println("小王查询商品价格");
Product productWang = productMapper.selectById(1);
System.out.println("小王查询到的商品价格为:" + productWang.getPrice());
System.out.println("\n 小李将商品价格+50");
productLi.setPrice(productLi.getPrice()+50);
productMapper.updateById(productLi);
System.out.println("\n小王将商品的价格-30");
productWang.setPrice(productWang.getPrice()-30);
productMapper.updateById(productWang);
Product productLaoBan = productMapper.selectById(1);
System.out.println("老板查询到的商品价格为:" + productLaoBan.getPrice());
}
结果信息:
小李查询商品价格
小李查询到的商品价格为:70
小王查询商品价格
小王查询到的商品价格为:70
小李将商品价格+50
小王将商品的价格-30
老板查询到的商品价格为:40
d>乐观锁实现流程
数据库中添加version字段
取出记录时,获取当前version
更新时,version+1,如果where语句中的version版本不对则更新失败
e>MyBatis-Plus实现乐观锁
1、修改实体类
使用@Version注解,指定该属性为乐观锁字段
@Data
public class Product {
private Long id;
private String name;
private Integer price;
@Version
//用于表示乐观锁版本号字段
private Integer version;
}
2、配置类添加插件
@Configuration
//扫描mapper接口的所在的包
@MapperScan("com.wz.mapper")
public class MyBatisPlusConfig {
//配置mybatis_plus的插件(分页查询)
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor()
{
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//在插件对象中添加一个内部插件(具体需要使用的插件)
//由于数据库的分页操作并不是全部一样的,所以需要设置数据库的类型 DbType.MYSQL
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
3、测试
小李查询商品价格
小李查询到的商品价格为:100
小王查询商品价格
小王查询到的商品价格为:100
小李将商品价格+50
小王将商品的价格-30
//原因:当一个人修改数据时,其版本号字段会改变,当其他人修改时,如果版本号不同,则无法修改
如下:修改语句
UPDATE product SET name=?, price=?, version=? WHERE id=? AND version=?
如上:在修改语句中添加version条件判断
老板查询到的商品价格为:150
4、优化修改流程
即在更新表时,判断是否更新成功!!!!!
//乐观锁的演示
@Test
public void testProduct()
{
System.out.println("小李查询商品价格");
Product productLi = productMapper.selectById(1);
System.out.println("小李查询到的商品价格为:" + productLi.getPrice());
System.out.println("小王查询商品价格");
Product productWang = productMapper.selectById(1);
System.out.println("小王查询到的商品价格为:" + productWang.getPrice());
System.out.println("\n 小李将商品价格+50");
productLi.setPrice(productLi.getPrice()+50);
productMapper.updateById(productLi);
System.out.println("\n小王将商品的价格-30");
productWang.setPrice(productWang.getPrice()-30);
int result = productMapper.updateById(productWang);
if(result==0)
{
//修改失败,重试
Product productNew = productMapper.selectById(1);
productNew.setPrice(productNew.getPrice()-30);
productMapper.updateById(productNew);
}
Product productLaoBan = productMapper.selectById(1);
System.out.println("老板查询到的商品价格为:" + productLaoBan.getPrice());
}
七、通用枚举
表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现
如何让mybatis-plus扫描枚举,并将枚举中指定的属性值存储在表中
- @EnumValue注解
- 配置文件添加扫描配置
a>数据库表添加字段sex
b>设置枚举类
package com.wz.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
@Getter //通过lombok添加get方法
public enum SexEnum {
MALE(1,"男"),
FEMALE(2,"女");
@EnumValue //将注解所标识的属性的值存储到数据库中
private Integer sex;
private String sexName;
//创建构造器
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
c>实体类中添加枚举属性值
private SexEnum sex;
d>配置文件中添加扫描枚举配置
#扫描通用枚举的包
mybatis-plus.type-enums-package=com.wz.enums.SexEnum
八、代码生成器
1、引入依赖
mybatis-plus逆向工程(代码生成器的核心依赖)
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
freemarker的相关依赖,在实现的功能中会生成一个Freemarker引擎模板
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
2、快速生成
public class FastAutoGeneratorTest{
public static void main(String[] args)
{
/*
url:数据库地址
username:数据库用户名
password:数据库用户名密码
baomidou:自定义作者名
D://:自定义指定输出目录
com.baomidou.mybatisplus.samples.generator:设置父包名
system:设置父包模块名
t_user:数据库中的表名,当前要操作的表
"t_", "c_" :过来表的前缀,可以多个设置
*/
FastAutoGenerator.create("url", "username", "password")
.globalConfig(builder -> {
builder.author("baomidou") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://"); // 指定输出目录
})
.packageConfig(builder -> { //设置包
builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_user") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
九、多数据源
1、选择或创建两个数据库
2、引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
3、配置多数据源
说明:注释掉之前的数据库连接,添加新配置
jdbc:mysql://localhost:3306/fileup?characterEncoding=utf-8&userSSL=false
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
#注意此处master必须和primary后设置的值一样,由于默认master所以直接使用master即可
master:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
#从数据源1
slave_1:
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
#从数据源2
slave_2:
url: ENC(xxxxx) # 内置加密,使用请查看详细文档
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: com.mysql.jdbc.Driver
#......省略
#以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
4、创建service
- 创建实体类
- 创建mapper
- 创建service接口,实现接口
mapper1、
@Repository
public interface PersonMapper extends BaseMapper<Person> {
}
mapper2、
@Repository
public interface UserMapper extends BaseMapper<User> {
}
service接口1、
public interface UserService extends IService<User> {
}
service接口2、
public interface PersonService extends IService<Person> {
}
service接口实现1、
@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
service接口实现2、
@Service
@DS("slave_1") //指定操作的数据源为slave_1
public class PersonServiceImpl extends ServiceImpl<PersonMapper,Person> implements PersonService {
}
注意:在service层有两个注解
- @Service 标注为业务层
- @DS 指定当前业务层操纵的数据源,其中值为在配置文件中设置的数据源名
5、测验
@Autowired
private UserService userService;
@Autowired
private PersonService personService;
@Test
public void test()
{
System.out.println(userService.getById(224305007L));
System.out.println(personService.getById(1));
}
结果:
==> Preparing: SELECT id,name,age,sex FROM user WHERE id=?
==> Parameters: 224305007(Long)
<== Columns: id, name, age, sex
<== Row: 224305007, 王俊杰, 19, null
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f6b7426]
User(id=224305007, name=王俊杰, age=19, sex=null)
==> Preparing: SELECT id,name,sex,age FROM person WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, sex, age
<== Row: 1, 张三, 男, 13
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@91a2543]
Person(id=1, name=张三, sex=男, age=13)
@DS可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
十、MyBatisX插件
idea安装MyBatisX
使用官方文档:https://baomidou.com/pages/ba5b24/#%E5%8A%9F%E8%83%BD
工具1**:
快速定位到mapper接口所对应的映射文件,以及映射文件所对应的接口
工具2**:代码快速生成
工具3、MyBatisX快速生成CRUD
1、注意方法前缀insert、selete、update、delete
通过输入方法名前缀选择方法
alt+enter实现快速生成
生成成功
十一:mybatis使用过程中的问题
1、in语句结果无序变有序
https://blog.csdn.net/GUILTYxc/article/details/132950592
场景
当我在MySQL中使用IN语句进行查询时
例如SELECT * FROM Course WHERE teacher_id in (“6553”,“2145”,“3162”),从课程表里使用教师id查询记录,这时我希望返回的记录是按(“6553”,“2145”,“3162”)的顺序,但是发现并不是,而是(“2145”,“3162”,“6553”)的顺序,进行了排序。
如何解决?
可以使用ORDER BY FIELD语句。
SELECT * FROM Course WHERE teacher_id in (“6553”,“2145”,“3162”) ORDER BY FIELD(tracher_id,“6553”,“2145”,“3162”),其中tracher_id指定排序的字段,之后给出顺序,返回的结果即可满足按IN中指定的顺序排序。
MyBatisPlus
在MyBatis或MyBatisPlus中,可以手动拼接sql语句达到同样的效果。
方法(传入字段名和字段列表):
private String makeOrderByFieldSql(String fieldName, List<String> fields) {
StringBuilder orderSql = new StringBuilder();
orderSql.append("ORDER BY FIELD(").append(fieldName).append(",");
for (String field : fields) {
orderSql.append("\"").append(field).append("\"").append(",");
}
//去除最后一个 ",",加上括号
orderSql.deleteCharAt(orderSql.length() - 1).append(")");
return orderSql.toString();
}
使用时,构建:
//构建LambdaQueryWrapper
LambdaQueryWrapper<Course> courseLambdaQueryWrapper = new LambdaQueryWrapper<>();
//调用方法生成sql语句
String orderSql = this.makeOrderByFieldSql("teacher_ids", teacherIds);
//使用last函数在查询的最后拼接sql语句
courseLambdaQueryWrapper.in(CourseResource::getTeacherId, resourceIds).last(orderSql);
//查询得到结果
courses = courseService.list(courseLambdaQueryWrapper);
前缀选择方法
[外链图片转存中…(img-Q8l1iFl7-1723616746850)]
alt+enter实现快速生成
生成成功
十一:mybatis使用过程中的问题
1、in语句结果无序变有序
https://blog.csdn.net/GUILTYxc/article/details/132950592
场景
当我在MySQL中使用IN语句进行查询时
例如SELECT * FROM Course WHERE teacher_id in (“6553”,“2145”,“3162”),从课程表里使用教师id查询记录,这时我希望返回的记录是按(“6553”,“2145”,“3162”)的顺序,但是发现并不是,而是(“2145”,“3162”,“6553”)的顺序,进行了排序。
如何解决?
可以使用ORDER BY FIELD语句。
SELECT * FROM Course WHERE teacher_id in (“6553”,“2145”,“3162”) ORDER BY FIELD(tracher_id,“6553”,“2145”,“3162”),其中tracher_id指定排序的字段,之后给出顺序,返回的结果即可满足按IN中指定的顺序排序。
MyBatisPlus
在MyBatis或MyBatisPlus中,可以手动拼接sql语句达到同样的效果。
方法(传入字段名和字段列表):
private String makeOrderByFieldSql(String fieldName, List<String> fields) {
StringBuilder orderSql = new StringBuilder();
orderSql.append("ORDER BY FIELD(").append(fieldName).append(",");
for (String field : fields) {
orderSql.append("\"").append(field).append("\"").append(",");
}
//去除最后一个 ",",加上括号
orderSql.deleteCharAt(orderSql.length() - 1).append(")");
return orderSql.toString();
}
使用时,构建:
//构建LambdaQueryWrapper
LambdaQueryWrapper<Course> courseLambdaQueryWrapper = new LambdaQueryWrapper<>();
//调用方法生成sql语句
String orderSql = this.makeOrderByFieldSql("teacher_ids", teacherIds);
//使用last函数在查询的最后拼接sql语句
courseLambdaQueryWrapper.in(CourseResource::getTeacherId, resourceIds).last(orderSql);
//查询得到结果
courses = courseService.list(courseLambdaQueryWrapper);