现在直接使用SpringBoot来构建项目,官网的快速开始也是直接用的SpringBoot
步骤一:创建数据库和表
CREATE TABLE user (
id bigint(20) primary key auto_increment,
name varchar(32) not null,
password varchar(32) not null,
age int(3) not null ,
tel varchar(32) not null
);
insert into user values(1,'Tom','tom',3,'18866668888');
insert into user values(2,'Jerry','jerry',4,'16688886666');
insert into user values(3,'Jock','123456',41,'18812345678');
insert into user values(4,'略略略','nigger',15,'4006184000');
步骤二:创建SpringBoot工程
只需要勾选MySQL,不用勾选MyBatis了
步骤三:补全依赖
导入德鲁伊和MyBatisPlus的坐标
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
步骤四:编写数据库连接四要素
还是将application的后缀名改为yml,以后配置都是用yml来配置
注意要设置一下时区,不然可能会报错(指高版本的mysql)
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
username: root
password: YOUSONOFABTICH.
## mybatis的日志信息l
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
步骤五:根据数据表来创建对应的模型类
注意id是Long类型,至于为什么是Long,接着往下看
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", age=" + age +
", tel='" + tel + '\'' +
'}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
}
步骤六:创建dao接口
@Mapper
public interface UserDao extends BaseMapper<User>{
}
这样写就完事儿了,
只需要在类上方加一个@Mapper注解
,同时继承BaseMapper<>
,泛型写创建的模型类的类型
然后这样就能完成单表的CRUD了
步骤七:测试
懒死,以后连简单的CRUD都不用写了
SpringBoot的测试类也是简单的一批,只需要一个@SpringBootTest
注解就能完成(创建SpringBoot工程的时候已经帮我们自动弄好了)
测试类里需要什么东西就用@Autowired
自动装配,测试方法上用@Test注解
@SpringBootTest
class MybatisplusApplicationTests {
@Autowired
private UserDao userDao;
@Test
void contextLoads() {
List<User> users = userDao.selectList(null);
for (User b : users) {
System.out.println(b);
}
}
}
selectList() 方法的参数为 MP 内置的条件封装器 Wrapper,所以不填写就是无任何条件
MyBatisPlus的官网
因为域名被抢注了,但是粉丝也捐赠了一个 https://mybatis.plus
域名
MP旨在成为MyBatis的最好搭档,而不是替换掉MyBatis,从名称上来看也是这个意思,一个MyBatis的plus版本,在原有的MyBatis上做增强,其底层仍然是MyBatis的东西,所以我们当然也可以在MP中写MyBatis的内容
对于MP的深入学习,可以多看看官方文档,锻炼自己自学的能力,毕竟不是所有知识都有像这样的网课,更多的还是自己看文档,挖源码。
MP的特性:
SpringBoot集成MyBatisPlus非常的简单,只需要导入MyBatisPlus的坐标,然后令dao类继承BaseMapper
,写上泛型,类上方加@Mapper
注解
可能存在的疑问:
@TableName注解
tb_use
r但数据类叫User
,那么就在User类上加@TableName("tb_user")注解
功能 | 自定义接口 | MP接口 |
---|---|---|
新增 | boolean save(T t) | int insert(T t) |
删除 | boolean delete(int id) | int deleteById(Serializable id) |
修改 | boolean update(T t) | int updateById(T t) |
根据id查询 | T getById(int id) | T selectById(Serializable id) |
查询全部 | List getAll() | List selectList() |
分页查询 | PageInfo getAll(int page,int size) | IPage selectPage(IPage page) |
按条件查询 | List getAll(Condition condition) | IPage selectPage(WrapperqueryWrapper) |
int insert(T t)
参数类型是泛型,也就是我们当初继承BaseMapper
的时候,填的泛型,返回值是int类型,0代表添加失败,1代表添加成功
@Test
void testInsert(){
User user = new User();
user.setName("magua");
user.setAge(23);
user.setTel("4005129421");
user.setPassword("MUSICIAN");
userDao.insert(user);
}
随便写一个User的数据,运行程序,然后去数据库看看新增是否成功
1572364408896622593 magua MUSICIAN 23 4005129421
这个主键自增id看着有点奇怪,但现在你知道为什么要将id设为long类型了吧
int deleteByIds(
//Serializable id
)
参数类型为什么是一个序列化类Serializable
返回值类型是int
那下面我们就来删除刚刚添加的数据,注意末尾加个L
@Test
void testDelete(){
userDao.deleteById(1572364408896622593L);
}
int updateById(T t);
@Test
void testUpdate(){
User user = new User();
user.setId(1L);
user.setName("Alen");
userDao.updateById(user);
}
修改功能只修改指定的字段,未指定的字段保持原样,与比较之前方便许多,之前修改的话,要加很多判断语句是否为空。
T selectById (Serializable id)
T selectById (Serializable id)
@Test
void testSelectById(){
User user = userDao.selectById(1);
System.out.println(user);
}
List<T> selectList(Wrapper<T> queryWrapper)
Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
@Test
void testSelectAll() {
List<User> users = userDao.selectList(null);
for (User u : users) {
System.out.println(u);
}
}
方法都测试完了,那你们有没有想过,这些方法都是谁提供的呢?
代码写到这,我们发现之前的dao接口,都不用我们自己写了,只需要继承BaseMapper
,用他提供的方法就好了
但是现在我还想偷点懒,毕竟懒是第一生产力,之前我们手写模型类的时候,创建好对应的属性,然后用IDEA的Alt+Insert
快捷键,快速生成get和set方法,toSring,各种构造器(有需要的话)等
项目做这么久,写模型类都给我写烦了,有没有更简单的方式呢?
具体步骤如下
步骤一:添加Lombok依赖
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
版本不用写,SpringBoot中已经管理了lombok的版本,
步骤二:在模型类上添加注解
Lombok常见的注解有:
@Setter
:为模型类的属性提供setter方法@Getter
:为模型类的属性提供getter方法@ToString
:为模型类的属性提供toString方法@EqualsAndHashCode
:为模型类的属性提供equals和hashcode方法@Setter
@Getter
@ToString
@EqualsAndHashCode
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
@Data
:是个组合注解,包含上面的注解的功能@NoArgsConstructor
:提供一个无参构造函数@AllArgsConstructor
:提供一个包含所有参数的构造函数@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
说明:Lombok只是简化模型类的编写,我们之前的方法也能用
例如你有特殊的构造器需求,只想要name和password这两个参数,那么可以手写一个
public User(String name, String password) {
this.name = name;
this.password = password;
}
基础的增删改查功能就完成了,现在我们来进行分页功能的学习
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
具体的使用步骤如下
步骤一:调用方法传入参数获取返回值
@Test
void testSelectPage() {
IPage<User> page = new Page<>(1, 3);
userDao.selectPage(page, null);
System.out.println("当前页码" + page.getCurrent());
System.out.println("本页条数" + page.getSize());
System.out.println("总页数" + page.getPages());
System.out.println("总条数" + page.getTotal());
System.out.println(page.getRecords());
}
步骤二:设置分页拦截器
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1.设置一个MP的拦截器
MybatisPlusInterceptor myInterceptor = new MybatisPlusInterceptor();
//2.添加具体的拦截器(小的)
myInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return myInterceptor;
}
}
步骤三:运行测试程序
增删改查四个操作中,查询是非常重要的也是非常复杂的操作,这部分我们主要学习的内容有:
MP将复杂的SQL查询语句都做了封装,使用编程的方式来完成查询条件的组合
之前我们在写CRUD时,都看到了一个Wrapper类,我们当初都是赋一个null值,但其实这个类就是用来查询的
QueryWrapper
小于用lt
,大于用gt
回想之前我们在html页面中,如果需要用到小于号或者大于号,需要用对应的html实体来替换
小于
号的实体是 <
,大于
号的实体是>
@Test
void testQueryWrapper(){
QueryWrapper<User> qw = new QueryWrapper<>();
//条件为 age字段小于18
qw.lt("age",18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
这种方法有个弊端,那就是字段名是字符串类型,没有提示信息和自动补全,如果写错了,那就查不出来
QueryWrapper
的基础上,使用lambda
@Test
void testQueryWrapper(){
QueryWrapper<User> qw = new QueryWrapper<>();
qw.lambda().lt(User::getAge,18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
User::getAget
,为lambda表达式中的,类名::方法名
LambdaQueryWrapper
方式二解决了方式一的弊端,但是要多些一个lambda(),那方式三就来解决方式二的弊端,使用LambdaQueryWrapper,就可以不写lambda()
@Test
void testQueryWrapper(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.lt(User::getAge,18);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
上面三种都是单条件的查询,那我们现在想进行多条件的查询,该如何编写代码呢?
需求:查询表中年龄在10~30岁的用户信息
@Test
void testQueryWrapper(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
//大于10
lqw.gt(User::getAge,10);
//小于30
lqw.lt(User::getAge,30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
构建多条件的时候,我们还可以使用链式编程
@Test
void testQueryWrapper() {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.gt(User::getAge, 10).lt(User::getAge, 30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
lqw.gt(User::getAge, 10).or().lt(User::getAge, 30);
需求:查询年龄小于10,或者年龄大于30的用户信息
@Test
void testQueryWrapper() {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
在做条件查询的时候,一般都会有很多条件供用户查询
这些条件用户可以选择用,也可以选择不用
之前我们是通过动态SQL来实现的
<select id="selectByPageAndCondition" resultMap="brandResultMap">
select *
from tb_brand
<where>
<if test="brand.brandName != null and brand.brandName != '' ">
and brand_name like #{brand.brandName}
if>
<if test="brand.companyName != null and brand.companyName != '' ">
and company_name like #{brand.companyName}
if>
<if test="brand.status != null">
and status = #{brand.status}
if>
where>
limit #{begin} , #{size}
select>
那现在我们来试试在MP里怎么写
需求:查询数据库表中,根据输入年龄范围来查询符合条件的记录
用户在输入值的时候, 如果只输入第一个框,说明要查询大于该年龄的用户
如果只输入第二个框,说明要查询小于该年龄的用户
如果两个框都输入了,说明要查询年龄在两个范围之间的用户
问题一:后台如果想接收前端的两个数据,该如何接收?
我们可以使用两个简单数据类型,也可以使用一个模型类,但是User类中目前只有一个age属性
@TableName("tb_user")
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
使用一个age属性,如何去接收页面上的两个值呢?这个时候我们有两个解决方案
@Data
@TableName("tb_user")
public class UserQuery extends User{
private Integer age2;
}
@Test
void testQueryWrapper() {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
if (null != uq.getAge()) {
lqw.gt(User::getAge, uq.getAge());
}
if (null != uq.getAge2()) {
lqw.lt(User::getAge, uq.getAge2());
}
for (User user : userDao.selectList(lqw)) {
System.out.println(user);
}
}
lt
还有一个重载的方法,当condition为true时,添加条件,为false时,不添加条件public Children lt(boolean condition, R column, Object val) {
return this.addCondition(condition, column, SqlKeyword.LT, val);
}
故我们可以把if的判断操作,放到lt和gt方法中当做参数来写
@Test
void testQueryWrapper() {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
lqw.gt(null != uq.getAge(), User::getAge, uq.getAge())
.lt(null != uq.getAge2(), User::getAge, uq.getAge2());
for (User user : userDao.selectList(lqw)) {
System.out.println(user);
}
}
目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容,我们所说的查询投影即不查询所有字段,只查询出指定内容的数据。
具体如何来实现?
@Test
void testQueryWrapper() {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.select(User::getName,User::getName);
for (User user : userDao.selectList(lqw)) {
System.out.println(user);
}
}
select(…)
方法用来设置查询的字段列,可以设置多个
lqw.select(User::getName,User::getName);
如果使用的不是lambda,就需要手动指定字段
@Test
void testQueryWrapper() {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("name", "age");
for (User user : userDao.selectList(qw)) {
System.out.println(user);
}
}
需求:聚合函数查询,完成count、max、min、avg、sum的使用
- count:总记录数
- max:最大值
- min:最小值
- avg:平均值
- sum:求和
count
@Test
void testQueryWrapper() {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("count(*) as count");
for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {
System.out.println(selectMap);
}
}
max
@Test
void testQueryWrapper() {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("max(age) as maxAge");
for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {
System.out.println(selectMap);
}
}
min
@Test
void testQueryWrapper() {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("min(age) as minAge");
for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {
System.out.println(selectMap);
}
}
avg
@Test
void testQueryWrapper() {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("avg(age) as avgAge");
for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {
System.out.println(selectMap);
}
}
sum
@Test
void testQueryWrapper() {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("sum(age) as sumAge");
for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {
System.out.println(selectMap);
}
}
@Test
void testQueryWrapper() {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("max(age) as maxAge");
qw.groupBy("tel");
for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {
System.out.println(selectMap);
}
}
注意:
前面我们只使用了lt()和gt(),除了这两个方法外,MP还封装了很多条件对应的方法
需求:根据用户名和密码查询用户信息
@Test
void testQueryWrapper() {
LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();
qw.eq(User::getName,"Seto").eq(User::getPassword,"MUSICIAN");
User user = userDao.selectOne(qw);
System.out.println(user);
}
eq(): 相当于 =,对应的sql语句为
SELECT * FROM tb_user WHERE name = 'seto' AND password = 'MUSICIAN';
selectList:查询结果为多个或者单个
selectOne:查询结果为单个
需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询
@Test
void testQueryWrapper() {
LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();
qw.between(User::getAge,10,30);
List<User> users = userDao.selectList(qw);
for (User u : users) {
System.out.println(u);
}
}
gt()
:大于(>)ge()
:大于等于(>=)lt()
:小于(<)lte()
:小于等于(<=)between()
:between ? and ?需求:查询表中name属性的值以J开头的用户信息,使用like进行模糊查询
@Test
void testQueryWrapper() {
LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();
qw.likeRight(User::getName,"J");
List<User> users = userDao.selectList(qw);
for (User u : users) {
System.out.println(u);
}
}
like()
:前后加百分号,如 %J%,相当于包含J的namelikeLeft()
:前面加百分号,如 %J,相当于J结尾的namelikeRight()
:后面加百分号,如 J%,相当于J开头的name需求:查询表中name属性的值包含e的用户信息,使用like进行模糊查询
@Test
void testQueryWrapper() {
LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();
qw.like(User::getName,"e");
List<User> users = userDao.selectList(qw);
for (User u : users) {
System.out.println(u);
}
}
需求:查询所有数据,然后按照age降序
@Test
void testQueryWrapper() {
LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();
/**
* condition :条件,返回boolean,
当condition为true,进行排序,如果为false,则不排序
* isAsc:是否为升序,true为升序,false为降序
* columns:需要操作的列
*/
qw.orderBy(true,false,User::getAge);
List<User> users = userDao.selectList(qw);
for (User u : users) {
System.out.println(u);
}
}
遇到想用的功能,先自己用一个试试,方法名和形参名都很见名知意,遇到不确定的用法,再去官方文档查阅资料
我们做查询的时候,数据表中的字段名与模型类中的属性名一致,查询的时候没有问题,那么问题就来了
问题一:表字段与模型类编码属性不一致
问题二:编码中添加了数据库中未定义的属性
问题三:采用默认查询开放了更多的字段查看权限
知识点:@TableField
名称 | @TableField |
---|---|
类型 | 属性注解 |
位置 | 模型类属性定义上方 |
作用 | 设置当前属性对应的数据库表中的字段关系 |
相关属性 | value(默认) :设置数据库表字段名称 exist :设置属性在数据库表字段中是否存在,默认为true,此属性不能与value合并使用 select :设置属性是否参与查询,此属性与select()映射配置不冲突 |
知识点:@TableName
名称 | @TableName |
---|---|
类型 | 类注解 |
位置 | 模型类定义上方 |
作用 | 设置当前类对应于数据库表关系 |
相关属性 | value(默认):设置数据库表名称 |
前面我们在新增数据的时候,主键ID是一个很长的Long类型,我们现在想要主键按照数据表字段进行自增长,在解决这个问题之前,我们先来分析一下ID的生成策略
不同的表,应用不同的id生成策略
不同的业务采用的ID生成方式应该是不一样的,那么在MP中都提供了哪些主键生成策略,以及我们该如何进行选择?
@TableId
知识点:@TableId
名称 | @TableId |
---|---|
类型 | 属性注解 |
位置 | 模型类中用于表示主键的属性定义上方 |
作用 | 设置当前类中主键属性的生成策略 |
相关属性 | value(默认):设置数据库表主键名称type:设置主键属性的生成策略,值查照IdType的枚举值 |
步骤一:设置生成策略为AUTO
@TableName("tb_user")
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Integer online;
}
步骤二:设置自动增量为5,将4之后的数据都删掉,防止影响我们的结果
步骤三:运行新增方法
@Test
void testInsert(){
User user = new User();
user.setName("Helsing");
user.setAge(531);
user.setPassword("HELL_SING");
user.setTel("4006669999");
userDao.insert(user);
}
会发现,新增成功,并且主键id也是从5开始
我们进入源码来看看还有什么生成策略
public enum IdType {
AUTO(0),
NONE(1),
INPUT(2),
ASSIGN_ID(3),
ASSIGN_UUID(4),
/** @deprecated */
@Deprecated
ID_WORKER(3),
/** @deprecated */
@Deprecated
ID_WORKER_STR(3),
/** @deprecated */
@Deprecated
UUID(4);
private final int key;
private IdType(int key) {
this.key = key;
}
public int getKey() {
return this.key;
}
}
拓展: 分布式ID是什么?
- 当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储
- 比如订单表就有可能被存储在不同的服务器上
- 如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突
- 这个时候就需要一个全局唯一ID,这个ID就是分布式ID。
步骤一:将ID生成策略改为INPUT
@TableName("tb_user")
@Data
public class User {
@TableId(type = IdType.INPUT)
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Integer online;
}
步骤二:运行新增方法
注意这里需要手动设置ID了
@Test
void testInsert(){
User user = new User();
user.setId(6L);
user.setName("Helsing");
user.setAge(531);
user.setPassword("HELL_SING");
user.setTel("4006669999");
userDao.insert(user);
}
查看数据库,ID确实是我们设置的值
步骤一:设置生成策略为ASSIGN_ID
@TableName("tb_user")
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Integer online;
}
步骤二:运行新增方法
这里就不要手动设置ID了
@Test
void testInsert(){
User user = new User();
user.setName("Helsing");
user.setAge(531);
user.setPassword("HELL_SING");
user.setTel("4006669999");
userDao.insert(user);
}
查看结果,生成的ID就是一个Long类型的数据,生成ID时,使用的是雪花算法
雪花算法(SnowFlake),是Twitter官方给出的算法实现 是用Scala写的。其生成的结果是一个64bit大小整数
步骤一:设置生成策略为ASSIGN_UUID
@TableName("tb_user")
@Data
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Integer online;
}
步骤二:修改表的主键类型
主键类型设置为varchar,长度要大于32,因为UUID生成的主键为32位,如果长度小的话就会导致插入失败。
步骤三:运行新增方法
@Test
void testInsert(){
User user = new User();
user.setName("Helsing");
user.setAge(531);
user.setPassword("HELL_SING");
user.setTel("4006669999");
userDao.insert(user);
}
介绍完了这些主键ID的生成策略,那么以后我们开发用哪个呢?
模型类主键策略设置
如果要在项目中的每一个模型类上都需要使用相同的生成策略,比如你有Book表,User表,Student表,Score表等好多个表,如果你每一个表的主键生成策略都是ASSIGN_ID,那我们就可以用yml配置文件来简化开发,不用在每一个表的id上都加上@TableId(type = IdType.ASSIGN_ID)
mybatis-plus:
global-config:
db-config:
id-type: assign_id
数据库表与模型类的映射关系
MP会默认将模型类的类名名首字母小写作为表名使用,假如数据库表的名称都以tb_开头,那么我们就需要将所有的模型类上添加@TableName("tb_TABLENAME")
,这样做很繁琐,有没有更简单的方式呢?
mybatis-plus:
global-config:
db-config:
id-type: assign_id
table-prefix: tb_
设置表的前缀内容,这样MP就会拿 tb_加上模型类的首字母小写,就刚好组装成数据库的表名(前提是你的表名得规范命名,别瞎起花里胡哨的名)。将User类的@TableName注解去掉,再次运行新增方法
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
需求:根据传入的id集合将数据库表中的数据删除掉。
@Test
void testDeleteByIds(){
ArrayList<Long> list = new ArrayList<>();
list.add(1572543345085964289L);
list.add(1572554951983460354L);
list.add(1572555035978534913L);
userDao.deleteBatchIds(list);
}
执行成功后,数据库表中的数据就会按照指定的id进行删除。上面三个数据是我之前新增插入的,可以随便换成数据库中有的id
需求:根据传入的ID集合查询用户信息
@Test
void testSelectByIds() {
ArrayList<Long> list = new ArrayList<>();
list.add(1L);
list.add(2L);
list.add(3L);
for (User user : userDao.selectBatchIds(list)) {
System.out.println(user);
}
}
这是一个员工和其所办理的合同表,一个员工可以办理多张合同表
员工ID为1的张业绩,办理了三个合同,但是她现在想离职跳槽了,我们需要将员工表中的数据进行删除,执行DELETE操作
后来公司要统计今年的总业绩,发现这数据咋对不上呢,业绩这么少,原因是张业绩办理的合同信息被删掉了
如果只删除员工,却不删除员工对应的合同表数据,那么合同的员工编号对应的员工信息不存在,那么就会产生垃圾数据,出现无主合同,根本不知道有张业绩这个人的存在
区分的方式,就是在员工表中添加一列数据deleted
,如果为0说明在职员工,如果离职则将其改完1,(0和1所代表的含义是可以自定义的)
所以对于删除操作业务问题来说有:
- 物理删除:业务数据从数据库中丢弃,执行的是delete操作
- 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中,执行的是update操作
MP中逻辑删除具体该如何实现?
步骤二:实体类添加属性
还得修改对应的pojo类,增加delete属性(属性名也任意,对不上用@TableField
来添加映射关系
标识新增的字段为逻辑删除字段,使用@TableLogic
//表名前缀和id生成策略在yml配置文件写了
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
//新增delete属性
//value为正常数据的值(在职),delval为删除数据的值(离职)
@TableLogic(value = "0",delval = "1")
private Integer deleted;
}
步骤三:运行删除方法
@Test
void testLogicDelete(){
userDao.deleteById(1);
}
从测试结果来看,逻辑删除最后走的是update操作,执行的是UPDATE tb_user SET deleted=1 WHERE id=? AND deleted=0
,会将指定的字段修改成删除状态对应的值。
@Test
void testSelectAll() {
for (User user : userDao.selectList(null)) {
System.out.println(user);
}
}
从日志中可以看到执行的SQL语句如下,WHERE条件中,规定只查询deleted字段为0的数据
SELECT id,name,password,age,tel,deleted FROM tb_user WHERE deleted=0
输出结果当然也没有ID为1的数据了
如果还是想把已经删除的数据都查询出来该如何实现呢?
@Mapper
public interface UserDao extends BaseMapper<User> {
//查询所有数据包含已经被删除的数据
@Select("select * from tb_user")
public List<User> selectAll();
}
@TableLogic注解
,如何优化?
mybatis-plus:
global-config:
db-config:
## 逻辑删除字段名
logic-delete-field: deleted
## 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
## 逻辑删除字面值:删除为1
logic-delete-value: 1
使用yml配置文件配置了之后,就不需要
在模型类上用@TableLogic注解
了
介绍完逻辑删除,逻辑删除的本质为修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
执行的SQL语句为:
UPDATE tb_user SET deleted=1 WHERE id=? AND deleted=0
知识点:@TableLogic
名称 | @TableLogic |
---|---|
类型 | 属性注解 |
位置 | 模型类中用于表示删除字段的属性定义上方 |
作用 | 标识该字段为进行逻辑删除的字段 |
相关属性 | value:逻辑未删除值;delval:逻辑删除值 |
在学乐观锁之前,我们还是先由一个案例来引入
简单来说,乐观锁主要解决的问题是,当要更新一条记录的时候,希望这条记录没有被别人更新
version
字段,比如默认值给个1步骤一:数据库表添加列
加一列version,长度给个11,默认值设为1
步骤二:在模型类中添加对应的属性
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
@TableLogic(value = "0", delval = "1")
private Integer deleted;
@Version
private Integer version;
}
步骤三:添加乐观锁拦截器
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
步骤四:执行更新操作
@Test
void testUpdate(){
//1. 先通过要修改的数据id将当前数据查询出来
User user = userDao.selectById(1L);
//2. 修改属性
user.setName("Person");
userDao.updateById(user);
}
我们传递的是1(oldVersion),MP会将1进行加1,变成2,然后更新回到数据库中(newVersion)
大概分析完乐观锁的实现步骤以后,我们来模拟一种加锁的情况,看看能不能实现多个人修改同一个数据的时候,只能有一个人修改成功。
@Test
void testUpdate() {
User userA = userDao.selectById(1L); //version=1
User userB = userDao.selectById(1L); //version=1
userB.setName("Jackson");
userDao.updateById(userB); //B修改完了之后,version=2
userA.setName("Person");
//A拿到的version是1,但现在的version已经是2了,那么A在执行 UPDATE ... WHERE version = 1时,就必然会失败
userDao.updateById(userA);
}
至此,乐观锁的实现就已经完成了
官方文档地址
通过观察我们之前写的代码,会发现其中有很多重复的内容,于是MP抽取了这些重复的地方,做成了一个模板供我们使用
要想完成代码自动生成,我们需要有以下内容:
步骤一:创建一个Maven项目
步骤二:导入对应的jar包
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.1version>
parent>
<groupId>com.bloggroupId>
<artifactId>mybatisplus_04_generatorartifactId>
<version>0.0.1-SNAPSHOTversion>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.12version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>org.apache.velocitygroupId>
<artifactId>velocity-engine-coreartifactId>
<version>2.3version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
步骤三:编写引导类
@SpringBootApplication
public class Mybatisplus04GeneratorApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus04GeneratorApplication.class, args);
}
}
步骤四:创建代码生成类
public class CodeGenerator {
public static void main(String[] args) {
//1.获取代码生成器的对象
AutoGenerator autoGenerator = new AutoGenerator();
//设置数据库相关配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("YOURPASSWORD");
autoGenerator.setDataSource(dataSource);
//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/项目名/src/main/java"); //设置代码生成位置
globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("heima"); //设置作者
globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件
globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称
globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);
//设置包名相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.aaa"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity("domain"); //设置实体类包名
packageInfo.setMapper("dao"); //设置数据层包名
autoGenerator.setPackageInfo(packageInfo);
//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tb_user"); //设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tb_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tb_user - tb_
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);
//2.执行生成操作
autoGenerator.execute();
}
}
对于代码生成器中的代码内容,我们可以直接从官方文档中获取代码进行修改,
controller
,service
,mapper
和entity
等至此代码生成器就已经完成工作,我们能快速根据数据库表来创建对应的类,简化我们的代码开发。
初期还是不建议直接使用代码生成器,还是多自己手写几遍比较好
回顾我们之前业务层代码的编写,编写接口和对应的实现类:
回顾我们之前业务层代码的编写,编写接口和对应的实现类:
public interface UserService{
}
@Service
public class UserServiceImpl implements UserService{
}
接口和实现类有了以后,需要在接口和实现类中声明方法
public interface UserService{
public List<User> findAll();
}
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
public List<User> findAll(){
return userDao.selectList(null);
}
}
MP看到上面的代码以后就说这些方法也是比较固定和通用的,那我来帮你抽取下,所以MP提供了一个Service接口和实现类,分别是:IService和ServiceImpl,后者是对前者的一个具体实现。
以后我们自己写的Service就可以进行如下修改:
public interface UserService extends IService<User>{
}
@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService{
}
修改以后的好处是,MP已经帮我们把业务层的一些基础的增删改查都已经实现了,可以直接进行使用。
编写测试类进行测试:
@SpringBootTest
class Mybatisplus04GeneratorApplicationTests {
private IUserService userService;
@Test
void testFindAll() {
List<User> list = userService.list();
System.out.println(list);
}
}
之前如果要写动态SQL查询,需要用XML配置文件,用
,
标签来自动去除and连接词啥的。
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
<where>
<if test="brand.brandName != null and brand.brandName != '' ">
and brand_name like #{brand.brandName}
if>
<if test="brand.companyName != null and brand.companyName != '' ">
and company_name like #{brand.companyName}
if>
<if test="brand.status != null">
and status = #{brand.status}
if>
where>
limit #{begin} , #{size}
select>
学完MyBatisPlus之后,我们可以不用XML配置文件,就用MP也能写动态SQL,用Wrapper类。
@Override
public List<Book> getByCondition(String type,String name) {
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();
lqw.like(!(type == null || "".equals(type)), Book::getType, type)
.like(!(name == null || "".equals(name)),Book::getName, name);
return bookDao.selectList(lqw);
}
MP里的and不用显示声明,而且还可以很简单的帮我们完成模糊查询,当判断条件为false时,则不会进行SQL语句的拼接。而且也不需要创建文件,写配置
不过复杂的SQL语句还是要用XML写的,用MP写的话,可读性不