文章目录
MybatisPlus
概念
国人开发的Mybatis的便捷版。
使用步骤
-
导入相关的starter:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
-
配置数据源:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC username: root password: zzc
-
如果数据源需要进行个性化的配置,就需要导入对应数据源的starter,如导入Druid的:
<dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> </dependencies>
修改配置:
spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC username: root password: root
-
-
创建数据层的 Dao/Mapper 接口
接口继承BaseMapper接口 —— BaseMapper接口中提供了许多常用方法,我们只需要从容器中获取Mapper就可以了,不用自己去编写sql语句。
扫描:可以直接在接口上加@Mapper,或者在启动类上设置扫描@MapperScan(“xxx”)
@Mapper public interface BookDao extends BaseMapper<Book> { }
-
测试
常用设置
表的映射规则
默认情况下,MP操作的表名就是实体类的类名,但如果表名与类名不一致就需要设置映射规则。
单独设置: 在实体类上加上 @TableName
@TableName("tbl_book")
@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
全局设置: 一般一个项目的表名的前缀有统一风格,可以通过 .yml 配置文件配置
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_ #设置表名通用前缀
主键生成策略
默认情况下,使用MP插入数据时,主键生成策略是基于雪花算法的自增id。
如果需要使用别的策略,可以:
单独设置: 在代表主键的字段上加上 @TableId,使用其type属性指定主键生成策略。
@Data
public class Book {
//设置主键自增长
@TableId(type = IdType.AUTO)
private Integer id;
...
}
全局设置:
mybatis-plus:
global-config:
db-config:
id-type: auto #设置主键id字段的生成策略 为参照数据库设定的策略,默认是使用 基于雪花算法的自增id
字段映射
默认MP会根据实体类的属性名去映射表的列名。
如果列名和实体类的属性不一致,可以用 @TableField 设置映射关系。
@TableField("address")
private String addressStr;
驼峰映射
默认情况下MP会开启字段名列名的驼峰映射,即将驼峰命名的属性翻译成下划线命名,如:userName会翻译成 user_name
sql执行日志
配置sql日志输出,即打印对应的sql执行语句:
mybatis-plus:
configuration:
# 日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
数据层使用
基本方法
包名为dao或mapper, 数据层接口需要继承BaseMapper,BaseMapper提供常见的sql方法。
- select() —— 查询
- selectBatchIds(list) 批量查询
- insert() —— 插入数据
- delete() —— 删除数据
- deleteBatchIds(list) 批量删除
- update() —— 更新数据
条件构造器Wrapper
wrapper是一个接口,用于更方便的构造条件。
继承体系:
- wrapper
- AbstractWrapper
- QueryWrapper
- UpdateWrapper
- AbstractWrapper
AbstractWrapper
提供了许多构造where条件的方法;
QueryWrapper
额外提供了针对Select语法的select
方法;
UpdateWrapper
额外提供了用于针对set语法的set
方法;
提供的方法中,可以加上一个Boolean参数,表示是否要加上当前的条件;因为有些时候传入的变量可能是null,而查询时会直接当作字符串处理,所以可以加上 变量 != null
、Strings.isNotEmpty(变量)
等等, 来判断。
@Test
public void test(){
QueryWrapper wrapper = new QueryWrapper();
wrapper.like(Strings.isNotEmpty(name), User::getName, name);
userMapper.selectList(wrapper);
}
AbstractWrapper
完整的AbstractWrapper方法参照官方文档:条件构造器 | MyBatis-Plus (baomidou.com)
常用AbstractWrapper方法:
eq:equals,等于
gt:greater than ,大于 >
ge:greater than or equals,大于等于≥
lt:less than,小于<
le:less than or equals,小于等于≤
between:相当于SQL中的BETWEEN
like:模糊匹配。like(“name”,“黄”),相当于SQL的name like ‘%黄%’
likeRight:模糊匹配右半边。likeRight(“name”,“黄”),相当于SQL的name like ‘黄%’
likeLeft:模糊匹配左半边。likeLeft(“name”,“黄”),相当于SQL的name like ‘%黄’
notLike:notLike(“name”,“黄”),相当于SQL的name not like ‘%黄%’
isNull
isNotNull
and:SQL连接符AND
or:SQL连接符OR
in: in(“age",{1,2,3})相当于 age in(1,2,3)
groupBy: groupBy(“id”,“name”)相当于 group by id,name
orderByAsc :orderByAsc(“id”,“name”)相当于 order by id ASC,name ASC
orderByDesc :orderByDesc (“id”,“name”)相当于 order by id DESC,name DESC
例1:
SQL语句如下:
SELECT
id,user_name,PASSWORD,NAME,age,address
FROM
`USER`
WHERE
age > 18 AND address = '狐山'
用Wrapper写法如下:
@Test
public void testWrapper01(){
QueryWrapper wrapper = new QueryWrapper();
wrapper.gt("age",18);
wrapper.eq("address","狐山");
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
例2:
SQL语句如下:
SELECT
id,user_name,`PASSWORD`,NAME,age,address
FROM
`USER`
WHERE
id IN(1,2,3) AND
age BETWEEN 12 AND 29 AND
address LIKE '%山%'
用Wrapper写法如下:
@Test
public void testWrapper02(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.in("id",1,2,3);
wrapper.between("age",12,29);
wrapper.like("address","山");
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
例3:
SQL语句如下:
SELECT
id,user_name,`PASSWORD`,NAME,age,address
FROM
USER
WHERE
id IN(1,2,3) AND
age > 10
ORDER BY
age DESC
用Wrapper写法如下:
@Test
public void testWrapper03(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.in("id",1,2,3);
queryWrapper.gt("age",10);
queryWrapper.orderByDesc("age");
List<User> users = userMapper.selectList(queryWrapper);
System.out.println(users);
}
QueryWrapper
QueryWrapper的 select() 可以设置 要/不要查询的列名:
-
select(String s… )—— 形参为要查询的列名
-
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) —— 第一个参数为实体类的class字节码对象,第二个参数是Predicate类型,用于过滤要查询的字段(主键除外)
// sql语句: SELECT id,user_name FROM USER 查user_name,而id作为主键默认会查 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.select(User.class, new Predicate<TableFieldInfo>() { @Override public boolean test(TableFieldInfo tableFieldInfo) { //返回true为要查询,false为不查询 return "user_name".equals(tableFieldInfo.getColumn()); } }); List<User> users = userMapper.selectList(queryWrapper);
-
select(Predicate<TableFieldInfo> predicate)
// sql语句: SELECT id,user_name,PASSWORD,NAME,age FROM USER 即不查address字段,其他都查 QueryWrapper<User> queryWrapper = new QueryWrapper<>(new User()); queryWrapper.select(new Predicate<TableFieldInfo>() { @Override public boolean test(TableFieldInfo tableFieldInfo) { return !"address".equals(tableFieldInfo.getColumn()); } }); List<User> users = userMapper.selectList(queryWrapper);
UpdateWrapper
UpdateWrapper的set方法来设置要更新的列及其值,不用传入一个类,同时这种方式也可以使用Wrapper去指定更复杂的更新条件。
//UPDATE USER
// SET age = 99
// where id > 1
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.gt("id",1);
updateWrapper.set("age",99);
userMapper.update(null,updateWrapper);
Lambda条件构造器
MP提供了一个Lambda条件构造器可以让我们直接以实体类的方法引用的形式来指定列名。这样就可以避免在条件构造中 写错列名 了。
QueryWrapper<User> queryWrapper = new QueryWrapper();
//queryWrapper.gt("age",18);
//queryWrapper.eq("address","狐山");
queryWrapper.gt(User::getAge,18);
queryWrapper.eq(User::getAddress,"狐山");
自定义SQL
虽然MP为我们提供了很多常用的方法,并且也提供了条件构造器。但是如果真的遇到了复制的SQL时,我们还是需要自己去定义方法,自己去写对应的SQL,这样SQL也更有利于后期维护。
因为MP是对mybatis做了增强,所以还是支持之前Mybatis的方式去自定义方法。
同时也支持在使用Mybatis的自定义方法时使用MP的条件构造器帮助我们进行条件构造。
Mybatis方式
1、定义方法
在Mapper接口中定义方法
public interface UserMapper extends BaseMapper<User> {
User findMyUser(Long id);
}
2、创建xml
先配置xml文件的存放目录
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
创建对应的xml映射文件
3、在xml映射文件中编写SQL
创建对应的标签,编写对应的SQL语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sangeng.mapper.UserMapper">
<select id="findMyUser" resultType="com.sangeng.domian.User">
select * from user where id = #{id}
</select>
</mapper>
Mybatis方式结合条件构造器
我们在使用上述方式自定义方法时。如果也希望我们的自定义方法能像MP自带方法一样使用条件构造器来进行条件构造的话只需要使用如下方式即可。
①方法定义中添加Warpper类型的参数
添加Warpper类型的参数,并且要注意给其指定参数名。
public interface UserMapper extends BaseMapper<User> {
User findMyUserByWrapper(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
}
②在SQL语句中获取Warpper拼接的SQL片段进行拼接。
<select id="findMyUserByWrapper" resultType="com.sangeng.domian.User">
select * from user ${ew.customSqlSegment}
</select>
注意:不能使用#{}应该用${}
分页查询
- 配置分页查询拦截器
@Configuration
public class PageConfig {
/**
* 3.4.0之前的版本
* @return
*/
/* @Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}*/
/**
* 3.4.0之后版本
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
-
接口:IPage, 实现类:Page
//分页功能 @Test void testGetPage(){ IPage page = new Page(2,5); //第2页,每页5条 bookDao.selectPage(page, null); //第二个参数为wrapper page.getCurrent(); //当前页码值 page.getSize(); //每页显示数 page.getTotal(); //数据总量 page.getPages(); //总页数 page.getRecords(); //详细数据 }
业务层Service
包名一般为service,也叫service层。
Service接口定义与数据层接口定义的差别:
- 业务层的接口名称一般是业务名,如登录业务就叫 login(username, passwd)
- 数据层的接口名称,一般要写明用什么查什么,如登录业务中要调用的查询用户信息功能,叫 selectByUserNameAndPassword(username, passwd)
MP提供了service层的实现,只需要编写一个接口,继承IService
,并创建一个接口实现类继承ServiceImpl
,即可使用。
相比于Mapper接口,Service层主要是支持了更多批量操作的方法。
如:
-
之前的写法:
public interface UserService { List<User> list(); }
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public List<User> list() { return userMapper.selectList(null); } }
-
现在的写法:
public interface UserService extends IService<User> { }
@Service public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService { }
需要其他的自定义方法的话,直接加就行。
更多功能
代码生成器
MP提供了一个代码生成器,可以让我们一键生成实体类,Mapper接口,Service,Controller等全套代码 。使用方式如下
- 添加依赖
<!--mybatisplus代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!--模板引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
- 生成
修改相应配置后执行以下代码即可
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
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.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.junit.jupiter.api.Test;
public class GeneratorTest {
@Test
public void generate() {
AutoGenerator generator = new AutoGenerator();
// 全局配置
GlobalConfig config = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
// 设置输出到的目录
config.setOutputDir(projectPath + "/src/main/java");
config.setAuthor("zzc");
// 生成结束后是否打开文件夹
config.setOpen(false);
// 全局配置添加到 generator 上
generator.setGlobalConfig(config);
// 数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=UTC");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("zzc");
// 数据源配置添加到 generator
generator.setDataSource(dataSourceConfig);
// 包配置, 生成的代码放在哪个包下
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("com.zzc");
// 包配置添加到 generator
generator.setPackageInfo(packageConfig);
// 策略配置
StrategyConfig strategyConfig = new StrategyConfig();
// 下划线驼峰命名转换
strategyConfig.setNaming(NamingStrategy.underline_to_camel);
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
// 开启lombok
strategyConfig.setEntityLombokModel(true);
// 开启RestController
strategyConfig.setRestControllerStyle(true);
generator.setStrategy(strategyConfig);
generator.setTemplateEngine(new FreemarkerTemplateEngine());
// 开始生成
generator.execute();
}
}
自动填充
在进行插入或更新操作时,对字段自动填充。
使用:
-
在需要填充的字段上添加注解 TableField, 其属性 fill 来设定什么时候进行自动填充。
/** * 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime;
-
继承自定义填充器MetaObjectHandler,实现两个方法(插入时填充,更新时填充)
@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("createTime", LocalDateTime.now(), metaObject); this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject); } @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject); } }
逻辑删除
逻辑删除:数据没有被删除,只是不会被展示出来;
使用:
-
先在数据库 配置好逻辑删除的实体字段名,并规定表示删除和未删除状态的字段值,如规定 delFlag = 1为已删除,delFlag = 0 为未删除;
-
再配置yaml文件:
mybatis-plus: global-config: db-config: logic-delete-field: delFlag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
乐观锁
并发操作时,我们需要保证对数据的操作不发生冲突。乐观锁就是其中一种方式。
乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。.因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
使用乐观锁时一般在表中增加一个version列。用来记录我们对每次记录操作的版本。每次对某条记录进行过操作是,对应的版本也需要+1。
在执行更新时: set version = 老版本+1 where version = 老版本
使用:
-
添加对应MP拦截器
@Configuration public class MPConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ //创建MP拦截器栈 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //分页拦截器 interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); //乐观锁拦截器 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }
-
在实体类的字段上加上
@Version
注解@Version private Integer version;
-
测试