具体可查看官方文档 https://baomidou.com/guide 。以下仅为笔记。
pom.xml
必要依赖:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.4version>
dependency>
application配置文件
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://192.168.72.149:3306/mybatis_plus?userSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
注:
如果想使用默认的Hikari数据源,则注释掉pom.xml中的druid依赖和application配置文件中spring.datasource.type
com.mysql.jdbc.Driver 是 mysql-connector-java 5中的
com.mysql.cj.jdbc.Driver 是 mysql-connector-java 6中的,相比mysql-connector-java 5 多了一个时区:serverTimezone,
把数据源配置的url中增加:serverTimezone=GMT%2B8 GMT%2B8表示北京时间东八区
pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
private String email;
}
Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
}
启动类
@SpringBootApplication
@MapperScan("com.jarvis.mptest.Mapper") //需要将Mapper包下的所有接口扫描到ioc中
public class MptestApplication {
public static void main(String[] args) {
SpringApplication.run(MptestApplication.class, args);
}
}
数据库表
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id) //注:此时id不是自增的
);
DELETE FROM user;
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
测试
@SpringBootTest
class MptestApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
=====output:
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
# 配置日志 StdOutImpl--打印控制台
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
User user = new User();
user.setName("yyb");
user.setAge(25);
user.setEmail("[email protected]");
int count = userMapper.insert(user);
log.info("插入{}条",count);
log.info("user={}",user);
报错:Could not set property 'id' of 'class com.jarvis.mptest.pojo.User'
with value '1344543840895275009' Cause: java.lang.IllegalArgumentException:
argument type mismatch
生成的id为:1344543840895275009,报错提示:将id回存给User的时候类型不匹配
将User的id类型从Integer改为Long,可插入成功,但是id不是自增的,输出:
插入1条
user=User(id=1344563190515642370, name=yyb, age=25, email=yyb97456@gmail.com)
主键生成策略:1. uuid 2. 自增id 3. 雪花算法 4. redis生成 …
雪花算法
SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的
3.4.1版本的mybatis-plus的主键生成策略如下
@Getter
public enum IdType {
//数据库ID自增 该类型请确保数据库设置了ID自增,否则无效
AUTO(0),
//未设置主键类型
NONE(1),
//手动输入ID
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
//分配ID (主键类型为number或string)
//默认实现类 { com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
ASSIGN_ID(3),
//UUID (主键类型为 string)
//默认实现类 { com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
ASSIGN_UUID(4),
//不推荐使用, 3.3.0 请使用 ASSIGN_ID
ID_WORKER(3),
//不推荐使用, 3.3.0 请使用 ASSIGN_ID
ID_WORKER_STR(3),
//不推荐使用, 3.3.0 请使用 ASSIGN_UUID
UUID(4);
private final int key;
IdType(int key) {
this.key = key;
}
}
AUTO----自增主键
INPUT----手动设置id
pojo:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.INPUT) //需要手动设置id值
private Integer id;
...
}
测试1----不设置id:
@Test
void testInsert(){
User user = new User(); //未设置id
user.setName("yyb787");
user.setAge(25);
user.setEmail("[email protected]");
int count = userMapper.insert(user);
log.info("插入{}条",count);
log.info("user={}",user);
}
当数据库表id不自增,会报错:Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null
当数据库表id自增,如果不设置id,则不报错,写入的是自增后的id。
测试2----id重复
@Test
void testInsert(){
User user = new User(6,"asdf",5,"asdfadsf");
int count = userMapper.insert(user);
log.info("插入{}条",count);
log.info("user={}",user);
}
报错:Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '6' for key 'PRIMARY'
ASSIGN_ID----雪花算法
注:id类型需要改为Long
ASSIGN_UUID----uuid
pojo:
@TableId(type = IdType.ASSIGN_UUID)
private String id;
测试:
@Test
void testInsert(){
User user = new User(null,"小王",15,"[email protected]");
int count = userMapper.insert(user);
log.info("插入{}条",count);
log.info("user={}",user);
}
updateById:传入的对象某个字段如果为null,则在拼接动态sql的时候该字段被忽略
User user = new User(1,null,null,"[email protected]");
int i = userMapper.updateById(user);
log.info("修改{}条",i);
================
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@559cedee] will not be managed by Spring
==> Preparing: UPDATE user SET email=? WHERE id=? //只拼接了email这个非null的字段
==> Parameters: 17816878521@163.com(String), 1(Integer)
<== Updates: 1
阿里巴巴开发手册:
【强制】表必备三字段:id, gmt_create, gmt_modified。
说明:其中 id 必为主键,类型为bigint unsigned、单表时自增、步长为 1。
gmt_create, gmt_modified 的类型均为 datetime类型,前者现在时表示主动式创建,后者过去分词表示被动式更新。
1. 将pojo的User新增两个字段:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(value = "id",type = IdType.AUTO)
private Integer Id;
private String name;
private Integer age;
private String email;
@TableField(fill = FieldFill.INSERT) //将创建时间在插入时自动填充
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) //将修改时间在插入和修改操作时自动填充
private LocalDateTime updateTime;
}
2. 编写处理器来处理注解 ---- 不同版本的MP有不同的填充方法
@Slf4j
@Component
public class PojoMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时的填充策略
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
//填充的是实体类属性名称,而不是数据库的字段名称
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
this.strictInsertFill(metaObject,"updateTime",LocalDateTime.class, LocalDateTime.now());
// // 或者
// this.strictUpdateFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// // 或者
// this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
/**
* 更新时的填充策略
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
// // 或者
// this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// // 或者
// this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
}
测试新增数据
@Test
void testInsert(){
User user = new User();
user.setName("小王");
user.setEmail("[email protected]");
user.setAge(26);
int count = userMapper.insert(user);
log.info("插入{}条",count);
log.info("user={}",user);
}
@Test
void testUpdate(){
User user = new User();
user.setId(102);
user.setEmail("[email protected]");
int i = userMapper.updateById(user);
log.info("修改{}条",i);
}
乐观锁实现方式:
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败
说明:
支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
整数类型下 newVersion = oldVersion + 1
newVersion 会回写到 entity 中
仅支持 updateById(id) 与 update(entity, wrapper) 方法
在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
1. 在数据库中增加一个字段version,类型int,默认值设置为1
2. pojo的User增加属性version
@Version //乐观锁注解
private Integer version;
3. 注册乐观锁组件
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
@Bean //新版本(当前测试版本3.4.1)会提示OptimisticLockerInterceptor不推荐使用,但是仍能起作用
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
或:
@Bean //新版本(当前测试版本3.4.1)可使用如下方法配置
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
4. 测试乐观锁
1. 先查询,后更新
@Test
void testOptimisticLocker(){
User user = userMapper.selectById(104);
System.out.println(user);
user.setEmail("[email protected]");
userMapper.updateById(user);
}
==> Preparing: UPDATE user SET name=?, birth=?, age=?, email=?, create_time=?, update_time=?, version=? WHERE id=? AND version=? //加了AND version=?
==> Parameters: 小王(String), 2018-05-01T15:26:09(LocalDateTime), 26(Integer), opop@163.com(String), 2021-01-03T19:20:42(LocalDateTime), 2021-01-03T20:25:16(LocalDateTime), 4(Integer), 104(Integer), 3(Integer)
<== Updates: 1
2. 直接更新,无法动态拼接AND version=?
@Test
void testOptimisticLocker(){
User user = new User();
user.setId(104);
user.setEmail("[email protected]");
userMapper.updateById(user);
}
==> Preparing: UPDATE user SET email=?, update_time=? WHERE id=? //没有加and version=?
==> Parameters: opuuddop@163.com(String), 2021-01-03T20:25:16.297(LocalDateTime), 104(Integer)
<== Updates: 1
3. 测试乐观锁失败
@Test
void testOptimisticLocker() throws InterruptedException {
User user = userMapper.selectById(104);
System.out.println(user); //User(Id=104, name=小王, birth=2018-05-01T15:26:09, age=26, [email protected], createTime=2021-01-03T19:20:42, updateTime=2021-01-03T20:25:16, version=5)
Thread.sleep(5000); //睡眠5s,此时手动去修改数据库中的version为6,导致本次无法update成功
user.setEmail("[email protected]");
userMapper.updateById(user);
}
==> Preparing: UPDATE user SET name=?, birth=?, age=?, email=?, create_time=?, update_time=?, version=? WHERE id=? AND version=?
==> Parameters: 小王(String), 2018-05-01T15:26:09(LocalDateTime), 26(Integer), opop@163.com(String), 2021-01-03T19:20:42(LocalDateTime), 2021-01-03T20:25:16(LocalDateTime), 6(Integer), 104(Integer), 5(Integer)
<== Updates: 0
@Test
void testSelect(){
//根据id查询
User user = userMapper.selectById(103);
//SELECT id,name,birth,age,email,create_time,update_time,version FROM user WHERE id=?
System.out.println(user);
//根据id集合批量查询
List<User> users = userMapper.selectBatchIds(Arrays.asList(103, 104));
//SELECT id,name,birth,age,email,create_time,update_time,version FROM user WHERE id IN ( ? , ? )
users.forEach(System.out::println);
//按条件查询 map
HashMap<String,Object> map = new HashMap<>();
map.put("name","小李");
map.put("email","[email protected]");
List<User> users1 = userMapper.selectByMap(map);
//SELECT id,name,birth,age,email,create_time,update_time,version FROM user WHERE name = ? AND email = ?
System.out.println(users1);
//按条件查询 Wrapper
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.eq("name","小李").eq("email","[email protected]");
List<User> users2 = userMapper.selectList(wrapper);
//SELECT id,name,birth,age,email,create_time,update_time,version FROM user WHERE (name = ? AND email = ?)
System.out.println(users2);
}
1. 注册分页插件 ---- 不同版本的MP有不同的注册方法
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
@Bean //新版本(当前测试版本3.4.1)会提示PaginationInterceptor 不推荐使用
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
或:
@Bean //新版本(当前测试版本3.4.1)可使用如下方法配置
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor();
// 设置请求的页面大于最大页后操作,true调回到首页,false继续请求。默认false
pageInterceptor.setOverflow(false);
// 单页分页条数限制,默认无限制
pageInterceptor.setMaxLimit(500L);
// 设置数据库类型
pageInterceptor.setDbType(DbType.MYSQL);
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
}
2. 使用Page对象
@Test
void testSelectPage(){
Page<User> page = new Page<>(2,5);
//selectPage方法将查询的数据封装到了Page对象的records属性中(该属性是个List),并且返回这个Page对象
Page<User> page1 = userMapper.selectPage(page, null); //page1 = page
long total = page.getTotal(); //getTotal需要在selectPage方法之后才能调用
System.out.println("total="+total);
page.getRecords().forEach(System.out::println);
}
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@29b40b3] will not be managed by Spring
//selectPage一个方法中一共查询两次数据库
//第一次查询:查询总数,并且把查询的总数赋值给Page对象的total属性,所以getTotal需要在selectPage方法之后才能调用
==> Preparing: SELECT COUNT(*) FROM user
==> Parameters:
<== Columns: COUNT(*)
<== Row: 14
<== Total: 1
//第二次查询分页数据
==> Preparing: SELECT id,name,birth,age,email,create_time,update_time,version FROM user LIMIT ?,?
==> Parameters: 5(Long), 5(Long)
<== Columns: id, name, birth, age, email, create_time, update_time, version
<== Row: 108, 小王02, 2018-05-01 15:26:09, 26, 17816549851@163.com, 2021-01-04 15:42:45, 2021-01-04 15:42:45, 1
<== Row: 109, 小王02, 2018-05-01 15:26:09, 26, 17816549851@163.com, 2021-01-04 15:42:45, 2021-01-04 15:42:45, 1
<== Row: 110, 小王02, 2018-05-01 15:26:09, 26, 17816549851@163.com, 2021-01-04 15:42:45, 2021-01-04 15:42:45, 1
<== Row: 111, 小王02, 2018-05-01 15:26:09, 26, 17816549851@163.com, 2021-01-04 15:42:45, 2021-01-04 15:42:45, 1
<== Row: 112, 小王02, 2018-05-01 15:26:09, 26, 17816549851@163.com, 2021-01-04 15:42:45, 2021-01-04 15:42:45, 1
<== Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@439f2d87]
total=10
@Test
void testDelete(){
userMapper.deleteById(103);
userMapper.deleteBatchIds(Arrays.asList(104,105,106));
HashMap<String, Object> map = new HashMap<>();
map.put("name","小王02");
userMapper.deleteByMap(map);
}
通过设置字段值来使得某些记录失效,如is_delete
1. 在数据库的user表中增加is_delete字段,默认值为0
2. 在pojo的User类中增加isDelete属性,类型为Boolean或Integer,并且增加逻辑删除的注解:@TableLogic
3. application文件中配置(新版本写法)
# 配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
测试
@Test
void testDeleteLogic(){
userMapper.deleteById(128);
//UPDATE user SET is_delete=1 WHERE id=? AND is_delete=0
User user = userMapper.selectById(128);
//SELECT id,name,birth,age,email,create_time,update_time,version,is_delete FROM user WHERE id=? AND is_delete=0 自动拼接了逻辑删除的判断
System.out.println(user); //null
}
在新版本(3.3.0以后)使用p6spy,但该插件有性能损耗,不建议生产环境使用。
不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输
1. wrapper 很重
2. 传输 wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场)
3. 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作
4. 我们拒绝接受任何关于 RPC 传输 Wrapper 报错相关的 issue 甚至 pr
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
1. pom.xml添加代码生成器依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>org.freemarkergroupId>
<artifactId>freemarkerartifactId>
<version>2.3.30version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
2. 代码生成器
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
public class CodeGeneratorTest {
@Test
void test(){
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// *********************************全局配置*********************************
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir"); //获取项目路径
gc.setOutputDir(projectPath + "/src/main/java"); //设置代码生成路径
gc.setAuthor("jarvis");
gc.setOpen(false); //生成后是否打开文件夹,设置为不打开
gc.setFileOverride(false); //设置是否覆盖原来的
gc.setIdType(IdType.AUTO); //设置主键生成策略
gc.setServiceName("%sService"); //去Service的I前缀 无此配置生成的为IUserService
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
// *********************************数据源配置*********************************
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://192.168.72.152:3306/mybatis_plus?userSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8");
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("gmall");
pc.setParent("com.jarvis");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
// *********************************策略配置*********************************
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("user","order"); //生成哪些表的代码
strategy.setNaming(NamingStrategy.underline_to_camel); //下划线转驼峰
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
strategy.setEntityLombokModel(true); //是否开启Lombok
strategy.setRestControllerStyle(true); //设置Controller使用restful风格
strategy.setLogicDeleteFieldName("is_delete"); //设置逻辑删除的字段
//设置填充配置
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(createTime);
tableFills.add(updateTime);
strategy.setTableFillList(tableFills);
//乐观锁
strategy.setVersionFieldName("version");
mpg.setStrategy(strategy);
//*********************************自定义配置Mapper.xml生成路径*********************************
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) {
// 自定义输入文件名称
return projectPath + "/src/main/resources/mapper/"
+ tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
//application文件中加:mybatis-plus.mapper-locations=classpath:mapper/*.xml
}
});
cfg.setFileOutConfigList(focList);
//选择 freemarker 引擎需要指定如下,注意 pom 依赖必须有!
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.setCfg(cfg);
mpg.setTemplate(new TemplateConfig().setXml(null));
//*********************************生成代码*********************************
mpg.execute();
}
}
3. 生成代码