我的个人博客Alexios,欢迎大家来吐槽交流。
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
任何能使用 mybatis 进行 crud, 并且支持标准 sql 的数据库
使用一个springboot项目来体验MP的强大之处。
建表
create database mp;
use mp;
create table user(
id bigint(20) primary key auto_increment,
name varchar(50),
email varchar(50),
age int
);
insert into
user(name, email, age)
VALUES
('A','[email protected]',18),
('B','[email protected]',19),
('C','[email protected]',12),
('D','[email protected]',11),
('E','[email protected]',111);
建立spring-boot项目,引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.1.3.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<version>2.1.3.RELEASEversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.22version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.1.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.19version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13version>
dependency>
dependencies>
项目的application.yml配置文件如下
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf8&useSSL=false&nullCatalogMeansCurrent=true&serverTimezone=Asia/Shanghai
password: tianxin1230.
username: root
编写主启动类
@MapperScan("com.hzx.mpblog.mapper")
@SpringBootApplication
public class MpApplication {
public static void main(String[] args) {
SpringApplication.run(MpApplication.class);
}
}
根据数据库表建立对应entity实体类,这里使用Lombok插件并开启链式编程来简化开发
@Data
@Accessors(chain = true)
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
编写UserMapper接口,该接口需要继承BaseMapper< T >,其中T为要操作的数据库表对应的实体类
@Repository
public interface UserMapper extends BaseMapper<User> {
}
编写测试类,测试mybatis-plus为我们提供的查询所有方法
@SpringBootTest
@RunWith(SpringRunner.class)
public class MpApplicationTest {
@Autowired
private UserMapper userMapper;
@Test
public void testFindAll() {
List<User> userList = userMapper.selectList(null);
userList.forEach(user ->
System.out.println(user)
);
}
}
结果
我们可以在application.yml中添加配置,让mybatis-plus在执行时输出的sql语句
# 打印sql语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
重新启动,测试结果
使用Mybatis-plus自带的插入方法进行测试
@Test
public void testInsert() {
User user = new User();
user.setName("小明")
.setAge(18)
.setEmail("[email protected]");
int result = userMapper.insert(user);
System.out.println(result == 1 ? "插入成功!" : "插入失败!");
}
在这次插入操作中,我们没有指定插入数据库对象的id,这个属性是Mybatis-plus为我们生成的,Mybatis-plus提供的主键生成策略在枚举类IdType中.
public enum IdType {
AUTO(0),
NONE(1),
INPUT(2),
ID_WORKER(3),
UUID(4),
ID_WORKER_STR(5);
}
- 雪花算法是推特开源的分布式ID生成算法,结果是一个long型的ID。
- 核心思想是:使用41bit作为毫秒数,10bit作为及其的ID(5个bit是数据中心,5个bit的及其ID),12bit作为毫秒的流水号(意味着每个节点在每秒可以产生4096个Id),最后还有一个符号位永远是0
配置主键自增
- 在实体类字段上使用@TableId(type = IdType.AUTO)
- 要求数据库字段一定是自增的,否则会报错
AUTO(0):自增
NONE(1):不使用
INPUT(2):手动输入
ID_WORKER(3):默认唯一全局id
UUID(4):uuid
ID_WORKER_STR(5):默认唯一全局id字符串形式
使用mybatis-plus提供的updateById方法可以快速修改表中数据,更新Id为2L的对象信息
@Test
public void testUpdate() {
User user = new User();
user.setId(2L)
.setName("芜湖")
.setEmail("[email protected]")
.setAge(18);
int result = userMapper.updateById(user);
System.out.println(result == 1 ? "修改成功!" : "修改失败!");
}
在实际开发中,数据库往往有create_time和update_time两个字段,对于这两个字段,我们不希望手动填充,而希望程序或者数据库自动填充,mp提供了这个功能。
在数据库中添加create_time和update_time两个属性,并在实体类中添加对于字段。
在新建的两个属性下面勾选根据时间戳自动更新即可,这种方式不推荐使用
@Data
@Accessors(chain = true)
public class User {
private Long id;
private String name;
private Integer age;
private String email;
/***
* 字段添加时填充内容
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/***
* 字段添加/更新时填充内容
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
@Test
public void testInsert() {
User user = new User();
user.setName("小明111")
.setAge(18)
.setEmail("[email protected]");
int result = userMapper.insert(user);
System.out.println(result == 1 ? "插入成功!" : "插入失败!");
}
@Test
public void testUpdate() {
User user = new User();
user.setId(2L)
.setName("芜湖222")
.setEmail("[email protected]")
.setAge(18);
int result = userMapper.updateById(user);
System.out.println(result == 1 ? "修改成功!" : "修改失败!");
}
悲观锁十分悲观,他总是认为会出现问题,无论干什么都会上锁!
而乐观锁十分乐观,他总是认为不会出现问题,无论干什么都不去上锁!如果出现了问题,再次更新值测试。
下面介绍Mybatis-plus中乐观锁的实现方法
取出记录时,获取当前version
更新时带上这个version
执行更新时,set version = newVersion where version = oldVersion
如果version不对,就更新失败
# 乐观锁:
# 1 先查询,获得版本号version = 1
# 假设现有AB两个线程在执行这条update语句,当A未执行完成时,B抢先完成这次更新,那么此时由于版本号version已经为2,所以A线程的更新工作不会成功,此时就保证了线程间的通讯安全
update user set name = "芜湖",version = version + 1 where id = 2 and version = 1;
给数据库表中添加一个version属性
给实体类中添加一个version字段,同时在上面添加一个@Version注解,表明这是一个乐观锁
@Version
private Integer version;
在配置类中注册乐观锁组件
@Configuration
@EnableTransactionManagement
public class MyBatisPlusConfig {
/***
* 注册乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
根据传入的主键值查询数据
@Test
public void testSelectById() {
User user = userMapper.selectById(1L);
System.out.println(user);
}
传入一个主键集合,根据传入的多个主键值查询多条数据,使用selectBatchIds方法,该方法实际上使用mysql中的in关键字
@Test
public void testSelectByIds() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 5L));
users.forEach(user ->
System.out.println(user)
);
}
传入一个map,mp将会以键为字段,值为字段值进行拼接并查询,如果map中有多个键值对,那么会以and关键词拼接
@Test
public void testSelectByMap() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("id",1L);
List<User> users = userMapper.selectByMap(map);
users.forEach(user -> System.out.println(user));
}
@Test
public void testSelectByMaps() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("id",2L);
map.put("name","芜湖222");
List<User> users = userMapper.selectByMap(map);
users.forEach(user -> System.out.println(user));
}
一般来说,分页查询的实现有以下几种方式
- 使用limit关键字进行分页
- 使用PageHelper和PageInfo等第三方插件进行分页
- 使用Mybatis-plus自带的分页插件进行分页
下面介绍mp的分页插件
@Test
public void testPage() {
/***
* 第一个参数:当前页
* 第二个参数:每页数据条数
*/
Page<User> page = new Page<>(1,2);
//使用Mybatis-plus提供的selectPage方法,传入分页对象page
IPage<User> userIPage = userMapper.selectPage(page, null);
//得到的Page对象中,records属性就是需要的集合,该集合中包含了查询的结果
List<User> records = userIPage.getRecords();
records.forEach(user -> System.out.println(user));
}
public class Page<T> implements IPage<T> {
private List<T> records;
private long total;
private long size;
private long current;
private String[] ascs;
private String[] descs;
private boolean optimizeCountSql;
private boolean isSearchCount;
}
- List records–用来存放查询出来的数据
- long total–返回记录的总数
- long size–每页显示条数,默认 10
- long current–当前页,默认1
- String[] ascs–升序字段集合
- String[] descs–降序字段集合
- boolean optimizeCountSql–自动优化count sql,默认为true
- boolean isSearchCount–是否进行count查询,默认为true
使用mp提供的deleteById方法,返回值为数据库表中受影响数据条数
@Test
public void testDeleteById() {
int result = userMapper.deleteById(1L);
System.out.println(result == 1 ? "删除成功!" : "删除失败!");
}
使用mp提供的deleteBatchIds方法
@Test
public void testDeleteByIds() {
int result = userMapper.deleteBatchIds(Arrays.asList(2L,3L,1L));
System.out.println(result);
}
与通过map进行查询类似,当有多个键值对时,使用and拼接
@Test
public void testDeleteByMap() {
Map<String, Object> map = new HashMap();
map.put("name","E");
int result = userMapper.deleteByMap(map);
System.out.println(result);
}
@Test
public void testDeleteByMaps() {
Map<String, Object> map = new HashMap();
map.put("name","小明");
map.put("age",18);
int result = userMapper.deleteByMap(map);
System.out.println(result);
}
逻辑删除:在数据库中没有被移除,只是使用一个变量来使这一条记录失效
物理删除:从数据库中直接移除
管理员可以查看被删除的记录,类似于回收站
在表中添加一个deleted字段,
在实体类中添加一个deleted字段,并添加注解
/***
* 逻辑删除字段
* 添加一个逻辑删除注解
*/
@TableLogic
private Integer deleted;
配置逻辑删除组件
//配置逻辑删除组件
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
在application.yml文件中配置
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
先在数据库中新插入几条数据,以便后面的测试
@Test
public void testLoginDeleted() {
int result = userMapper.deleteById(3L);
}
我们在平时开发中,会遇到一些慢sql,mp提供了一个性能分析插件,如果超过指定时间,就停止运行
性能分析拦截器,用于输出每条sql语句及其执行时间
在application.yml中配置开发环境,将当前环境设置为dev
spring:
profiles:
active: dev
在配置类中配置sql执行效率分析插件
/***
* sql执行效率分析插件
* @Profile({"dev","test"})表示此插件只在生产和测试环境下使用
*/
@Bean
@Profile({"dev","test"})
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
//设置sql执行的最大时间,如果超过这个时间就不执行
performanceInterceptor.setMaxTime(1);
//设置格式化SQL语句(美化)
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
@Test
public void testUpdateAndSelect() {
User user = new User();
user.setId(1L);
user.setName("芜湖");
user.setEmail("[email protected]");
user.setAge(1222);
int result = userMapper.updateById(user);
System.out.println(result);
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out :: println);
}
将最大时间设置为100ms,再次测试,修改 performanceInterceptor.setMaxTime(100);
Wrapper是一个接口,我们使用Wrapper的实现类:QueryWrapper来实现条件构造
这个字段的值不为空
这个字段的值必须大于等于/等于/不等于/小于等于/严格大于/严格小于传入的值
这个属性的值必须在左边值和右边值之间
字段值 not like “%值%”
字段 like “%值”/“值%”
字段 in/notIn(v0,v1…vn)
字段 in/notIn (sql语句),例如 inSql/notInSql(“id”,select * from user where id < 3)
等价于id in/notIn (select * from user where id < 3)
- 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
查询name、email不为空,且年龄大于等于12岁的用户(.isNotNull()、.ge())
/***
* 查询name、email不为空,且年龄大于等于12岁的用户(.isNotNull()、.ge())
*/
@Test
public void testQuestion01() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.isNotNull("email");
wrapper.isNotNull("name");
wrapper.ge("age",12);
List users = userMapper.selectList(wrapper);
users.forEach(user -> System.out.println(user));
}
结果
sql语句为:
==> Preparing: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND email IS NOT NULL AND name IS NOT NULL AND age >= ?
==> Parameters: 12(Integer)
查询name属性等于"小明"的用户
/***
* 查询name属性等于"小明"的用户
*/
@Test
public void testQuestion02() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("name","小明");
List users = userMapper.selectList(wrapper);
users.forEach(user -> System.out.println(user));
}
查询年龄在10-30之间的用户个数
/***
* 查询年龄在10-30之间的用户个数
*/
@Test
public void testQuestion03() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.between("age",10,30);
int count = userMapper.selectCount(wrapper);
System.out.println(count);
}
查询id小于等于4且没被逻辑删除的用户
/***
* 查询id小于等于4且没被逻辑删除的用户
*/
@Test
public void testQuestion04() {
QueryWrapper wrapper = new QueryWrapper();
String inSql = "select id from user where id <= 4";
wrapper.inSql("id",inSql);
List users = userMapper.selectList(wrapper);
users.forEach(user -> System.out.println(user));
}
SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND id IN (select id from user where id <= 4)
通过id进行降序排序
/***
* 查询所有用户,并根据id进行降序排序
*/
@Test
public void testQuestion05() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.orderByDesc("id");
List users = userMapper.selectList(wrapper);
users.forEach(user -> System.out.println(user));
}
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
代码生成器需要使用模板引擎,这里使用velocity模板引擎
<dependency>
<groupId>org.apache.velocitygroupId>
<artifactId>velocity-engine-coreartifactId>
<version>2.2version>
dependency>
配置需要生成对于后台的数据库,这里以leyou_shop数据库的user表为例,生成后台代码
这里以leyou_shop库的spu表为例
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
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.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;
/**
* @author 蔡大头
* @title: CodeAutoGenerator
* @projectName mybatis-plus-blog
* @description: TODO
* @date 2020/12/1016:47
*/
public class CodeAutoGenerator {
@Test
public void generator() {
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir("E:\\java\\blog\\src\\main\\java");
gc.setAuthor("蔡大头");
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.AUTO); //主键策略
gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
gc.setSwagger2(true);//开启Swagger2模式
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/leyou_shop?serverTimezone=UTC");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("tianxin1230.");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.hzx");
pc.setModuleName("mpblog"); //模块名
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("spu");
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}
}
生成路径、主键策略
设置包名和模块名
设置要生成的表
启动程序,查看结果
Mybatis-plus只对mybatis做增强,不做修改,所以Mybatis有的功能,MP均能实现,对于一些复杂的查询,可以使用写xml文件的方式来完成。