文章目录
- 一、环境搭键
- 二、基本CRUD
- 2.1 BaseMapper
- 2.2 插入
- 2.3 删除
- 2.4 修改
- 2.5 查询
- 三、通用Service
- 四、常用注解
- 4.1 雪花算法
- 4.2 注解@TableLogic
- 五、条件构造器和常用接口
- 5.1 Wrapper介绍
- 5.2 QueryWrapper
- 5.3 UpdateWrapper
- 5.4 condition
- 5.5 LambdaQueryWrapper
- 5.6 LambdaUpdateWrapper
一、环境搭键
这些配置可以当做模版,快速搭键MybatisPlus
环境。
1、pom.xml:
<properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><lombok>1.18.20</lombok><druid>1.2.1</druid><pagehelper>1.4.1</pagehelper><mybatisPlus>3.5.1</mybatisPlus></properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid}</version></dependency><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>${pagehelper}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatisPlus}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency></dependencies>
2、启动类:
@SpringBootApplication
@MapperScan("com.cabbage.mybatisplus.mapper")
public class MybatisplusStudyApplication {public static void main(String[] args) {SpringApplication.run(MybatisplusStudyApplication.class, args);}
}
3、创建实体类和mapper接口:
public interface UserMapper extends BaseMapper<User> {
}
4、配置数据库:
server:port: 8080
spring:profiles:active: devdatasource:druid:driver-class-name: ${sht.datasource.driver-class-name}url: jdbc:mysql://${sht.datasource.host}:${sht.datasource.port}/${sht.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: ${sht.datasource.username}password: ${sht.datasource.password}
# 配置MyBatis日志
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
sht:datasource:driver-class-name: com.mysql.cj.jdbc.Driverhost: localhostport: 3306database: ssmusername: rootpassword: root
二、基本CRUD
2.1 BaseMapper
MyBatis-Plus中的基本CRUD在内置的BaseMapper中都已得到了实现,我们可以直接使用,接口如下(具体内容可查看源码):
public interface BaseMapper<T> extends Mapper<T> {/*** 插入一条记录* @param entity 实体对象*/int insert(T entity);/*** 根据 ID 删除* @param id 主键ID*/int deleteById(Serializable id);/*** 根据实体(ID)删除* @param entity 实体对象* @since 3.4.4*/int deleteById(T entity);/*** 根据 columnMap 条件,删除记录* @param columnMap 表字段 map 对象*/int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);/*** 根据 entity 条件,删除记录* @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)*/int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
2.2 插入
@Testvoid t2() {User user = new User(null, "one", 18, "one@qq.com");int res = userMapper.insert(user);log.info("影响的行数:{},user.id:{}", res, user.getId());//会默认基于雪花算法的策略生成id}
2.3 删除
@Testvoid t3() {int res1 = userMapper.deleteById(1720674114752352257L);log.info("受影响的行数:{}",res1);List<User> list = Arrays.asList(new User(1L, null, null, null), new User(2L, null, null, null));int res2 = userMapper.deleteBatchIds(list);log.info("受影响的行数:{}",res2);}
@Testvoid t4() {Map<String, Object> map = new HashMap<>();map.put("age", 28);map.put("name", "Tom");int res = userMapper.deleteByMap(map);log.info("受影响的行数:{}",res);}
2.4 修改
@Testvoid t5() {int res = userMapper.updateById(new User(4L, "cabbage", 21, "cabbage@qq.com"));log.info("受影响的行数:{}",res);}
2.5 查询
@Testvoid t6() {User user = userMapper.selectById(6L);log.info("user:{}", user);List<Long> list = Arrays.asList(5L, 6L);List<User> userList = userMapper.selectBatchIds(list);log.info("userList:{}",userList);}
@Testvoid t7() {Map<String, Object> map = new HashMap<>();map.put("name", "one");map.put("age", 18);List<User> userList = userMapper.selectByMap(map);log.info("userList:{}", userList);}
三、通用Service
通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get查询单行
、remove删除
、list查询集合
、page分页
、save插入
前缀命名方式区分 Mapper 层避免混淆。
public interface UserService extends IService<User> {
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
测试查询记录数:
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@GetMappingpublic String test() {long count = userService.count();return String.valueOf(count);}
}
测试批量插入:
@GetMappingpublic String test() {List<User> list = Arrays.asList(new User(7L, "two", 22, "two@qq.com"),new User(8L, "three", 25, "three@qq.com"));boolean res = userService.saveBatch(list);return String.valueOf(res);}
四、常用注解
可以看之前写过的文章,介绍的比较详细:https://cabbage.blog.csdn.net/article/details/123300666
4.1 雪花算法
背景:
需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。数据库的扩展方式主要包括:业务分库、主从复制,数据库分表。
数据库分表:
将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。单表数据拆分有两种方式:
垂直分表
和水平分表
。示意图如下:
- 垂直分表
垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。例如,前面示意图中的 nickname 和 description字段,假设我们是一个婚恋网站,用户在筛选其他用户的时候,主要是用 age 和 sex 两个字段进行查询,而 nickname 和description 两个字段主要用于展 示,一般不会在业务查询中用到。description本身又比较长,因此我们可以将这两个字段独立到另外一张表中,这样在查询 age 和 sex 时,就能带来一定的性能提升。
- 水平分表
水平分表适合表行数特别大的表,有的公司要求单表行数超过 5000万就必须进行分表,这个数字可以作为参考,但并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过1000万就要分表了;而对于一些简单的表,即使存储数据超过1亿行,也可以不分表。水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理。
方式一:主键自增
- 以最常见的用户 ID 为例,可以按照1000000的范围大小进行分段,1 ~ 999999 放到表 1中,1000000 ~ 1999999 放到表2中,以此类推。
- 复杂点:分段大小的选取。分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会导致单表依然存在性能问题,一般建议分段大小在 100 万至 2000 万之间,具体需要根据业务选取合适的分段大小。
- 优点:可以随着数据的增加平滑地扩充新的表。例如,现在的用户是 100 万,如果增加到1000万,只需要增加新的表就可以了,原有的数据不需要动。
- 缺点:分布不均匀。假如按照1000万来进行分表,有可能某个分段实际存储的数据量只有 1 条,而另外一个分段实际存储的数据量有1000万条。
方式二:取模
- 同样以用户 ID 为例,假如我们一开始就规划了 10 个数据库表,可以简单地用 user_id % 10 的值来表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 5 的子表中,ID 为 10086 的用户放到编号为 6 的子表中。
- 复杂点:初始表数量的确定。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题。
- 优点:表分布比较均匀。
- 缺点:扩充新的表很麻烦,所有数据都要重分布。
方式三:雪花算法
- 雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
- 核心思想:
长度共64bit(一个long型)。
首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截)。
10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。 - 优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。
4.2 注解@TableLogic
再来看看逻辑删除的具体使用,数据库中is_delete默认值都是0,代表着未删除。
我们给数据库对应的实体类添加@TableLogic注解:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User implements Serializable {@TableId(type = IdType.AUTO)private Long id;private String name;private Integer age;private String email;@TableLogic@TableField(value = "is_delete")private Integer isDelete;
}
执行删除代码:
@Testvoid t1() {List<Long> list = Arrays.asList(4L, 5L);int res = userMapper.deleteBatchIds(list);log.info("共删除了{}条数据", res);}
通过控制台可以发现,执行删除语句,实际上是执行的修改操作。
数据库并未删除任何数据,只是把is_delete的值改为了1。
五、条件构造器和常用接口
5.1 Wrapper介绍
- Wrapper :条件构造抽象类,最顶端父类
- AbstractWrapper :用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper :查询条件封装
UpdateWrapper :Update 条件封装 - AbstractLambdaWrapper :使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper :Lambda 更新封装Wrapper
5.2 QueryWrapper
组装查询条件:
@Testvoid t2() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.like("name", "t").between("age", 20, 30).isNotNull("email");List<User> userList = userMapper.selectList(queryWrapper);userList.forEach(user -> {log.info("查询到的结果是:{}", user);});}
从控制台可以看到,框架自动的为我们查询逻辑上存在的数据。
组装排序条件:
@Testvoid t3() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.orderByDesc("age").orderByAsc("id");List<User> userList = userMapper.selectList(queryWrapper);userList.forEach(user -> {log.info("查询到的结果是:{}", user);});}
组装删除条件:
@Testvoid t4() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.isNull("email");int res = userMapper.delete(queryWrapper);log.info("共删除了{}条数据", res);}
条件的优先级:
@Testvoid t4() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();//查询年龄大于20,并且名字中含有'o',或者邮箱是空的用户queryWrapper.gt("age", 20).like("name", "o").or().isNull("email");List<User> userList = userMapper.selectList(queryWrapper);userList.forEach(user -> {log.info("查询都的用户是:{}", user);});}
@Testvoid t4() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();//查询名字中含有'o',并且年龄大于20或者邮箱是空的用户queryWrapper.like("name", 'o').and(i -> {i.gt("age", 20).or().isNull("email");});List<User> userList = userMapper.selectList(queryWrapper);userList.forEach(user -> {log.info("查询都的用户是:{}", user);});}
组装select子句:
@Testvoid t5() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.select("name", "age", "email").orderByAsc("age");List<Map<String, Object>> mapList = userMapper.selectMaps(queryWrapper);mapList.forEach(System.out::println);}
@Testvoid t5() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.select("name", "age", "email").orderByAsc("age");List<User> userList = userMapper.selectList(queryWrapper);
// List<Map<String, Object>> mapList = userMapper.selectMaps(queryWrapper);userList.forEach(System.out::println);}
实现子查询:
@Testvoid t6() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.inSql("id", "select id from user where id > 6");List<User> userList = userMapper.selectList(queryWrapper);userList.forEach(System.out::println);}
5.3 UpdateWrapper
@Testvoid t7() {//将(年龄大于25或邮箱为null)并且用户名中包含有t的用户信息修改UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();updateWrapper.like("name", "t").and(i -> {i.gt("age", 25).or().isNull("email");});int res = userMapper.update(new User(9L, "five", 30, "five@qq.com"), updateWrapper);log.info("影响的结果是:{}", res);}
5.4 condition
在真正开发的过程中,组装条件是常见的功能,而这些条件数据来源于用户输入,是可选的,因
此我们在组装这些条件时,必须先判断用户是否选择了这些条件,若选择则需要组装该条件,若
没有选择则一定不能组装,以免影响SQL执行的结果
思路一:
@Testvoid t8() {//定义查询条件,有可能为null(用户未输入或未选择)String username = null;Integer ageBegin = 10;Integer ageEnd = 24;QueryWrapper<User> queryWrapper = new QueryWrapper<>();//StringUtils.isNotBlank()判断某字符串是否不为空且长度不为0且不由空白符(whitespace)构成if(StringUtils.isNotBlank(username)){queryWrapper.like("name","o");}if(ageBegin != null){queryWrapper.ge("age", ageBegin);}if(ageEnd != null){queryWrapper.le("age", ageEnd);}List<User> users = userMapper.selectList(queryWrapper);users.forEach(System.out::println);}
思路二:
@Testvoid t9() {String name = null;Integer ageBegin = 10;Integer ageEnd = 24;QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.like(StringUtils.isNotBlank(name), "name", "o").gt(ageBegin != null, "age", ageBegin).lt(ageEnd != null, "age", ageEnd);List<User> userList = userMapper.selectList(queryWrapper);userList.forEach(System.out::println);}
思路二跟思路一的执行效果一样。
5.5 LambdaQueryWrapper
@Testvoid t9() {String name = "o";Integer ageBegin = 10;Integer ageEnd = 30;LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.like(StringUtils.isNotBlank(name), User::getName, "o").gt(ageBegin != null, User::getAge, ageBegin).lt(ageEnd != null, User::getAge, ageEnd);List<User> userList = userMapper.selectList(queryWrapper);userList.forEach(System.out::println);}
5.6 LambdaUpdateWrapper
@Testvoid t10() {LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.set(User::getAge, 18).set(User::getEmail, "user@atguigu.com").like(User::getName, "f").and(i -> i.gt(User::getAge, 24).or().isNull(User::getEmail));int result = userMapper.update(null, updateWrapper);System.out.println("受影响的行数:" + result);}