一、Mybatis 简介0. IService和baseMapper的区别
两个接口都可以对数据库增删改查(CRUD),两者的区别?既然IService是对baseMapper的补充,为什么还需要baseMapper? 1. Mybatis-plus基本工程搭建
1.1 步骤总览1.2 步骤展开
1、创建数据库2、创建SpringBoot工程3、引入依赖4、修改配置文件5、创建实体类6、创建 IService / baseMapper 接口7、启动类添加注解8、测试补充、查看sql输出日志 2. baseMapper CRUD 接口
2.1 增C(Insert)2.2 查R(Select)2.3 改U(Update)2.4 删D(Delete)2.5 配置分页拦截器 3. 通用Service(IService)
3.1 save(增)3.2 SaveOrUpdate(增或改)3.3 Remove(删)3.4 Update(改)3.5 Get(按条件查)3.6 List(查)3.7 Page(分页查)3.8 Count(查记录数)3.9 Chain(链式) 4. 自定义Mapper
4.1 步骤4.2 详细步骤
接口方法定义创建xml文件测试 5. 自定义Service
5.1 步骤5.2 详细步骤
接口方法定义实现接口方法测试 6. @Table注释
6.1 @TableName6.2 @TableId
6.2.1 value 属性6.2.2 type属性 6.3 @TableField
6.3.1 自动填充6.3.2 判断字段是否存在6.3.3 判断字段是否赋值 6.4 @TableLogic 7. 插件
7.1 分页插件
7.1.1 为什么需要分页?7.1.2 单表数据拆分有两种方式 7.2 乐观锁插件 8. Wrapper家族
一、Mybatis 简介JAVA三层架构包括持久层、业务层、表现层。Mybatis是持久层框架,简单说就是操作数据库的。
Mybatis-plus是对 Mybatis 的优化。主要学习 Service 和 Mapper 两个接口。
本篇文章学习:
通用Mapper(baseMapper)、通用Service(IService)
通用接口方法无法满足需求时:自定义Mapper、自定义Service
常用注释:@TableName、@TableId、@TableField、@TableLogic
插件:分页插件,乐观锁插件
提供更新和查找方法的Wrapper家族
对比两个接口提供的方法可以看出,IService 是对 baseMapper 的补充,光看数据库增删改查(CRUD)可以发现除了名字不同其他的都类似。baseMapper不支持批量操作,IbaseService支持批量操作。
Service虽然加入了数据库的操作,但还是以业务功能为主,而更加复杂的SQL查询,还是要靠Mapper对应的XML文件里去编写SQL语句。
1. Mybatis-plus基本工程搭建 1.1 步骤总览1、创建数据库
2、创建SpringBoot工程
3、引入依赖(pom文件中配置各个框架版本)
4、修改配置文件(数据库连接配置)
5、创建实体类(实体类是一种属性类,一般就是数据库的表)
6、创建 mapper 接口
7、启动类添加注解(在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹)
8、测试
看完步骤之后就可以尝试用一用了,增删改查的指令每次用来找就好
1、创建数据库 2、创建SpringBoot工程 3、引入依赖springboot配置(中的版本会限制下面中包的版本,这就是为什么中好多包没有人工加版本)
org.springframework.boot spring-boot-starter-parent 2.3.4.RELEASE
其他包和版本设置
4、修改配置文件org.springframework.boot spring-boot-starter com.baomidou mybatis-plus-boot-starter 3.4.1 mysql mysql-connector-java runtime org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine
在 application.properties 配置文件中添加 MySQL 数据库的相关配置:
// mysql数据库连接 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8 spring.datasource.username=root spring.datasource.password=123456
设置与MySQL版本有关:
driver-class-name(驱动)
8以上:com.mysql.cj.jdbc.Driver8以下:com.mysql.jdbc.Driver(没有cj)
url:
8以上:?serverTimezone=GMT%2B8 需要添加东八区时区,jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-88以下:jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=true 5、创建实体类
实体类是一种属性类,一般就是数据库的表
package com.atguigu.mybatisplus.entity;
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
6、创建 IService / baseMapper 接口
package com.atguigu.mybatisplus.mapper; public interface UserMapper extends baseMapper{ }
package com.atguigu.mybatisplus.service; public interface UserService extends IService7、启动类添加注解{ }
在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹
package com.atguigu.mybatisplus;
@SpringBootApplication
@MapperScan("com.atguigu.mybatisplus.mapper")
public class MybatisPlusApplication {
......
}
8、测试
package com.atguigu.mybatisplus;
@SpringBootTest
class MybatisPlusApplicationTests {
//@Autowired //默认按类型装配。是spring的注解
@Resource //默认按名称装配,找不到与名称匹配的bean,则按照类型装配。是J2EE的注解
private UserMapper userMapper;
@Test
void testSelectList() {
//selectList()方法的参数:封装了查询条件
//null:无任何查询条件
List users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
补充、查看sql输出日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl2. baseMapper CRUD 接口 2.1 增C(Insert)
@Test
public void testInsert(){
User user = new User();
user.setName("Helen");
user.setAge(18);
//不设置email属性,则生成的动态sql中不包括email字段
int result = userMapper.insert(user);
System.out.println("影响的行数:" + result); //影响的行数
System.out.println("id:" + user.getId()); //id自动回填
}
2.2 查R(Select)
@Test
public void testSelect(){
//按id查询
User user = userMapper.selectById(1);
System.out.println(user);
//按id列表查询
List users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
//按条件查询
Map map = new HashMap<>();
map.put("name", "Helen"); //注意此处是表中的列名,不是类中的属性名
map.put("age", 18);
List users1 = userMapper.selectByMap(map);
users1.forEach(System.out::println);
}
2.3 改U(Update)
@Test
public void testUpdate(){
User user = new User();
user.setId(1L);
user.setAge(28);
//注意:update时生成的sql自动是动态sql
int result = userMapper.updateById(user);
System.out.println("影响的行数:" + result);
}
2.4 删D(Delete)
@Test
public void testDelete(){
int result = userMapper.deleteById(5);
System.out.println("影响的行数:" + result);
}
2.5 配置分页拦截器
3. 通用Service(IService)
参考文章
3.1 save(增)// 插入一条记录(选择字段,策略插入) boolean save(T entity); // 插入(批量) boolean saveBatch(Collection3.2 SaveOrUpdate(增或改)entityList); // 插入(批量) boolean saveBatch(Collection entityList, int batchSize);
// TableId 注解存在更新记录,否插入一条记录 boolean saveOrUpdate(T entity); // 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法 boolean saveOrUpdate(T entity, Wrapper3.3 Remove(删)updateWrapper); // 批量修改插入 boolean saveOrUpdateBatch(Collection entityList); // 批量修改插入 boolean saveOrUpdateBatch(Collection entityList, int batchSize);
// 根据 entity 条件,删除记录 boolean remove(Wrapper3.4 Update(改)queryWrapper); // 根据 ID 删除 boolean removeById(Serializable id); // 根据 columnMap 条件,删除记录 boolean removeByMap(Map columnMap); // 删除(根据ID 批量删除) boolean removeByIds(Collection extends Serializable> idList);
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset boolean update(Wrapper3.5 Get(按条件查)updateWrapper); // 根据 whereEntity 条件,更新记录 boolean update(T entity, Wrapper updateWrapper); // 根据 ID 选择修改 boolean updateById(T entity); // 根据ID 批量更新 boolean updateBatchById(Collection entityList); // 根据ID 批量更新 boolean updateBatchById(Collection entityList, int batchSize);
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map getMap(Wrapper queryWrapper);
// 根据 Wrapper,查询一条记录
V getObj(Wrapper queryWrapper, Function super Object, V> mapper);
3.6 List(查)
// 查询所有 List3.7 Page(分页查)list(); // 查询列表 List list(Wrapper queryWrapper); // 查询(根据ID 批量查询) Collection listByIds(Collection extends Serializable> idList); // 查询(根据 columnMap 条件) Collection listByMap(Map columnMap); // 查询所有列表 List
// 无条件分页查询 IPage3.8 Count(查记录数)page(IPage page); // 条件分页查询 IPage page(IPage page, Wrapper queryWrapper); // 无条件分页查询 IPage > pageMaps(IPage page); // 条件分页查询 IPage > pageMaps(IPage page, Wrapper queryWrapper);
// 查询总记录数 int count(); // 根据 Wrapper 条件,查询总记录数 int count(Wrapper3.9 Chain(链式)queryWrapper);
query(链式查询)
// 链式查询 普通 QueryChainWrapperquery(); // 链式查询 lambda 式。注意:不支持 Kotlin LambdaQueryChainWrapper lambdaQuery(); // 示例: query().eq("column", value).one(); lambdaQuery().eq(Entity::getId, value).list();
update(链式更改)
// 链式更改 普通 UpdateChainWrapper4. 自定义Mapper 4.1 步骤update(); // 链式更改 lambda 式。注意:不支持 Kotlin LambdaUpdateChainWrapper lambdaUpdate(); // 示例: update().eq("column", value).remove(); lambdaUpdate().eq(Entity::getId, value).update(entity);
1.定义接口
2.配置xml文件,并在xml文件中配置SQL语句
其他的都和通用Mapper一样
List创建xml文件selectAllByName(String name);
在resources目录中创建mapper目录,创建UserMapper.xml。mapper目录是持久层映射文件的默认目录,如果是其他目录,需要配置mapper-locations,例如:mybatis-plus.mapper-locations=classpath:xml/*.xml
测试id, name, age, email
在MapperTests中创建如下测试用例
@Test
public void testSelectAllByName(){
List users = userMapper.selectAllByName("Helen");
users.forEach(System.out::println);
}
5. 自定义Service
5.1 步骤
1.定义接口
2.实现接口方法
其他的都和通用Service一样
List实现接口方法listAllByName(String name);
@Override public List测试listAllByName(String name) { // baseMapper对象指向当前业务的mapper对象 return baseMapper.selectAllByName("Helen"); }
ServiceTests中添加测试方法
@Test
public void testListAllByName(){
List users = userService.listAllByName("Helen");
users.forEach(System.out::println);
}
6. @Table注释
6.1 @TableName
指定连接的表名 t_user
@TableName(value = "t_user")
public class User {
}
6.2 @TableId
默认情况下数据库的 id 列使用的是基于雪花算法的策略生成.
为什么需要使用雪花算法?
所以的数据基本都是根据 id 来查询的,数据量很大的时候不会存在同一个表中。mysql数据库是根据聚簇索引进行查找的,而这种查找方法在 id 有顺序的时候查找速度更快。所以 id 的设置需要满足有序、方便扩展到其他分表、各个分表使用频率差不多(均衡)、id 不能重复。
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
长度共 64 bit(一个long型)。首先是一个符号位,正数是 0,负数是 1。41bit 时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于 69.73年。10bit 作为机器的 ID,5个 bit 是数据中心(是哪个地区的数据中心),5个 bit 的机器ID(每个数据中心中的机器)。12bit 作为毫秒内的流水号。 6.2.1 value 属性
表中的 id 只有设置为 id 的时候mybatis才会知道这个数据是id而采用雪花算法,如果数据库中 id 没有命名为 id 就需要 value 属性。
比如 id 在数据库中叫 uid。
@TableId(value = "uid") private String id;6.2.2 type属性
IdType.ASSIGN_ID:使用基于雪花算法的策略生成数据id
IdType.AUTO:使用数据库的自增策略
@TableId(type = IdType.ASSIGN_ID) private Long id;
全局配置:要想影响所有实体的配置,可以设置全局主键配置
// 全局设置主键生成策略 mybatis-plus.global-config.db-config.id-type = auto6.3 @TableField 6.3.1 自动填充
照阿里的编写要求,每个数据库都要有 create_time 和 update_time ,也就是表创建的时间和表更新的时间。
在这种很多表都需要添加的时候才用自动填充,只改几个表的时候直接在实体类中标注就可以。
step1:添加fill属性
@TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;
step2:实现元对象处理器接口 -> 创建handler包,创建MymetaObjectHandler类
添加 @Component 注解
package com.atguigu.mybatisplus.handler;
@Slf4j
@Component
public class MymetaObjectHandler implements metaObjectHandler {
@Override
public void insertFill(metaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(metaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
6.3.2 判断字段是否存在
@Override
public void insertFill(metaObject metaObject) {
//其他代码
//判断是否具备author属性
boolean hasAuthor = metaObject.hasSetter("author");
if(hasAuthor){
log.info("start insert fill author....");
this.strictInsertFill(metaObject, "author", String.class, "Helen");
}
}
6.3.3 判断字段是否赋值
@TableField(fill = FieldFill.INSERT) private Integer age;
@Override
public void insertFill(metaObject metaObject) {
//其他代码
//判断age是否赋值
Object age = this.getFieldValByName("age", metaObject);
if(age == null){
log.info("start insert fill age....");
this.strictInsertFill(metaObject, "age", String.class, "18");
}
}
6.4 @TableLogic
逻辑删除(和物理删除不同,类似于放入回收站,是可以找回的)
step1:数据库中创建逻辑删除状态列
数据库中添加 is_deleted 列(是否删除,根据阿里编程准则,是否用is_命名),0是未逻辑删除,1是逻辑删除。
step2:实体类中添加逻辑删除属性
@TableLogic @TableField(value = "is_deleted") private Integer deleted;
step3:测试
测试删除:删除功能被转变为更新功能
-- 实际执行的SQL update user set is_deleted=1 where id = 1 and is_deleted=0
测试查询:被逻辑删除的数据默认不会被查询
-- 实际执行的SQL select id,name,is_deleted from user where is_deleted=07. 插件 7.1 分页插件 7.1.1 为什么需要分页?
同一业务的单表数据也会达到单台数据库服务器的处理瓶颈
7.1.2 单表数据拆分有两种方式垂直分表和水平分表



