1️⃣ 先在表中增加一列字段,表示是否删除的状态,这里我们使用的字段类型为int类型【通过1表示该条数据可用,0表示该条数据不可用】
2️⃣ 实体类添加一个字段为Integer,用于对应表中的字段
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User extends Model<User> {
private Long id;
private String name;
private Integer age;
private String email;
@TableLogic(value = "1",delval = "0")
private Integer status;
}
3️⃣ 测试逻辑删除效果
@Test
void logicDelete(){
userMapper.deleteById(7L);
}
1️⃣ 我们先在表中添加一个字段,表示性别,这里我们一般使用int来描述,因为int类型可以通过0和1这两个值来表示两个不同的性别
2️⃣ 编写枚举类
public enum GenderEnum {
MAN(0,"男"),
WOMAN(1,"女");
private Integer gender;
private String genderName;
GenderEnum(Integer gender, String genderName) {
this.gender = gender;
this.genderName = genderName;
}
}
3️⃣ 实体类添加相关字段
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User extends Model<User> {
private Long id;
private String name;
private Integer age;
private String email;
private GenderEnum gender;
private Integer status;
}
4️⃣ 添加数据
@Test
void enumTest(){
User user = new User();
user.setName("liu");
user.setAge(29);
user.setEmail("[email protected]");
user.setGenderEnum(GenderEnum.MAN);
user.setStatus(1);
userMapper.insert(user);
}
public enum GenderEnum {
MAN(0,"男"),
WOMAN(1,"女");
@EnumValue
private Integer gender;
private String genderName;
GenderEnum(Integer gender, String genderName) {
this.gender = gender;
this.genderName = genderName;
}
}
此时我们再次执行添加操作,发现可以成功添加数据,而枚举类型的值也作为数据被插入到数据库中
在某些场景下,我们在实体类中是使用Map集合作为属性接收前端传递过来的数据的,但是这些数据存储在数据库时,我们使用的是json格式的数据进行存储,json本质是一个字符串,就是varchar类型
那怎么做到实体类的Map类型和数据库的varchar类型的互相转换?
在实体中是一个map,但是需要转为json的形式存储的,也就是string类型
1️⃣ 我们先在实体类中添加一个字段,Map 类型
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User extends Model<User> {
private Long id;
private String name;
private Integer age;
private String email;
private GenderEnum gender;
private Integer status;
private Map<String,String> contact;//联系方式
}
2️⃣ 在数据库中我们添加一个字段,为varchar类型
3️⃣ 为实体类添加上对应的注解,实现使用字段类型处理器进行不同类型数据转换
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(autoResultMap = true)//查询时将json字符串封装为Map集合
public class User extends Model<User> {
private Long id;
private String name;
private Integer age;
private String email;
private GenderEnum gender;
private Integer status;
@TableField(typeHandler = FastjsonTypeHandler.class)//指定字段类型处理器
private Map<String,String> contact;//联系方式
}
4️⃣ 字段类型处理器依赖Fastjson这个Json处理器,所以我们需要引入对应的依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
5️⃣ 测试添加操作
@Test
void typeHandler(){
User user = new User();
user.setName("zhang");
user.setAge(28);
user.setEmail("[email protected]");
user.setGender(GenderEnum.MAN);
user.setStatus(1);
HashMap<String, String> contact = new HashMap<>();
contact.put("phone","010-1234567");
contact.put("tel","13388889999");
user.setContact(contact);
userMapper.insert(user);
}
6️⃣ 测试查询操作,通过结果发现,从数据库中查询出来的数据,已经被转到Map集合
@Test
void typeHandlerSelect(){
User user = userMapper.selectById(1646422830251880449L);
System.out.println(user);
}
在项目中有一些属性,如果我们不希望每次都填充的话,我们可以设置为自动填充,比如常见的时间,创建时间和更新时间可以设置为自动填充
1️⃣ 在数据库的表中添加两个字段
注意只有设置了下划线和小驼峰映射,这种mysql的写法才能和实体类完成映射
2️⃣ 在实体类中,添加对应字段,并为需要自动填充的属性指定填充时机
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(autoResultMap = true)
public class User extends Model<User> {
@TableId
private Long id;
private String name;
private Integer age;
private String email;
private Integer status;
private GenderEnum gender;
@TableField(typeHandler = FastjsonTypeHandler.class)
private Map<String,String> contact;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
3️⃣ 编写自动填充处理器,指定填充策略
package com.powernode.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @author Bonbons
* @version 1.0
*/
@Component
public class MyMetaHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
setFieldValByName("createTime", new Date(), metaObject);
setFieldValByName("updateTime", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
setFieldValByName("updateTime", new Date(), metaObject);
}
}
4️⃣ 这里在插入前先设置一下mysql时区,并查看目前时区时间正常
5️⃣ 再将配置文件的时区修改为 serverTimezone=Asia/Shanghai
6️⃣ 测试插入操作
@Test
void testFillInsert(){
User user = new User();
user.setName("华佗");
user.setAge(22);
user.setEmail("[email protected]");
user.setStatus(1);
user.setGender(GenderEnum.MAN);
Map<String, String> map = new HashMap<>();
map.put("tel", "12366668888");
map.put("phone", "010-1234567");
user.setContact(map);
userMapper.insert(user);
}
7️⃣ 测试更新操作
@Test
void testFillUpdate(){
User user = new User();
user.setId(1646430159940669442L);
user.setAge(88);
userMapper.updateById(user);
}
先演示一下全表更新的场景
@Test
public void testUpdateAll(){
User user = new User();
user.setGender(GenderEnum.MAN);
userService.saveOrUpdate(user,null);
}
package com.powernode.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Bonbons
* @version 1.0
*/
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//全表更新拦截器
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
//实现乐观锁控制的拦截器
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
测试全表更新,会出现抛出异常,防止了全表更新
package com.powernode;
import com.powernode.domain1.User;
import com.powernode.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author Bonbons
* @version 1.0
*/
@SpringBootTest
public class UpdateAllTest {
@Autowired
private UserService userService;
@Test
void updateAll(){
User user = new User();
user.setId(999L);
user.setName("wang");
user.setEmail("[email protected]");
userService.saveOrUpdate(user,null);
}
}
1️⃣ 首先选择File -> Settings, 选择Plugins,搜索MybatisX,点击安装,这种效果就是安装完毕了
2️⃣ 插件安装好以后,我们来看一下插件的功能
(2)逆向工程
引入依赖,和编写对应的配置文件信息
<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.7.10version>
<relativePath/>
parent>
<groupId>com.powernodegroupId>
<artifactId>mp03artifactId>
<version>0.0.1-SNAPSHOTversion>
<name>mp03name>
<description>mp03description>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.31version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.8version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>p6spygroupId>
<artifactId>p6spyartifactId>
<version>3.9.1version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>dynamic-datasource-spring-boot-starterartifactId>
<version>3.1.0version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
1、首先使用IDEA连接mysql,填写连接信息,测试连接通过
3、编写逆向工程配置信息
4、编写生成信息
5、观察生成结构,发现下面这些内容都已产生
(1)实体类
(2)Mapper接口
(3)Mapper映射文件
(4)Service接口
(5)Service映射文件
6、接下来我们在Mapper接口上添加@Mapper注解
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
测试代码环境
(3)常见需求代码生成
@Mapper
public interface UserMapper extends BaseMapper<User> {
//添加操作
int insertSelective(User user);
//删除操作
int deleteByNameAndAge(@Param("name") String name, @Param("age") Integer age);
//修改操作
int updateNameByAge(@Param("name") String name, @Param("age") Integer age);
//查询操作
List<User> selectAllByAgeBetween(@Param("beginAge") Integer beginAge, @Param("endAge") Integer endAge);
}
在映射配置文件中,会生成对应的sql,并不需要我们编写
<insert id="insertSelective">
insert into powershop_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,if>
<if test="name != null">name,if>
<if test="age != null">age,if>
<if test="email != null">email,if>
trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">#{id,jdbcType=BIGINT},if>
<if test="name != null">#{name,jdbcType=VARCHAR},if>
<if test="age != null">#{age,jdbcType=INTEGER},if>
<if test="email != null">#{email,jdbcType=VARCHAR},if>
trim>
insert>
<delete id="deleteByNameAndAge">
delete
from powershop_user
where name = #{name,jdbcType=VARCHAR}
AND age = #{age,jdbcType=NUMERIC}
delete>
<update id="updateNameByAge">
update powershop_user
set name = #{name,jdbcType=VARCHAR}
where age = #{age,jdbcType=NUMERIC}
update>
<select id="selectAllByAgeBetween" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from powershop_user
where
age between #{beginAge,jdbcType=INTEGER} and #{endAge,jdbcType=INTEGER}
select>
首先我们先要了解开发中的一个常见场景,叫做并发请求。并发请求就是在同一时刻有多个请求同时请求服务器资源,如果是获取信息,没什么问题,但是如果是对于信息做修改操作呢,那就会出现问题。
这里举一个具体的例子,比如目前商品的库存只剩余1件了,这个时候有多个用户都想要购买这件商品,都发起了购买商品的请求,那么能让这多个用户都购买到么,肯定是不行的,因为多个用户都买到了这件商品,那么就会出现超卖问题,库存不够是没法发货的。所以在开发中就要解决这种超卖的问题
抛开超卖这一种场景,诸如此类并发访问的场景非常多,这类场景的核心问题就是,一个请求在执行的过程中,其他请求不能改变数据,如果是一次完整的请求,在该请求的过程中其他请求没有对于这个数据产生修改操作,那么这个请求是能够正常修改数据的。如果该请求在改变数据的过程中,已经有其他请求改变了数据,那该请求就不去改变这条数据了。
想要解决这类问题,最常见的就是加锁的思想,锁可以用验证在请求的执行过程中,是否有数据发生改变。
常见的数据库锁类型有两种,悲观锁和乐观锁。
一次完成的修改操作是,先查询数据,然后修改数据。
这样做的操作能够保证读取到的信息就是当前的信息,保证了信息的正确性,但是并发效率很低,在实际开发中使用悲观锁的场景很少,因为在并发时我们是要保证效率的。
具体的通过sql是这样实现的
Update 表 set 字段 = 新值,version = version + 1 where version = 1
这样做的操作是不会对于数据读取产生影响,并发的效率较高。但是可能目前看到的数据并不是真实信息数据,是被修改之前的,但是在很多场景下是可以容忍的,并不是产生很大影响,例如很多时候我们看到的是有库存,或者都加入到购物车了,但是点进去以后库存没有了。
接下来我们来看一下乐观锁的使用
1️⃣ 在数据库表中添加一个字段version,表示版本,默认值是1
2️⃣ 找到实体类,添加对应的属性,并使用**@Version**标注为这是一个乐观锁字段信息
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
@Version
private Integer version;
}
3️⃣ 因为要对每条修改语句完成语句的增强,这里我们通过拦截器的配置,让每条修改的sql语句在执行的时候,都加上版本控制的功能
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
4️⃣ 测试效果,这里我们模拟先查询,再修改
@Test
void updateTest(){
User user = userMapper.selectById(6L);
user.setName("li");
userMapper.updateById(user);
}
我们通过查看拼接好的SQL语句发现,查询时将User的数据查询出来,是包含version版本信息的
当我们完成修改时,他会将版本号 + 1
接下来我们模拟一下,当出现多个修改请求的时候,是否能够做到乐观锁的效果。
乐观锁的效果是,一个请求在修改的过程中,是允许另一个请求查询的,但是修改时会通过版本号是否改变来决定是否修改,如果版本号变了,证明已经有请求修改过数据了,那这次修改不生效,如果版本号没有发生变化,那就完成修改。
@Test
void updateTest2(){
//模拟操作1的查询操作
User user1 = userMapper.selectById(6L);
//模拟操作2的查询操作
User user2 = userMapper.selectById(6L);
//模拟操作2的修改操作
user2.setName("lisi");
userMapper.updateById(user2);
//模拟操作1的修改操作
user1.setName("zhangsan");
userMapper.updateById(user1);
}
我们来看下这段代码的执行过程,这段代码其实是两次操作,只不过操作1在执行的过程中,有操作2完成了对于数据的修改,这时操作1就无法再次进行修改了
操作1的查询:此时版本为2
操作2的修改:此时检查版本,版本没有变化,所以完成修改,并将版本改为3
操作1的修改:此时检查版本,版本已经有最初获取的版本信息发生了变化,所以杜绝修改
1️⃣ 参考官网,使用代码生成器需要引入两个依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.5.3version>
dependency>
<dependency>
<groupId>org.freemarkergroupId>
<artifactId>freemarkerartifactId>
<version>2.3.31version>
dependency>
2️⃣ 编写代码生成器代码
package com.powernode;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Collections;
/**
* @author Bonbons
* @version 1.0
*/
@SpringBootTest
public class GenerationApplicationTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false", "root", "111111")
.globalConfig(builder -> {
builder.author("powernode") // 设置作者
//.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.powernode") // 设置父包名
.moduleName("mybatisplus") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
// builder.addInclude("powershop_user") // 设置需要生成的表名
// .addTablePrefix("powershop"); // 设置过滤表前缀
builder.addInclude("user");
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
1️⃣ 由于该功能依赖于p6spy组件,所以需要在pom.xml中先引入该组件
<dependency>
<groupId>p6spygroupId>
<artifactId>p6spyartifactId>
<version>3.9.1version>
dependency>
2️⃣ 在application.yml中进行配置
将驱动和url修改
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql
3️⃣ 在resources下,创建 spy.properties配置文件
#3.2.1以上使用modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
4️⃣ 测试
执行查询所有的操作,可以看到sql语句的执行时间
在学习多数据源之前,我们先来了解一下分库分表。当一个项目的数据库的数据十分庞大时,在完成SQL操作的时候,需要检索的数据就会更多,我们会遇到性能问题,会出现SQL执行效率低的问题。
针对这个问题,我们的解决方案是,将一个数据库中的数据,拆分到多个数据库中,从而减少单个数据库的数据量,从分摊访问请求的压力和减少单个数据库数据量这两个方面,都提升了效率。
我们来演示一下,在MybatisPlus中,如何演示数据源切换的效果
1️⃣ 先创建一个新的模块,将之前模块中的内容复制过来,结构如下
2️⃣ 引入依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>dynamic-datasource-spring-boot-starterartifactId>
<version>3.1.0version>
dependency>
3️⃣ 创建新的数据库,提供多数据源环境
数据库
4️⃣ 编写配置文件,指定多数据源信息
spring:
datasource:
dynamic:
primary: master
strict: false
datasource:
master:
username: root
password: 111111
url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
slave_1:
username: root
password: 111111
url: jdbc:mysql://localhost:3306/mybatisplus2?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
5️⃣ 创建多个Service,分别使用@DS注解描述不同的数据源信息
@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
@Service
@DS("slave_1")
public class UserServiceImpl2 extends ServiceImpl<UserMapper, User> implements UserService{
}
6️⃣ 测试service多数据源环境执行结果
package com.powernode;
import com.powernode.domain.User;
import com.powernode.service.impl.UserServiceImpl;
import com.powernode.service.impl.UserServiceImpl2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author Bonbons
* @version 1.0
*/
@SpringBootTest
public class ManyDBTest {
@Autowired
private UserServiceImpl userService1;
@Autowired
private UserServiceImpl2 userService2;
@Test
void test1(){
User byId = userService1.getById(3L);
System.out.println(byId);
}
@Test
void test2(){
User byId = userService2.getById(1L);
System.out.println(byId);
}
}
7️⃣ 观察测试结果,发现结果可以从两个数据源中获取