MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,
更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
创建SpringBoot项目
com.baomidou
mybatis-plus-boot-starter
3.5.2
org.projectlombok
lombok
mysql
mysql-connector-java
spring.datasource.type=com.zaxxer.hikari.HikariDataSource # 使用默认的连接池
server.port=7777
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///jdbcDemo
spring.datasource.username=root
spring.datasource.password=123456
@Data
public class Person {
private Long id;
private String name;
private int age;
}
mapper继承自BaseMapper
public interface PersonMapper extends BaseMapper {}
@SpringBootApplication
@MapperScan(basePackages = "com.whitecamellia.mapper")
public class MybatisplusdemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisplusdemoApplication.class, args);
}
}
单元测试
@SpringBootTest
class MybatisplusdemoApplicationTests {
@Autowired
private PersonMapper personMapper;
@Test
public void test1() {
List list = personMapper.selectList(null);
list.forEach(System.out::println);
}
}
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
如果配置为org.apache.ibatis.logging.stdout.StdOutImpl就只会在控制台窗口打印,不会记录到日志文件。如果需要保存打印的SQL到文件就不能设置为StdOutImpl,可以设置为Slf4jImpl,也可以不设置。然后对应接口所在包设置logback对应包的日志等级
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.slf4j.Slf4jImpl
logging.level.root = warn
logging.level.com.whitecamellia.mybatis_plusdemo =debug
logging.file.name=log.log
Person(id=1, name=zs, age=23)
Person(id=2, name=ls, age=24)
@Test
public void testInsert() {
//新增用户信息
//INSERT INTO person ( id, name, age ) VALUES ( ?, ?, ? )
Person person = new Person();
person.setId(null);
person.setAge(25);
person.setName("王五");
int row = personMapper.insert(person);
System.out.println(row);//1
//添加过程中读取 id值
System.out.println(person.getId());//1500016221535105026 雪花算法
}
@Test
public void testDelete() {
//DELETE FROM person WHERE id=?
//int i = personMapper.deleteById(1500017501649301505L);
//Person person = new Person();
//person.setId(2L);
//personMapper.deleteById(person);
//DELETE FROM person WHERE name = ? AND age = ?
//HashMap map = new HashMap<>();
//map.put("name", "王五");
//map.put("age", "25");
//int i = personMapper.deleteByMap(map);
//DELETE FROM person WHERE id IN ( ? , ? )
ArrayList list = new ArrayList<>();
list.add(1L);
list.add(2L);
int i = personMapper.deleteBatchIds(list);
}
@Test
public void testUpdate() {
//UPDATE person SET name=?, age=? WHERE id=?
Person person = new Person();
person.setId(1L);
person.setName("zss");
person.setAge(233);
int row = personMapper.updateById(person);
}
/**
* 查询数据
*/
@Test
public void testQuery() {
//SELECT id,name,age FROM person WHERE id=?
//Person person1 = personMapper.selectById(1L);
// SELECT id,name,age FROM person WHERE id IN ( ? , ? , ? )
//List list = Arrays.asList(1, 2, 3);
//List list1 = personMapper.selectBatchIds(list);
// HashMap map = new HashMap<>();
// map.put("name", "zs");
// map.put("age", "23");
//SELECT id,name,age FROM person WHERE name = ? AND age = ?
// List list = personMapper.selectByMap(map);
// QueryWrapper queryWrapper = new QueryWrapper<>();
// queryWrapper.select("id","name");
// List list = personMapper.selectList(queryWrapper);
}
如果你觉得BaseMapper提供的方法都不适用,也可以自己封装方法,和之前mybatis是一样的。
mybatisPlus对mybatis只是增强,不做改变。
mapper映射文件配置
mybatis-plus.mapper-locations=classpath:/mapper/*Mapper.xml
public interface PersonService extends IService {
}
@Service
public class PersonServiceImpl extends ServiceImpl implements PersonService {
}
@Autowired
private PersonService personService;
@Test
void testPersonService () {
long count = personService.count();
log.debug("{}",count);
}
批量添加
@Test
void saveBatch () {
//Service中批量添加操作
//INSERT INTO person ( id, name, age ) VALUES ( ?, ?, ? )
ArrayList list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Person person = new Person();
person.setName("zs" + i);
person.setAge(i + 1);
list.add(person);
}
personService.saveBatch(list);
}
}
解决表名和实体映射问题
@TableName("t_person")
public class Person {
private Long id;
private String name;
private int age;
}
如果数据库中每张表都加前缀 t_
# mybatisPlus的全局配置 设置实体类和表的统一前缀
mybatis-plus.global-config.db-config.table-prefix=t_
表明类中的某个属性为主键字段对应的属性
在Mybatis中需要使用 useGeneratedKeys,keyProperty,keyColumn 设置自增主键值的回返,在实体类对象中获取即可。MybatisPlus中在进行数据新增时,在新增成功后,会自动的将自增的主键值返回到实体类对象中,前提是需要在实体类中使用@TableId表明主键字段,并且为自增类型。
@TableId
private Long id;
value
如果实体类名和列名不一致,可以使用value属性
@TableId(value = "u_id")
private Long uid;
type
雪花算法主键增长:type = IdType.ASSIGN_ID,雪花算法自动增长,与列是否是自动增长没关系
默认主键增长:type = IdType.AUTO,要把列设置为自动增长,否则无效
@TableId(value = "u_id", type = IdType.AUTO)
private Long uid;
如果所有表都需要默认主键自增,可以全局配置
# 默认主键自增
mybatis-plus.global-config.db-config.id-type=auto
解决数据库列名和实体类列名不一致问题
@TableField("user_name")
private String name;
注意:mybatisPlus中默认支持驼峰映射
内置 SQL 默认指定排序,优先级低于 wrapper 条件查询
@OrderBy(asc = true)
private int age;
描述:表字段逻辑处理注解(逻辑删除)
@TableLogic(value = "0",delval = "1")
private Integer isDelete;
全局配置如下(不建议)
mybatis-plus.global-config.db-config.logic-delete-field=isDelete
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
雪花算法(Snowflake)是一种生成分布式全局唯一ID的算法,生成的ID称为Snowflake[ˈsnoʊfleɪk] IDs或snowflakes。这种算法由Twitter创建,并用于推文的ID
需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量
数据库的扩展方式:业务分库、主从复制、数据库分表
主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库。在赋值过程中,一个服务器充当主服务器,而另外一台服务器充当从服务器。
优点:
做数据的热备:
(主数据库宕机,从数据库可继续工作,避免数据的丢失),更好的实现了负载均衡。
降低磁盘I/O访问的频率:
业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率。
读写分离,使数据库能支持更大的并发:
其中一个是 Master 主库,负责写入数据,称为:写库;其它都是 Slave 从库,负责读取数据,称为:读库
当主库进行更新时,会自动将数据复制到从库中,而在客户端读取数据时,会从从库中进行读取
减少锁表的影响,实现更高的并发访问
数据备份:
将主库上的数据,复制到从库,热备份机制,即在主库正常运行的情况下进行备份,不会影响到服务
高可用:
数据备份实际上是一种冗余的机制,通过这种冗余的方式可以换取数据库的高可用性,
当服务器出现故障 / 宕机,可以切换到从服务器上,保证服务的正常运行
一张表中放所有数据肯定不现实,所以需要分多张表,
分表可以按照垂直分表和水平分表
垂直分表
概念:
是指表的记录并不多,但是字段却很长,表占用空间很大,检索表的时候需要执行大量的IO,严重降低了性能。这时需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。
比如筛选人信息时候,比较重要的两列是sex和age,我们将不太重要的两列独立放到另一张表中,而且像nicename这种昵称的列和描述
列本身又很长,这样对于比较重要的age,sex列来说,查询时会带来一定性能的提升,
优点:
充分提高了”热点“数据的操作效率
缺点:
表的数量增多,对于表的维护复杂度也随之增加
水平分表
概念:
水平分表就是指以行为单位对数据进行拆分,一般意义上的分表指的就是水平分表。
水平分表适合 表行数特别大的表,有些时候一般我们表行数超过比如3000万就必须分表,但是对于复杂的表可能1000万就需要分表了。
但是不管怎样,当表的数量达到千万级别时,身为架构师的我们就需要考虑架构的性能瓶颈或者隐患。
假设现在有30万行数据,需要对它们进行水平分表:那如何拆分呢?
解决方案
1.主键自增(范围法)
范围法很好理解,可以让第1-100000行数据存放在表1,第100001-200000行数据存放在表2,第200001-300000行数据存放在表3,
以此类推,就是分段,就完成了水平分表。
但是这样做有些复杂,分段范围太小,表会增多,维护比较麻烦,分段范围太大可能依然会导致单表存在性能问题。
分布也不均匀,也许会导致某个分段中只有一条数据,而另一个表中数据达到了100万条
2 取模(hash法)
选择一个合适的hash函数,让id值 和 10取余数,来判断这个到底存到哪张表,比如id是985 就放到5的子表中,id为10086 放到编号为6的子表中,
但是也有问题:复杂,表太多维护麻烦,表数量增多,导致单表性能也存在问题,
好处是:表分布会比较均匀,
问题
水平分表会比垂直分表 复杂很多,比如要求全局唯一的id值,该如何处理?
雪花算法是由Twitter公布的分布式主键生成算法,能保证不同表的主键不重复,
以及相同表的主键有序性(后添加的主键数据会比之前的数大)。
核心思想
长度是一个Long类型,8个字节,64位
第一个bit位是标识部分,在java中由于long的最高位是符号位,正数是0,负数是1,一般生成的ID为正数,所以固定为0。
时间戳部分占41bit,这个是毫秒级的时间,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间),这样可以使产生的ID从更小值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
工作机器id占10bit,比较灵活,比如,可以使用前5位作为数据中心机房标识,后5位作为单机房机器标识,可以部署1024个节点。
序列号部分占12bit,支持同一毫秒内同一个节点可以生成4095个ID
优点: 整体上按照时间自增排序,并且整个分布式系统不会产生ID重复,并且效率较高
缺点:
条件构造器
Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : 查询条件封装
UpdateWrapper : Update 条件封装
AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper : Lambda 更新封装Wrapper
需求:查询用户名包含z的用户,并且年龄是在23-27之间的,并且名字不为null的
//查询用户名包含z的用户,并且年龄是在23-27之间的,并且名字不为null的
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.like("user_name", 'z').
between("age", 24, 27).
isNotNull("user_name");
List list = personMapper.selectList(wrapper);
System.out.println(list);
//按照年龄降序,如果年龄相同按照id升序排列
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.orderByDesc("age").orderByAsc("id");
List list = personMapper.selectList(wrapper);
System.out.println(list);
//删除名字 为null这一列的数据
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.isNull("user_name");
int delete = personMapper.delete(wrapper);
System.out.println(delete);
//更新年龄大于24岁的并且名字包含w的,或者id等于6的
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.gt("age", 24).like("user_name", "w")
.or().eq("u_id", 6);
//把满足条件的数据 更新成person对象信息
Person person = new Person();
person.setAge(100);
person.setUserName("码上未来");
int update = personMapper.update(person, wrapper);
//查询指定列,user_name,age
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.select("user_name", "age");
List
//查询id小于等于5的 用户信息
//SELECT * FROM t_person WHERE u_id IN (SELECT u_id FROM t_person WHERE u_id < 5)
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.inSql("u_id","SELECT u_id FROM t_person WHERE u_id < 5");
List list = personMapper.selectList(wrapper);
System.out.println(list);
//更新年龄大于24岁的并且名字包含w的,或者id等于6的
UpdateWrapper wrapper = new UpdateWrapper<>();
wrapper.gt("age", 24).like("user_name", "w")
.or().eq("u_id", 6);
wrapper.set("age", 101).set("user_name", "whitecamellia");
//不需要person对象了
int update = personMapper.update(null, wrapper);
System.out.println(update);
配置
@Configuration
@MapperScan(basePackages = "com.whitecamellia.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor pii = new PaginationInnerInterceptor(DbType.MYSQL);
mybatisPlusInterceptor.addInnerInterceptor(pii);
return mybatisPlusInterceptor;
}
}
测试
@Test
public void test1() {
//SELECT u_id,user_name,age FROM t_person LIMIT ?,?
Page page = new Page<>(2, 3);//第二页三条数据
personMapper.selectPage(page, null);
System.out.println(page);//page对象
System.out.println(page.getRecords());//返回指定数据的集合
System.out.println(page.getCurrent());//获取当前页码
System.out.println(page.getSize());//每页显示数量
System.out.println(page.getTotal());//总条数
System.out.println(page.hasPrevious());//是否有上一页
System.out.println(page.hasNext());//是否有下一页
}
根据年龄查询用户信息,并分页
Page selectPageAsPage(@Param("page") Page page, @Param("age") Integer age);
# 自定义别名
mybatis-plus.type-aliases-package=com.whitecamellia.entity
Page page = new Page<>(1, 2);
personMapper.selectPageAsPage(page, 24);
System.out.println(page.getRecords());
mysql
mysql-connector-java
8.0.25
org.projectlombok
lombok
com.baomidou
mybatis-plus-boot-starter
3.0.5
com.baomidou
mybatis-plus-generator
3.0.5
org.apache.velocity
velocity-engine-core
2.2
# 端口号
server:
port: 8088
#应用名称
spring:
application:
name: Spring_mybatis-plus
# 配置数据源
datasource:
username: root
password: 123456
url: jdbc:mysql:///db04
driver-class-name: com.mysql.cj.jdbc.Driver
# 配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置逻辑删除
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
CodeGenerator
package com.whitecamellia.demo_test;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
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.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Scanner;
/**
* @author Petrel
*/
public class CodeGenerator {
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir("/Users/123456/IdeaProjects/project/quickStart/src/main/java1/src");//设置代码生成路径
gc.setFileOverride(true);//是否覆盖以前文件
gc.setOpen(false);//是否打开生成目录
gc.setAuthor("Petrel");//设置项目作者名称
gc.setIdType(IdType.AUTO);//设置主键策略
gc.setBaseResultMap(true);//生成基本ResultMap
gc.setBaseColumnList(true);//生成基本ColumnList
gc.setServiceName("%sService");//去掉服务默认前缀
gc.setDateType(DateType.ONLY_DATE);//设置时间类型
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/db04");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword(123456);
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.whitecamellia");
pc.setMapper("mapper");
pc.setXml("mapper.xml");
pc.setEntity("entity");
pc.setService("service");
pc.setServiceImpl("service.impl");
pc.setController("controller");
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig sc = new StrategyConfig();
//数据库表映射到实体的命名策略:默命名:NamingStrategy.underline_to_camel认下划线转驼峰
sc.setNaming(NamingStrategy.underline_to_camel);
//数据库表字段映射到实体的命名策略:默认下划线转驼峰命名:NamingStrategy.underline_to_camel
sc.setColumnNaming(NamingStrategy.underline_to_camel);
sc.setEntityLombokModel(true);//自动lombok
sc.setRestControllerStyle(true);
sc.setControllerMappingHyphenStyle(true);
sc.setLogicDeleteFieldName("deleted");//设置逻辑删除
//设置自动填充配置
TableFill gmt_create = new TableFill("create_time", FieldFill.INSERT);
TableFill gmt_modified = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList tableFills=new ArrayList<>();
tableFills.add(gmt_create);
tableFills.add(gmt_modified);
sc.setTableFillList(tableFills);
//乐观锁
sc.setVersionFieldName("version");
sc.setRestControllerStyle(true);//驼峰命名
// sc.setTablePrefix("tbl_"); 设置表名前缀
sc.setInclude(scanner("表名,多个英文逗号分割").split(","));
mpg.setStrategy(sc);
// 生成代码
mpg.execute();
}
}