MyBatisPlus学习(1)
MyBatisPlus是MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。
依赖引入:在Mybatis的基础上添加
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
基本使用
首先我们需要直到,MybatisPlus内置所有SQL都是不可见的,因此为了在后台能看到SQL的执行,我们需要配置日志。
我们在配置文件中配置:
#配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
我们设定初始表:
public class User {
Long id;
String name;
Integer age;
}
创建对应的UserMapper:
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
这里我们直接继承MyBatisPlus提供的BaseMapper,泛型使用对应的Data,该继承内部封装了大部分我们需要的代码,这样我们就不需要在写UserMapper的逻辑了。
测试使用:
@Controller
public class TestController {
@Autowired
UserMapper userMapper;
@RequestMapping("/test")
@ResponseBody
public void test(){
User user=new User();
user.setName("test");
user.setAge(12);
userMapper.insert(user);
}
}
我们发现直到现在,我们都没去设定目标表,MybatisPlus会自动去设定目标表,具体为类的小写对应表,这里如果我们设定表名为users,就找不到了。
但MyBatisPlus自然也存在指定表的功能,同理,也可以指定字段名:
@Data
@TableName("users") //对应的表名
public class User {
@TableId("id") //对应的主键
Long id;
@TableField("name") //对应的字段
String name;
@TableField("age")
Integer age;
}
我们执行insert,就成功插入,并且我们发现,MybatisPlus自动为我们添加了id:
自定义ID生成器
上面我们提到了MybatisPlus会自动为我们插入ID添加一个全局唯一ID。我们可以自定义ID生成
在User
中,我们给id字段添加了@TableId注解,内部可以设定主键生成的方法
自 3.3.0 开始,默认使用雪花算法+UUID(不含中划线)
//对应数据库中的主键(UUID、自增id、雪花算法、redis、zookeeper)
@TableId(type = IdType.ASSIGN_ID) //对应的主键
Long id;
我们打开IdType
就能看到其他生成方法:
/**
* 生成ID类型枚举类
*
* @author hubin
* @since 2015-11-10
*/
@Getter
public enum IdType {
/**
* 数据库ID自增
* <p>该类型请确保数据库设置了 ID自增 否则无效</p>
*/
AUTO(0),
/**
* 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
*/
NONE(1),
/**
* 用户输入ID
* <p>该类型可以通过自己注册自动填充插件进行填充</p>
*/
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
* 分配ID (主键类型为number或string),
* 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
*
* @since 3.3.0
*/
ASSIGN_ID(3),
/**
* 分配UUID (主键类型为 string)
* 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
*/
ASSIGN_UUID(4);
private final int key;
IdType(int key) {
this.key = key;
}
}
1. UUId
UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:550e8400-e29b-41d4-a716-446655440000,到目前为止业界一共有5种方式生成UUID,详情见IETF发布的UUID规范
优点:
- 性能非常高:本地生成,没有网络消耗。
缺点:
- 没有排序,无法保证趋势递增。
- UUID往往使用字符串存储,查询的效率比较低。
- 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
- 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
- ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用:
- MySQL官方有明确的建议主键要尽量越短越好,36个字符长度的UUID不符合要求。
- 对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。
2. 雪花算法
这种方案大致来说是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等,比如在snowflake中的64-bit分别表示如下图(图片来自网络)所示:
41-bit的时间可以表示(1L<<41)/(1000L360024*365)=69年的时间,10-bit机器可以分别表示1024台机器。如果我们对IDC划分有需求,还可以将10-bit分5-bit给IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,可以根据自身需求定义。12个自增序列号可以表示2^12个ID,理论上snowflake方案的QPS约为409.6w/s,这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。
核心思想:
使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号位,永远是0,。
优点:
- 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
- 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
- 可以根据自身业务特性分配bit位,非常灵活。
缺点:
- 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
更新操作
例子:
public void testUpdate(){
User user = new User();
//可以通过条件自动拼接动态SQL
user.setId(5L);
user.setName("id:5,修改过后");
//updateById 参数是一个对象!
int i = userMapper.updateById(user);
System.out.println(i);
}
需要注意的是,updateById内传入的不是id而是user对象
自动填充
在通常业务中,数据库中的某些配置需要一些默认值如时间更新,而MyBatisPlus也实现了这个功能。
我们可以在注解@TableField
中设置填充方案:
@TableField(fill = FieldFill.INSERT)
private Data createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Data updateTime;
并自定义类实现MyMetaObjectHandler来处理这个注解:
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start intsert fill ....");
//strictInsertFill(MetaObject metaObject, String fieldName, Class<T> fieldType, E fieldVal)
this.strictInsertFill(metaObject,"createTime", LocalDateTime.class,LocalDateTime.now());// 起始版本 3.3.0(推荐使用)
}
//更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
}
}
这里的两个方法分别对应INSERT和INSERT_UPDATE两个使用。
之后在执行更新操作和插入操作时,就会自动填充了。
增删改查
1.通过id查询
User user = userMapper.selectById(1L);
2.根据id批量查询
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
3.条件查询
HashMap<String,Object> map = new HashMap<>();
//自定义查询
map.put("name","小文");
map.put("age",20);
List<User> users = userMapper.selectByMap(map);
map类的参数(字段名,参数)会被MySQLPlus自动组合成查询条件
分页查询
MybatisPlus内置了分页查询插件其内置几个参数:
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
overflow | boolean | false | 溢出总页数后是否进行处理(默认不处理) |
maxLimit | Long | 单页分页条数限制(默认无限制) | |
dbType | DbType | 数据库类型(根据类型获取应使用的分页方言) | |
dialect | IDialect | 方言实现类 |
需要使用,我们要先配置拦截器组件:
/**
* 注册插件
*/
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
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;
}
之后就可以进行分页查询:
public void testMybatisPlus_Page(){
// 两个参数:current和size
//current是当前页数,默认是1,从1开始,不是0。size是每一页的条数。
Page<User> page = new Page<>(1, 4);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
}
除此之外,Page的方法还有很多:
//page的其他方法
System.out.println("当前页:" + page.getCurrent());
System.out.println("总页数:" + page.getPages());
System.out.println("记录数:" + page.getTotal());
System.out.println("是否有上一页:" + page.hasPrevious());
System.out.println("是否有下一页:" + page.hasNext());
删除操作
删除操作和查询类似:
userMapper.deleteById(4L);
userMapper.deleteBatchIds(Arrays.asList(1L,2L));
Map<String, Object> map = new HashMap<>();
map.put("name","xiaotian");
userMapper.deleteByMap(map);
逻辑删除
物理删除:从数据库中直接删除
逻辑删除:在数据库中没有被删除,而是通过一个变量来让它失效。 deleted=0 ==》deleted=1
首先我们在数据表中增加一个deleted字段,之后在实体类上添加@TableLogic
注解
@TableLogic
Integer deleted;
配置配置项:
#配置日志
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
这里可以通过注解指定逻辑删除字段,也可以在配置项中设置。
逻辑删除只对自动注入的SQL有效:
- 在更新和查找时会追加where忽略逻辑删除的数据
- 删除操作会转变为更新操作,进行逻辑删除
条件构造器
MyBatisPlus可以使用Wrapper,通过其构造复杂的SQL。
但需要注意的是,使用条件构造器会使得代码耦合性高,传输wrapper相当于controller用map接收值,后期维护极为困难。
具体使用看Wrapper的使用。
接口操作
MybatisPlus除了对Mapper封装了方便的模版,也对Service业务进行了模版设计,我们继承对应类:
@Service
public interface UserService extends IService<User> {
//除了继承模版,我们也可以把它当成普通Service添加自己需要的方法
}
内部提供了很多方便的方法,如我们希望批量插入:
List<User> users = List.of(new User("xxx"), new User("yyy"));
//预设方法中已经支持批量保存了,这相比我们直接用for效率高不少
service.saveBatch(users);
比如还有更加方便快捷的保存或更新方法,当数据不存在时插入,存在时则更新:
service.saveOrUpdate(new User("aaa"));