来吧,MyBatisPlus的知识点都在这里了(适合收藏夹吃灰)_mybatisplus知识点(1)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

// wrapper.notInSql(“name”,“select name from user where age>21”);

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}



##### 4)分组与排序


* 1)分组:


通过Wrapper.query()构建的查询字段默认是表中的所有字段,因此在这种情况下分组是没有意义的,分组具体的用法我们后面再详细介绍;



/**
* 分组
*/
@Test
public void test4() {

// 创建wrapper对象
QueryWrapper<User> wrapper = Wrappers.query();

/\*

相当于 select * from user where gropu sex
这是一个没有意义的分组,分组必须结合查询的字段来体现,后续学QueryWrapper的select方法再介绍
*/
wrapper.groupBy(“sex”);

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}


* 2)having操作



/**
* having操作
*/
@Test
public void test5() {

// 创建wrapper对象
QueryWrapper<User> wrapper = Wrappers.query();

// group by sex having sex = 0
wrapper.groupBy("sex");
wrapper.having("sex", "0");

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}


* 3)排序:



/**
* 排序
*/
@Test
public void test6() {
// 创建wrapper对象
QueryWrapper wrapper = Wrappers.query();

/\*\*

* 参数1: 是否是Asc排序(升序), true : asc排序, false: desc排序
* 参数2: 排序的字段
*/
// wrapper.orderByAsc(“age”); // order by age asc

// wrapper.orderByDesc(“age”); // order by age desc

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}



##### 5)多条件的拼接


Wrapper对象在调用每一个方法时都会返回当前对象(Wrapper),这样可以很好的方便我们链式编程;另外MyBatis Plus在拼接多个条件时默认使用and拼接,如果需要使用or,那么需要显示的调用or()方法;


* 1)and拼接条件:



/**
* and拼接条件
*/
@Test
public void test7() {
// 创建wrapper对象
QueryWrapper wrapper = Wrappers.query();

/\*

默认情况下,多条件是以and拼接
SQL语句: name LIKE “%a%” AND age > 20 AND sex = 0
*/
wrapper.like(“name”, “%a%”)
.lt(“age”, 20)
.eq(“sex”, 0);

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}


* 2)or拼接条件:



/**
* or拼接条件
*/
@Test
public void test8() {
// 创建wrapper对象
QueryWrapper wrapper = Wrappers.query();

/\*

默认情况下,多条件是以and拼接
SQL语句: name LIKE “%a%” OR age > 20 AND sex = 0
*/
wrapper.like(“name”, “%a%”)
.or()
.lt(“age”, 20)
.eq(“sex”, 0); // 该条件仍然以AND拼接

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}



##### 6)Wrapper的其他方法


* 1)and方法:用于拼接一个其他的整体条件;示例如下:



/**
* and方法
*/
@Test
public void test1() {
QueryWrapper wrapper = Wrappers.query();

// 生成的SQL为: (age < ? AND (sex = ? OR name LIKE ?))
/\*

wrapper.lt(“age”, 20);
wrapper.and(new Consumer<QueryWrapper>() {
@Override
public void accept(QueryWrapper userQueryWrapper) {
userQueryWrapper.eq(“sex”, 0)
.or()
.like(“name”, “J”);
}
});
*/

// 生成的SQL为: (age < ? AND sex = ? OR name LIKE ?)
wrapper.lt("age", 20)
        .eq("sex", 0)
        .or()
        .like("name", "J");

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}


* 2)func方法:用于多条件的拼接,直接使用之前的方法也可以做到这个功能;示例如下:



/**
* func方法
*/
@Test
public void test2() {
QueryWrapper wrapper = Wrappers.query();

// 生成的SQL语句条件: (age < ? AND name LIKE ? AND sex = ?)
wrapper.lt("age", 20);
wrapper.func(new Consumer<QueryWrapper<User>>() {
    @Override
    public void accept(QueryWrapper<User> userQueryWrapper) {

        userQueryWrapper.like("name", "a");
        userQueryWrapper.eq("sex", "0");
    }
});
// 等价于:

// wrapper.lt(“age”, 20).like(“name”, “a”).eq(“sex”, “0”);

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}


* 3)nested方法:功能等价于and方法



/**
* nested方法
*/
@Test
public void test3() {
QueryWrapper wrapper = Wrappers.query();

// 生成的SQL语句条件为: (id = ? AND (name LIKE ? OR age > ?))
// nested()等价于and方法()
wrapper.eq("id", 1);
wrapper.nested(new Consumer<QueryWrapper<User>>() {
    @Override
    public void accept(QueryWrapper<User> userQueryWrapper) {

        // 默认情况下是用and来拼接多个条件
        userQueryWrapper
                .like("name", "a")
                .or()
                .gt("age", 20);
    }
});

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}


* 4)apply方法:可以使用占位符进行参数传参;示例如下:



/**
* apply方法
*/
@Test
public void test4() {
QueryWrapper wrapper = Wrappers.query();

// SQL: (name like ? and age > ?)

// wrapper.apply(“name like {0} and age > {1}”, “%J%”, 18);

// SQL: (date\_format(birthday, '%Y-%m-%d') = ?)
wrapper.apply("date\_format(birthday, '%Y-%m-%d') = {0}", "2001-10-04");

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}


* 4)last方法:无视优化规则直接拼接到 sql 的最后,只能调用一次,多次调用以最后一次为准 有sql注入的风险



> 
> Tips:apply方法可以防止SQL注入,但last方法不能;
> 
> 
> 



@Test
public void test13() {
QueryWrapper wrapper = Wrappers.query();

// 无视优化规则直接拼接到 sql 的最后,只能调用一次,多次调用以最后一次为准 有sql注入的风险

// SQL: name = 'Jone'

// String name = “Jone”;
// wrapper.last(“where name = '” + name + “'”);

// SQL: SELECT id,name,sex,age FROM user where name ='' or 1=1; -- '
String name = "' or 1=1; -- ";
wrapper.last("where name ='" + name + "'");         // 出现SQL注入问题

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}


* 5)exists方法:用于exists语句



/**
* exists方法
*/
@Test
public void test6() {
QueryWrapper wrapper = Wrappers.query();

// SQL: (EXISTS (select 1))
wrapper.exists("select 1");

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}


* 6)notExists方法:



/**
* notExists方法
*/
@Test
public void test7() {
QueryWrapper wrapper = Wrappers.query();

// SQL: (NOT EXISTS (select 1))
wrapper.notExists("select 1");

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}



#### 3.3.2 QueryMapper


QueryMapper是AbstractWrapper的子类,主要用于查询指定字段,方法列表如下:




| 方法名 | 解释 | 示例 |
| --- | --- | --- |
| select(String… sqlSelect) | 设置查询字段 | 例1:select(“id”, “name”, “age”) 例2:select(i -> i.getProperty().startsWith(“test”)) |


* 示例代码:



package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo04_QueryWrapper {

@Autowired
private UserMapper userMapper;

// 选择查询的字段
@Test
public void test1() throws Exception {
    QueryWrapper<User> wrapper = Wrappers.query();

    // 确定要查询的字段
    wrapper.select("id", "name", "sex");

    // in
    wrapper.in("id","1","2");

    // SQL: SELECT id,name,sex FROM user WHERE (id IN (?,?))
    List<User> userList = userMapper.selectList(wrapper);

    userList.forEach(System.out::println);
}


@Test
public void test2() throws Exception {

    User user = new User();
    user.setId(1L);

    // user当做查询的条件
    QueryWrapper<User> wrapper = Wrappers.query(user);
    
    // 指定查询的列
    wrapper.select("id", "name", "sex");

    // SQL: SELECT id,name,sex FROM user WHERE id=?
    List<User> userList = userMapper.selectList(wrapper);

    userList.forEach(System.out::println);
}

}



#### 3.3.3 UpdateWrapper


UpdateWrapper也是AbstractWrapper的子类,因此UpdateWrapper也具备之前的那些查询方法,不同的是,UpdateMapper在那些方法基础之上还提供了很多有关于更新操作的方法;


* 方法如下:  
 | 方法名 | 解释 | 示例 |  
 | — | — | — |  
 | set(String column, Object val) | 设置查询字段 | 例1:`set("name", "老李头")`  
例2:`set("name", "")`  
—>数据库字段值变为**空字符串**例3:`set("name", null)`  
—>数据库字段值变为`null` |  
 | setSql(String sql) | 设置set子句的部分SQL | 例1:`setSql("name = '老李头'")`  
例2:`setSql("name = '老李头',age=20 where id=1")` |


示例代码:



package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo05_UpdateWrapper {

@Autowired
private UserMapper userMapper;

@Test
public void test1() throws Exception {
    UpdateWrapper<User> wrapper = Wrappers.update();

    // UpdateWrapper也是AbstractWrapper的子类,因此也具备一些基本的查询方法
    wrapper.like("name", "J");

    List<User> userList = userMapper.selectList(wrapper);

    for (User user : userList) {
        System.out.println(user);
    }
}


/\*\*

* 第一种方法: 使用wrapper来修改,并且指定查询条件
*
* @throws Exception
*/
@Test
public void test2() throws Exception {
UpdateWrapper wrapper = Wrappers.update();
wrapper.set(“name”, “Jackson”);
wrapper.set(“age”, “16”);
wrapper.set(“sex”, “1”);
wrapper.eq(“id”, 2L);

    // SQL: UPDATE user SET name=?, sex=?, age=? WHERE (id = ?)
    userMapper.update(null, wrapper);
}


/\*\*

* 第二种方法: 使用wrapper来封装条件,使用entity来封装修改的数据
*
* @throws Exception
*/
@Test
public void test3() throws Exception {
UpdateWrapper wrapper = Wrappers.update();
wrapper.eq(“id”, 2L);

    User user = new User(null, "Jack", "0", 28);

    // SQL: UPDATE user SET name=?, sex=?, age=? WHERE (id = ?)
    userMapper.update(user, wrapper);

}

/\*\*

* 第三种方法: Wrappers.update(user)传递查询的条件,使用wrapper来修改
*
* @throws Exception
*/
@Test
public void test4() throws Exception {
User user = new User();
user.setId(1L);

    // user当做查询条件
    UpdateWrapper<User> wrapper = Wrappers.update(user);
    wrapper.set("name", "xiaohui");
    wrapper.set("sex", "0");
    wrapper.set("age", "22");

    // SQL : UPDATE user SET name=?,sex=?,age=? WHERE id=?
    userMapper.update(null, wrapper);
}

/\*\*

* setSql方法
*
* @throws Exception
*/
@Test
public void test5() throws Exception {
UpdateWrapper wrapper = Wrappers.update();
wrapper.setSql(“name=‘abc’,sex=‘0’,age=18 where id=1”);

    // SQL: UPDATE user SET name='abc',sex='0',age=18 where id=1
    userMapper.update(null, wrapper);
}

}



#### 3.3.4 LambdaQueryWrapper


LambdaQueryWrapper是QueryWrapper的子类,具备QueryWrapper的所有方法,QueryWrapper的方法上提供了一系列有关于方法引的操作;


* 使用示例:



package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo06_LambdaQueryMapper {

@Autowired
private UserMapper userMapper;

/\*\*

* 使用QueryWrapper
* @throws Exception
*/
@Test
public void test1() throws Exception {

    QueryWrapper<User> wrapper = Wrappers.query();
    wrapper.eq("id","1");

    List<User> userList = userMapper.selectList(wrapper);
    for (User user : userList) {
        System.out.println(user);
    }
}

/\*\*

* 使用LambdaQueryWrapper
* @throws Exception
*/
@Test
public void test2() throws Exception {

    LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
    
    // id=1

// wrapper.eq(User::getId,1);

    // select id,name,age from user where id in (1,2,3) and name like "%a%" 
    wrapper.in(User::getId,"1","2","3")
            .like(User::getName,"a")
            .select(User::getId,User::getName,User::getAge);

    List<User> userList = userMapper.selectList(wrapper);
    for (User user : userList) {
        System.out.println(user);
    }
}

}



#### 3.3.5 LambdaUpdateMapper


LambdaUpdateMapper同样是UpdateMapper的子类,具备UpdateMapper的所有方法,UpdateMapper的方法上提供了一系列有关于方法引的操作;


* 示例代码:



package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo07_LambdaUpdateWrapper {

@Autowired
private UserMapper userMapper;

/\*\*

* 使用UpdateWrapper
*
* @throws Exception
*/
@Test
public void test1() throws Exception {

    UpdateWrapper<User> wrapper = Wrappers.update();
    wrapper.eq("id", "1");
    wrapper.set("name", "xiaohui");
    wrapper.set("age", 20);

    userMapper.update(null, wrapper);
}

/\*\*

* 使用LambdaUpdateWrapper
*
* @throws Exception
*/
@Test
public void test2() throws Exception {

    LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate();

    wrapper.eq(User::getId,"1");
    wrapper.set(User::getName,"xiaolan");
    wrapper.set(User::getAge,18);

    userMapper.update(null,wrapper);
}

}



### 3.3 Mapper的分页查询



#### 3.3.1 Mapper分页查询配置


在MyBatis中提供有Page对象来帮助我们实现分页查询,在Page对象中有如下成员:




| 成员变量 | 说明 |
| --- | --- |
| List getRecords() | 当前页数据 |
| public long getTotal() | 总记录数 |
| public long getSize() | 页大小 |
| public long getCurrent() | 当前页 |
| default long getPages() | 总页数 |
| public boolean hasNext() | 是否有上一页 |
| public boolean hasPrevious() | 是否有上一页 |


MyBatisPlus的分页逻辑底层是通过分页插件来完成的,因此我们首先要配置MyBatisPlus的分页插件;


* 配置分页插件:



package com.dfbz.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@Configuration
@MapperScan(“com.dfbz.mapper”) // mapper接口的所在位置
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new
PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}



#### 3.3.2 Mapper完成分页查询


在BaseMapper中主要提供有如下方法来完成分页查询:


* `<E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper)`:
	+ 参数1:分页配置类
	+ 参数2:分页查询条件
	+ 解释:根据分页配置和分页查询条件来完成分页查询,当前页数据为指定类型
* `<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper)`
	+ 参数1:分页配置类
	+ 参数2:分页查询条件
	+ 解释:根据分页配置和分页查询条件来完成分页查询,当前页数据为Map类型




---


* 1)无条件分页查询:



package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo06_BaseMapper的分页查询 {

@Autowired
private UserMapper userMapper;

/\*\*

* 无条件分页查询
* @throws Exception
*/
@Test
public void test1() throws Exception {

    // 封装分页信息
    Page<User> page = new Page<>(1,3);

    /\*

执行分页查询,并将结果封装到page中
参数1: 分页配置
参数2: 查询条件
*/
userMapper.selectPage(page, null);

    // 当前页数据
    List<User> pageData = page.getRecords();
    for (User user : pageData) {
        System.out.println(user);
    }

    System.out.println("------------");

    System.out.println("当前页:" + page.getCurrent());
    System.out.println("每页显示的条数:" + page.getSize());
    System.out.println("总记录数:" + page.getTotal());
    System.out.println("总页数:" + page.getPages());
    System.out.println("是否有上一页:" + page.hasPrevious());
    System.out.println("是否有下一页:" + page.hasNext());
}

}


* 查询结果:



首先查询总记录数

> Preparing: SELECT COUNT() FROM user
> Parameters:
<
Columns: COUNT(
)
<
Row: 10
<== Total: 1

再查询分页

> Preparing: SELECT id,name,sex,age FROM user LIMIT ?
> Parameters: 3(Long)
<
Columns: id, name, sex, age
<
Row: 1, Jone, 1, 27
<== Row: 2, Jack, 0, 20
<== Row: 3, Tom, 1, 28
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@226eba67]
User(id=1, name=Jone, sex=1, age=27)
User(id=2, name=Jack, sex=0, age=20)
User(id=3, name=Tom, sex=1, age=28)

当前页:1
每页显示的条数:3
总记录数:10
总页数:4
是否有上一页:false
是否有下一页:true
2022-11-15 15:28:16.946 INFO 8724 — [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated…
2022-11-15 15:28:16.948 INFO 8724 — [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.

Process finished with exit code 0


* 带条件分页查询:



/**
* 带条件分页查询
*
* @throws Exception
*/
@Test
public void test2() throws Exception {

QueryWrapper<User> wrapper = Wrappers.query();
wrapper.like("name", "a");

// 封装分页信息
Page<User> page = new Page<>(1, 3);

/\*

执行分页查询,并将结果封装到page中
参数1: 分页配置
参数2: 查询条件
*/
userMapper.selectPage(page, wrapper);

// 当前页数据
List<User> pageData = page.getRecords();
for (User user : pageData) {
    System.out.println(user);
}

System.out.println("------------");

System.out.println("当前页:" + page.getCurrent());
System.out.println("每页显示的条数:" + page.getSize());
System.out.println("总记录数:" + page.getTotal());
System.out.println("总页数:" + page.getPages());
System.out.println("是否有上一页:" + page.hasPrevious());
System.out.println("是否有下一页:" + page.hasNext());

}


* 查询结果:



> Preparing: SELECT COUNT() FROM user WHERE (name LIKE ?)
> Parameters: %a%(String)
<
Columns: COUNT(
)
<
Row: 5
<== Total: 1
> Preparing: SELECT id,name,sex,age FROM user WHERE (name LIKE ?) LIMIT ?
> Parameters: %a%(String), 3(Long)
<
Columns: id, name, sex, age
<
Row: 2, Jack, 0, 20
<== Row: 4, Sandy, 1, 21
<== Row: 6, Jackson, 0, 18
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58a2b4c]
User(id=2, name=Jack, sex=0, age=20)
User(id=4, name=Sandy, sex=1, age=21)
User(id=6, name=Jackson, sex=0, age=18)

当前页:1
每页显示的条数:3
总记录数:5
总页数:2
是否有上一页:false
是否有下一页:true


* 3)将分页数据的查询结果以Map类型返回



/**
* 查询结果以Map返回
*
* @throws Exception
*/
@Test
public void test3() throws Exception {

// 封装分页信息
Page page = new Page<>(1, 3);

userMapper.selectMapsPage(page, null);

// 每一条记录都是一个HashMap
List<HashMap<String,Object>> pageData = page.getRecords();
for (HashMap userMap : pageData) {
    System.out.println(userMap);
}

System.out.println("------------");

System.out.println("当前页:" + page.getCurrent());
System.out.println("每页显示的条数:" + page.getSize());
System.out.println("总记录数:" + page.getTotal());
System.out.println("总页数:" + page.getPages());
System.out.println("是否有上一页:" + page.hasPrevious());
System.out.println("是否有下一页:" + page.hasNext());

}


* 查询结果:



> Preparing: SELECT COUNT() FROM user
> Parameters:
<
Columns: COUNT(
)
<
Row: 10
<== Total: 1
> Preparing: SELECT id,name,sex,age FROM user LIMIT ?
> Parameters: 3(Long)
<
Columns: id, name, sex, age
<
Row: 1, Jone, 1, 27
<== Row: 2, Jack, 0, 20
<== Row: 3, Tom, 1, 28
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7123be6c]
{sex=1, name=Jone, id=1, age=27}
{sex=0, name=Jack, id=2, age=20}
{sex=1, name=Tom, id=3, age=28}

当前页:1
每页显示的条数:3
总记录数:10
总页数:4
是否有上一页:false
是否有下一页:true
2022-11-15 15:50:37.905 INFO 20980 — [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated…
2022-11-15 15:50:37.905 INFO 20980 — [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.

Process finished with exit code 0



### 3.4 MyBatis Plus的Service查询



#### 3.4.1 通用Service简介


通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行、remove删除、list 查询集合、page查询分页



> 
> 官网案例:<https://baomidou.com/pages/49cc81/>
> 
> 
> 


使用步骤:


* 1)定义一个UserService接口继承与MyBatisPlus提供的IService接口:



package com.dfbz.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.dfbz.entity.User;

/**
* @author lscl
* @version 1.0
* @intro:
*/
public interface IUserService extends IService {
}


* 2)定义一个UserService的实现类,并且继承与MyBatisPlus提供的ServiceImpl:



package com.dfbz.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import com.dfbz.service.IUserService;
import org.springframework.stereotype.Service;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@Service(“userService”)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

}



#### 3.4.2 通用service常用方法


* 新增:
	+ `default boolean save(T entity)`:新增记录
	+ `boolean saveBatch(Collection<T> entityList)`:批量插入
	+ `saveBatch(Collection<T> entityList, int batchSize)`:一次性批量插入batchSize条记录
* 删除:
	+ `boolean removeById(Serializable id)`:根据id删除
	+ `boolean removeByMap(Map<String, Object> columnMap)`:根据条件删除
	+ `boolean remove(Wrapper<T> queryWrapper)`:使用Wrapper封装条件删除
	+ `boolean removeByIds(Collection<? extends Serializable> idList)`:删除一批
* 修改:
	+ `boolean updateById(T entity)`:修改
	+ `boolean update(Wrapper<T> updateWrapper)`:根据Wrapper修改
	+ `boolean update(T entity, Wrapper<T> updateWrapper)`:使用Wrapper查询出结果,修改为entity
	+ `boolean updateBatchById(Collection<T> entityList)`:批量修改
	+ `updateBatchById(Collection<T> entityList, int batchSize)`:一次性批量修改batchSize条记录
	+ `boolean saveOrUpdate(T entity)`:如果id存在则修改,如果id不存在则新增
* 查询:
	+ `T getById(Serializable id)`:根据id查询
	+ `List<T> listByIds(Collection<? extends Serializable> idList)`:根据一批id查询多条记录
	+ `List<T> listByMap(Map<String, Object> columnMap)`:根据条件查询多条记录
	+ `T getOne(Wrapper<T> queryWrapper)`:根据Wrapper查询一条记录,如果查询到多条则抛出异常
	+ `T getOne(Wrapper<T> queryWrapper, boolean throwEx)`:根据Wrapper查询一条记录,通过throwEx决定是否抛出异常
	+ `int count()`:查询总记录数
	+ `int count(Wrapper<T> queryWrapper)`:根据条件查询总记录数
* 分页:
	+ `<E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper)`:带条件分页查询,当前页数据为T类型
	+ `<E extends IPage<T>> E page(E page)`:无条件分页
	+ `List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper)`:带条件分页查询,当前页数据为HashMap类型
	+ `List<Map<String, Object>> listMaps()`:无条件分页




---


* 测试代码:



package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dfbz.entity.User;
import com.dfbz.service.IUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.List;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo07_Service查询 {

@Autowired
private IUserService userService;

/\*\*

* 新增
*
* @throws Exception
*/
@Test
public void test1() throws Exception {
User user = new User(null, “xiaohui”, “0”, 20);
userService.save(user);
}

/\*\*

* 如果id存在则修改,不存在则新增
*
* @throws Exception
*/
@Test
public void test2() throws Exception {
User user = new User(1L, “xiaohui”, “0”, 20);
userService.saveOrUpdate(user);
}

/\*\*

* 根据id删除
*
* @throws Exception
*/
@Test
public void test3() throws Exception {
userService.removeById(1L);
}

/\*\*

* 根据id修改
*
* @throws Exception
*/
@Test
public void test4() throws Exception {

    User user = new User(1L, "xiaolan", "1", 18);
    userService.updateById(user);
}

/\*\*

* 根据id查询
*
* @throws Exception
*/
@Test
public void test5() throws Exception {
User user = userService.getById(1L);
System.out.println(user);
}

/\*\*

* 查询列表
*
* @throws Exception
*/
@Test
public void test6() throws Exception {

    QueryWrapper<User> wrapper = Wrappers.query();
    wrapper.in("id", "1", "2");

    // 查询所有

// List userList = userService.list();

    // 通过wrapper查询
    List<User> userList = userService.list(wrapper);

    for (User user : userList) {
        System.out.println(user);
    }
}


/\*\*

* 查询总记录数
*
* @throws Exception
*/
@Test
public void test7() throws Exception {

    QueryWrapper<User> wrapper = Wrappers.query();
    wrapper.like("name", "a");

    // 查询总记录数

// int count = userService.count();

    // 根据条件查询总记录数
    int count = userService.count(wrapper);

    System.out.println(count);
}

/\*\*

* 分页查询(当前页类型为指定类型)
*
* @throws Exception
*/
@Test
public void test8() throws Exception {

    Page<User> page = new Page<>(1, 3);

    userService.page(page);

    // 当前页数据
    List<User> pageData = page.getRecords();
    for (User user : pageData) {
        System.out.println(user);
    }

    System.out.println("------------");

    System.out.println("当前页:" + page.getCurrent());
    System.out.println("每页显示的条数:" + page.getSize());
    System.out.println("总记录数:" + page.getTotal());
    System.out.println("总页数:" + page.getPages());
    System.out.println("是否有上一页:" + page.hasPrevious());
    System.out.println("是否有下一页:" + page.hasNext());
}


/\*\*

* 分页查询(当前页结果为HashMap类型)
*
* @throws Exception
*/
@Test
public void test9() throws Exception {

    Page page = new Page<>(1, 3);

    userService.pageMaps(page);

    // 当前页数据
    List<HashMap<String, Object>> pageData = page.getRecords();
    for (HashMap userMap : pageData) {
        System.out.println(userMap);
    }

    System.out.println("------------");

    System.out.println("当前页:" + page.getCurrent());
    System.out.println("每页显示的条数:" + page.getSize());
    System.out.println("总记录数:" + page.getTotal());
    System.out.println("总页数:" + page.getPages());
    System.out.println("是否有上一页:" + page.hasPrevious());
    System.out.println("是否有下一页:" + page.hasNext());
}


/\*\*

* 链式查询
*
* @throws Exception
*/
@Test
public void test10() throws Exception {

    QueryChainWrapper<User> chainWrapper = userService.query();

    // SQL: SELECT id,name,age FROM user WHERE (id IN (?,?,?) AND name LIKE ?)
    List<User> userList = chainWrapper.select("id", "name", "age")
            .in("id", "1", "2", "3")
            .like("name", "a")
            .list();

    for (User user : userList) {
        System.out.println(user);
    }
}


/\*\*

* 链式修改
*
* @throws Exception
*/
@Test
public void test11() throws Exception {

    UpdateChainWrapper<User> chainWrapper = userService.update();

    // SQL: UPDATE user SET age=? WHERE (id IN (?,?) OR sex = ?)
    chainWrapper.in("id","1","2")
            .or()
            .eq("sex","0")
            .set("age",20).
            update();
}

}



### 3.5 MyBatisPlus的常用注解



#### 3.5.1 @TableName


操作数据库表时,Mapper接口继承BaseMapper,泛型名和数据库表名对应,如果数据表名为t\_user,而BaseMapper的泛型为实体类User,导致找不到数据库的表。


* 1)解决方法1:实体类使用@TableName注解,value值为表名



/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor

@TableName(“t_user”)
public class User {
private Long id;
private String name;
private String sex;
private Integer age;
}


* 2)解决方法2:如果多张表的表名为t\_user/t\_cat/t\_xxx,不需要为每一个实体类添加@TableName注解,在MyBatis全局配置即可,为所有表名添加前缀



mybatis-plus:
global-config: # MyBatisPlus全局配置
db-config: # 配置数据库
table-prefix: t_ #配置表名前缀为t_



#### 3.5.2 @TableId


MyBatisPlus在实现CRUD默认会将Id作为主键,在插入数据时,如果主键不叫Id则添加功能会失败


解决方案:@TableId注解标识属性,将此属性对应的字段指定为主键



/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor

@TableName(“t_user”)
public class User {

// 将当前属性所对应的字段作为主键
@TableId
private Long id;
private String name;
private String sex;
private Integer age;

}



##### 1)value属性


问题:实体类中被标识为主键的属性名为id,而数据库的主键为uid,则id属性不会对应uid字段上


解决方案:使用@TableId的value属性设置当前主键字段的字段名为uid



package com.dfbz.entity;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

@TableId(value = "uid")
private Long id;
private String name;
private String sex;
private Integer age;

}



##### 2)主键策略


MybatisPlus为我们支持了许多种的主键策略;


主键策略是指Mybatis-plus可以自动生成主键的策略,不需要手动插入主键,由MybatisPlus的主键策略帮我们自动生成主键



> 
> 官网资料:[https://baomidou.com/pages/e131bd/#spring-boot)](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb)
> 
> 
> 


在枚举类IdType中定制有如下几种注解策略:



public enum IdType {

// 数据自增(该类型请确保数据库设置了 ID自增 否则无效)
AUTO(0),

// 采用雪花算法生成ID
NONE(1),

// 交由用户自己分配ID(必须分配,不分配则出现异常,除非数据库表本身设置了自增)
INPUT(2),

// 采用雪花算法((主键类型为number或string)
// 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
ASSIGN\_ID(3),

// 分配UUID (主键类型为 string)
// 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
ASSIGN\_UUID(4),

// 不推荐使用,推荐使用ASSIGN\_ID
@Deprecated
ID\_WORKER(3),

// 不推荐使用,推荐使用ASSIGN\_ID
@Deprecated
ID\_WORKER\_STR(3),

// 不推荐使用,推荐使用ASSIGN\_UUID
@Deprecated
UUID(4);

private final int key;

IdType(int key) {
    this.key = key;
}

}


* 实体类:



/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

// 使用数据库自增策略
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String sex;
private Integer age;

}


* 全局配置自增策略:



mybatis-plus:
global-config: # MyBatisPlus全局配置
db-config: # 配置数据库
id-type: auto # 统一设置id生成策略


测试代码:



package com.dfbz;

import com.dfbz.entity.User;
import com.dfbz.service.IUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo08_常用注解 {

@Autowired
private IUserService userService;

@Test
public void test1() throws Exception {
    User user = new User(111L, "xiaohui", "0", 20);
    userService.save(user);
}

}



#### 3.5.3 @TableField


如果实体类的普通属性名,和数据库非主键的字段名不一致解决方案:@TableField设置对应字段名



/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

@TableId(type = IdType.AUTO)
private Long id;

@TableField("user\_name")
private String name;

@TableField("user\_sex")
private String sex;

@TableField("user\_age")
private Integer age;

}



#### 3.5.4 @TableLogic


在实际开发中,我们删除一条记录可能只是修改其状态,并不是真正的从数据库中删除掉;这样的删除成为逻辑删除;


* 逻辑删除:表中设置字段为删除状态 比如删除为1 未删除为0 则查询时,只会查到状态为0的数据(可以进行数据恢复)。
* 物理删除:从表中删除。


准备一张数据表:



DROP TABLE IF EXISTS emp;
CREATE TABLE emp (
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘主键ID’,
name varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT ‘姓名’,
is\_delete int(11) NULL DEFAULT NULL COMMENT ‘是否删除 0:未删除 1:删除’,
PRIMARY KEY (id) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;


– Records of emp


INSERT INTO emp VALUES (1, ‘xiaohui’, 0);
INSERT INTO emp VALUES (2, ‘xiaolan’, 0);


实体类:



package com.dfbz.entity;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp {

@TableId(type = IdType.AUTO)
private Long id;
private String name;

/\*

表示该列为逻辑删除字段
0: 表示未删除
1: 表示已删除
*/
@TableLogic
private Integer isDelete;
}


测试代码:



@Autowired
private EmpMapper empMapper;

@Test
public void test2() throws Exception {

// 只是逻辑删除(把id为1的记录的is\_delete改为1)
empMapper.deleteById(1L);

}


执行代码,观察日志:


![97.png](https://img-blog.csdnimg.cn/img_convert/88932cba52ce951e2a8844dfb3228b27.png#averageHue=#2e3b43&clientId=u7de393e7-47d7-4&from=paste&height=110&id=u2e7e5c83&name=97.png&originHeight=247&originWidth=1417&originalType=binary&ratio=2.25&rotation=0&showTitle=false&size=68835&status=done&style=none&taskId=uec672296-6925-45b2-99f8-a885a4fa498&title=&width=629.7777777777778)



> 
> Tips:本质上就是执行了一个update
> 
> 
> 


当实体类中有标注逻辑删除字段时,在查询时是不会查询被逻辑删除的记录的,示例代码:



@Test
public void test3() throws Exception {

// 不会查询is\_delete为1的记录
List<Emp> empList = empMapper.selectList(null);
for (Emp emp : empList) {
    System.out.println(emp);
}

}


执行结果:


![96.png](https://img-blog.csdnimg.cn/img_convert/1ccd868ff360a8b9bc50324249304f7a.png#averageHue=#2b383f&clientId=u7de393e7-47d7-4&from=paste&height=225&id=u6384eb47&name=96.png&originHeight=506&originWidth=2012&originalType=binary&ratio=2.25&rotation=0&showTitle=false&size=122303&status=done&style=none&taskId=ufd9638e2-9701-4bde-9df1-a0a21290240&title=&width=894.2222222222222)



#### 3.5.5 @EnumValue


在数据库中,经常会有一些字段符合枚举类型;


例如:


* sex:0代表男,1代表女
* status:0代表未开始,1代表进行中,2代表已结束
* is\_secret:0代表私密,1代表公开等
* is\_mark:0代表未签收,1代表拒签,2代表已签收


我们通常都是将以上字段设置为char或者int类型,然后通过对应的字符/数字代表对应的含义,然后到Java代码中我们通常都需要做类似于如下的判断:



${sex==0?‘男’:‘女’}

<if test= s t a t u s = = 0 > 未开始 < / i f > < i f t e s t = {status==0}>未开始</if> <if test= status==0>未开始</if><iftest={status1}>进行中
<if test=${status
2}>已结束

if(status=0){
// 未开始…
}else if(status1){
// 进行中
} else if(status
2){
已结束
}


以上代码比较繁琐,并且可读性不是很高;


在MyBatisPlus中支持我们定义一个枚举与数据库中的字段对应起来,然后在枚举类中,使用@EnumValue注解标注真实的值(与数据库的字段对应),然后定义一个String类型的字段表示这个枚举项代表的字符串含义;


* 准备一张数据表:



DROP TABLE IF EXISTS student;
CREATE TABLE student (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT ‘姓名’,
sex varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT ‘性别 0:男 1:女’,
PRIMARY KEY (id) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;


– Records of student


INSERT INTO student VALUES (1, ‘小灰’, ‘0’);
INSERT INTO student VALUES (2, ‘小蓝’, ‘1’);


* 定义性别枚举:



package com.dfbz.enmu;

import com.baomidou.mybatisplus.annotation.EnumValue;

/**
* @author lscl
* @version 1.0
* @intro:
*/
public enum SexEnum {
MAN(0, “男”),
WOMAN(1, “女”);

// 这一列的值和数据库表映射
@EnumValue      
private Integer sexValue;

// 这个字段就是这个枚举项的字符串含义
private String sexName;

private SexEnum(Integer sexValue, String sexName) {
    this.sexValue = sexValue;
    this.sexName = sexName;
}

}


* 扫描枚举包:



#配置日志
mybatis-plus:

扫描枚举包

type-enums-package: com.dfbz.enum_


* 定义实体类:



package com.dfbz.entity;

import com.dfbz.enmu.SexEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @author lscl
* @version 1.0
* @intro:
*/

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer id;
private String name;

/\*

使用枚举类型
当从数据库中查询到了0则赋值SexEnum.MAN给sex变量
当从数据库中查询到了1则赋值SexEnum.WOMAN给sex变量
*/
private SexEnum sex;
}


* 定义Mapper接口:



/**
* @author lscl
* @version 1.0
* @intro:
*/
@Repository
public interface StudentMapper extends BaseMapper {
}


* 测试代码:



@Autowired
private StudentMapper studentMapper;

@Test
public void test4() throws Exception {
System.out.println(studentMapper.selectById(1L));
System.out.println(studentMapper.selectById(2L));
}


![95.png](https://img-blog.csdnimg.cn/img_convert/df9ee7757e38a7fd47df2849c4327514.png#averageHue=#2b313a&clientId=u7de393e7-47d7-4&from=paste&height=363&id=uf2ad15c5&name=95.png&originHeight=816&originWidth=1170&originalType=binary&ratio=2.25&rotation=0&showTitle=false&size=166422&status=done&style=none&taskId=u7aa1bd2a-2361-4624-9d63-f9ee513518e&title=&width=520)


虽然sex字段为变为了枚举类型,但是将搜索条件设置为sex=0依旧可以搜索到符合条件的记录:



@Test
public void test5() throws Exception {

QueryWrapper<Student> wrapper = Wrappers.query();
// 搜索条件为0依旧可以搜索出来
wrapper.eq("sex","0");

List<Student> stuList = studentMapper.selectList(wrapper);
for (Student student : stuList) {
    System.out.println(student);
}

}


![94.png](https://img-blog.csdnimg.cn/img_convert/097a44edf9028c2f505b63cf783c99e8.png#averageHue=#2b313a&clientId=u7de393e7-47d7-4&from=paste&height=178&id=ufa89c024&name=94.png&originHeight=401&originWidth=926&originalType=binary&ratio=2.25&rotation=0&showTitle=false&size=67969&status=done&style=none&taskId=u1ec920c3-6518-434a-a183-41e23569dde&title=&width=411.55555555555554)



#### 3.5.6 @Version



##### 1)乐观锁与悲观锁


在多个客户端(线程/进程)操作同一个资源并发生写的操作时,我们就需要保证数据的安全性了。


* 如图所示:


当我们在购买车票时,首先会进行票数的查询,例如A1在购买车票时查询到票还有100张,准备购买第100张票,与此同时,A2也查询到票数为100,也将购买第100张票;


![92.png](https://img-blog.csdnimg.cn/img_convert/074e0c858dea517bf0c8230034ffca74.png#averageHue=#f8f6f6&clientId=u7de393e7-47d7-4&from=paste&height=360&id=u5033ed4c&name=92.png&originHeight=811&originWidth=1552&originalType=binary&ratio=2.25&rotation=0&showTitle=false&size=92138&status=done&style=none&taskId=ue6469604-16be-4390-97c6-af0c5e3372d&title=&width=689.7777777777778)



> 
> Tips:**上述图中,两个窗口在买票之前分别查询了票,发现票数余额为100,然后都卖了第100张票,出现多卖情况**
> 
> 
> 


在并发修改某一资源时,我们必须保证线程安全的问题。在操作之前先加锁,这种方式就是采用悲观锁的方式;


* **悲观锁的概念**:悲观锁简单的理解就是程序处于悲观状态,在操作任何资源时都认为其他程序会来修改当前资源,自己不放心,因此在操作资源时加锁。


![91.png](https://img-blog.csdnimg.cn/img_convert/57cad027648bbf54992f3d9afacc9e79.png#averageHue=#f8f6f6&clientId=u7de393e7-47d7-4&from=paste&height=356&id=u1c6d43bf&name=91.png&originHeight=802&originWidth=1592&originalType=binary&ratio=2.25&rotation=0&showTitle=false&size=92760&status=done&style=none&taskId=u626e8ef5-ce72-4193-9ff9-362692bd0fd&title=&width=707.5555555555555)


悲观锁虽然保证了程序的安全性,同时效率也**降低**了很多,在一个客户端操作时,其他客户端均不可操作,降低了系统的并发性能。


* **乐观锁概念**:乐观锁简单理解就是程序一直处于乐观状态,在操作任何资源时认为其他程序不会来修改当前资源,整个过程不加锁,**不加锁效率是可以保证,但不是又回到了我们最初的那个状态吗?即线程安全问题**。


我们可以在每条记录中分配一个`_version`字段,每当我们对记录进行更新时,此版本号都会自增。我们可以借助该字段帮我们完成乐观锁。保证线程安全问题。


![90.png](https://img-blog.csdnimg.cn/img_convert/2b1cc810fd2950e584c258bcc7b76ff4.png#averageHue=#f4f4f4&clientId=u7de393e7-47d7-4&from=paste&height=301&id=u9255ae25&name=90.png&originHeight=677&originWidth=1704&originalType=binary&ratio=2.25&rotation=0&showTitle=false&size=105105&status=done&style=none&taskId=u73c458ca-99b9-4d2d-994b-5858f6a43ad&title=&width=757.3333333333334)


实现方式:



– 要修改数据之前,先查该数据上一次修改的时间戳
select version from table_ where id=1;

– 修改数据时,更新版本号
update table_ set goods_name=‘小苹果’, version=version+1 where version=${version};



##### 2)@Version实现乐观锁



### 给大家的福利


**零基础入门**


对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。


![](https://img-blog.csdnimg.cn/img_convert/95608e9062782d28f4f04f821405d99a.png)


同时每个成长路线对应的板块都有配套的视频提供:


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a91b9e8100834e9291cfcf1695d8cd42.png#pic_center)


因篇幅有限,仅展示部分资料


网络安全面试题


![](https://img-blog.csdnimg.cn/img_convert/80674985176a4889f7bb130756893764.png)


绿盟护网行动


![](https://img-blog.csdnimg.cn/img_convert/9f3395407120bb0e1b5bf17bb6b6c743.png)


还有大家最喜欢的黑客技术


![](https://img-blog.csdnimg.cn/img_convert/5912337446dee53639406fead3d3f03c.jpeg)


**网络安全源码合集+工具包**


![](https://img-blog.csdnimg.cn/img_convert/5072ce807750c7ec721c2501c29cb7d5.png)


![](https://img-blog.csdnimg.cn/img_convert/4a5f4281817dc4613353c120c9543810.png)

**所有资料共282G**,朋友们如果有需要全套《网络安全入门+黑客进阶学习资源包》,可以扫描下方二维码领取(如遇扫码问题,可以在评论区留言领取哦)~




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值