1.先准备MySQL的数据库表user
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
2.插入测试数据
DELETE FROM user;
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
JAVA代码准备
1.在创建时可以直接选上mysql+web模块,项目初始化完后,创建表对应的实体类User
package com.tao.mybatis.pojo;
import lombok.Data;
@Data//这个是lombok插件,需要下载插件和导入依赖
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
2.创建mapper接口
package com.tao.mybatis.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tao.mybatis.pojo.User;
public interface UserMapper extends BaseMapper<User> {
}
3.在主启动类上添加包扫描路径-mapper的包路径
package com.tao.mybatis;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan(basePackages = {"com.tao.mybatis.mapper"})
@SpringBootApplication
public class MybatisApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisApplication.class, args);
}
}
4.配置properties数据库驱动+链接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_plus?allowMultiQueries=true&serverTimezone=UTC&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
5.编写测试代码
package com.tao.mybatis;
import com.tao.mybatis.mapper.UserMapper;
import com.tao.mybatis.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class MybatisApplicationTests {
@Autowired
private UserMapper userMapper;
//查询user表所有数据
@Test
public void findAll() {
//UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper
//所以不填写就是无任何条件
List<User> users = userMapper.selectList(null);
System.out.println(users);
}
}
到这一步的代码提交版本:初始化基本代码
测试结果
注意:
IDEA在 userMapper 处报错,因为找不到注入的对象,因为类是动态创建的,但是程序可以正确的执行。
为了避免报错,可以在 dao 层 的接口上添加 @Repository 注
6.接下来基本的CURD来一套
//添加操作
@Test
public void addUser() {
User user = new User();
user.setName("111");
user.setAge(70);
user.setEmail("[email protected]");
int insert = userMapper.insert(user);
System.out.println("insert:"+insert);
}
//删除操作 物理删除
@Test
public void testDeleteById(){
int result = userMapper.deleteById(1231125349744828417L);
System.out.println(result);
}
//修改操作
@Test
public void updateUser() {
User user = new User();
user.setId(1);//数据库中的id
user.setAge(120);
int row = userMapper.updateById(user);
System.out.println(row);
}
@Test
public void testSelectByMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name", "Jone");
map.put("age", 18);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
7.添加SQL请求日志
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
或者使用调整SpringBoot的日志级别
logging:
level:
com.atguigu.gulimall: debug
显示效果(主要是开发环境查看SQL打印情况)
在插入语句的时候查看数据不会发现他的ID好像怪怪的,这是MP的主键默认生成策略导致的
1.ID_WORKER
MyBatis-Plus默认的主键策略是:ID_WORKER 全局唯一ID
参考资料:分布式系统唯一ID生成方案汇总:访问地址 也就是我们什么都不写插入一条数据MySQL中看到的ID;
@TableId(type = IdType.ID_WORKER) //mp自带策略,生成19位值,数字类型使用这种策略,比如long
@TableId(type = IdType.ID_WORKER_STR) //mp自带策略,生成19位值,字符串类型使用这种策略
2.自增策略–需要改表,主键改为自增
/**
* 数据库ID自增
*/
AUTO(0),
/**
* 该类型为未设置主键类型
*/
NONE(1),
/**
* 用户输入ID
* 该类型可以通过自己注册自动填充插件进行填充
*/
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
* 全局唯一ID (idWorker)
*/
ID_WORKER(3),
/**
* 全局唯一ID (UUID)
*/
UUID(4),
/**
* 字符串全局唯一ID (idWorker 的字符串表示)
*/
ID_WORKER_STR(5);
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。
我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:
1.数据库表中添加自动填充字段
在User表中添加datetime类型的新的字段 create_time、update_time
2.实体上添加注解
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
3.实现元对象处理器接口
package com.tao.mybatis.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @description: 自动填充
* @author TAO
* @date 2020/5/24 17:29
*/
@Component
public class MybatisPulsAutoFill implements MetaObjectHandler {
//使用mp实现添加操作,这个方法执行
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//使用mp实现修改操作,这个方法执行
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
简单介绍一下乐观锁,乐观锁本身不是问题,是解决问题产生的一种解决方案,乐观锁主要解决丢失更新的问题,这谈到丢失更新就会涉及到数据库事务, 事务就是逻辑上的操作要么都成功要么一起失败!
如果考虑事务的隔离性那么就会产生脏读,幻读/虚读,不可重复读;但这些都是读的问题,还有写的问题,丢失更新,那么乐观锁就是解决写的丢失问题,这个问题的产生是在并发写数据的时候才会产生,说白了就是多人操作同一条数据库记录。
举个例子,数据库中有一条记录,记录中有一个字段数据为100,这时A来修改这条记录,将100修改为800,这时数据还为提交,这时B也来了,也恰巧在操作这条记录,他是将100修改为200,这是A事务提交完成,B在A的后面完成事务提交,那么看似没什么问题,但是B的修改操作是直接将A的操作给覆盖掉,那也就是说A无论将值改成什么都将会被B的修改,这种现象就叫丢失更新问题,也就是A的更新数据会丢失,这个问题就可以使用乐观锁来解决;
这里补充一点:
悲观锁:A在操作数据的时候,B是不能操作这个数据的,只能排着队操作数据(串行化)效率低
乐观锁:
乐观锁的实现原理
上代码测一下效果
1.添加数据库字段
ALTER TABLE `user` ADD COLUMN `version` INT
2.用户实体类添加属性
@Version//乐观锁关键起作用的
@TableField(fill = FieldFill.INSERT)//这只是让version创建记录是有个默认版本而已
private Integer version;//版本号
3.添加自动填充insertFill中插入时version的默认数据
this.setFieldValByName("version", 1, metaObject);//这只是让version创建记录是有个默认版本而已
4.添加MyBatis-Puls乐观锁配置
package com.tao.mybatis.config;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @description: Mybatis-Push核心配置
* @author TAO
* @date 2020/5/25 14:59
*/
@Configuration
public class MyBatisPulsConfig {
//乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
5.运行测试案例中的插入方法addUser新加入一条记录AAA
6.编写测试代码
//测试 乐观锁插件
@Test
public void testOptimisticLocker() {
//查询
User user = userMapper.selectById("1264819379007172609");//这里的id是数据库里的
//修改数据
user.setName("CCC");
//执行更新
userMapper.updateById(user);
}
查看Mysql数据结果name字段修改了,version的版本自动也添加了
1.Mybatis-Push核心配置添加分页插件
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
2.测试代码添加分页请求
//分页查询
@Test
public void testPage() {
//1 创建page对象
//传入两个参数:当前页 和 每页显示记录数
Page<User> page = new Page<>(1,3);
//调用mp分页查询的方法
//调用mp分页查询过程中,底层封装
//把分页所有数据封装到page对象里面
userMapper.selectPage(page,null);
//通过page对象获取分页数据
System.out.println(page.getCurrent());//当前页
System.out.println(page.getRecords());//每页数据list集合
System.out.println(page.getSize());//每页显示记录数
System.out.println(page.getTotal()); //总记录数
System.out.println(page.getPages()); //总页数
System.out.println(page.hasNext()); //下一页
System.out.println(page.hasPrevious()); //上一页
}
3.打印结果
在车上代码中加入这个删除方法,删除一条数据
//删除操作 物理删除
@Test
public void testDeleteById(){
int result = userMapper.deleteById(1264819379007172609L);
System.out.println(result);
}
1.添加数据库删除标记字段
ALTER TABLE `user` ADD COLUMN `deleted` boolean
2.User实体类中添加标记字段
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;
3.添加自动填充insertFill中插入时deleted的删除标识
this.setFieldValByName("deleted", 0, metaObject);
4.Mybatis-Push核心配置添加逻辑删除
//逻辑删除,注意官方之初,MP3.1.1以后这段配置可以不需要配置了
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
5.properties配置文件
#删除后的值
mybatis-plus.global-config.db-config.logic-delete-value=1
#默认的值-和数据库中的删除标识值的默认值一样
mybatis-plus.global-config.db-config.logic-not-delete-value=0
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
6.再次调用删除测试代码
这里发现这条数据并没有被真实删除,不像开始的删除一样。这里是直接将删除标识改成了1,也就是我们porperties配置文件中添加的配置一样,然后我们调用查询所有记录的方法!
7.调用findAll查看效果
不难发现CCC用户确实不在了,这里发送的逻辑删除实际上不是发送的delete语句,而是update语句,然后查询的时候也会根据配置代码带上删除标识的值来查询!
1.添加插件配置
//SQL 执行性能分析插件
//开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
//dev开发 test测试 prod生产
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//ms,超过此处设置的ms则sql不执行
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
2.切换项目环境properties
#环境设置:dev、test、prod
spring.profiles.active=dev
3.启动添加测试
上面的是sql日志,下面的是sql性能分析
1、ge、gt、le、lt、isNull、isNotNull
@Test
public void testDelete() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.isNull("name")
.ge("age", 12)
.isNotNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("delete return count = " + result);
}
SQL:UPDATE user SET deleted=1 WHERE deleted=0 AND name IS NULL AND age >= ? AND email IS NOT NULL
2、eq、ne
注意:seletOne返回的是一条实体记录,当出现多条时会报错
@Test
public void testSelectOne() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "Tom");
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}
SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name = ?
3、between、notBetween–包含大小边界
@Test
public void testSelectCount() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.between("age", 20, 30);
Integer count = userMapper.selectCount(queryWrapper);
System.out.println(count);
}
SELECT COUNT(1) FROM user WHERE deleted=0 AND age BETWEEN ? AND ?
4、allEq
@Test
public void testSelectList() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("id", 2);
map.put("name", "Jack");
map.put("age", 20);
queryWrapper.allEq(map);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 AND name = ? AND id = ? AND age = ?
5、like、notLike、likeLeft、likeRight
selectMaps返回Map集合列表
@Test
public void testSelectMaps() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.notLike("name", "e")
.likeRight("email", "t");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);//返回值是Map列表
maps.forEach(System.out::println);
}
SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 AND name NOT LIKE ? AND email LIKE ?
6、in、notIn、inSql、notinSql、exists、notExists
in、notIn:
@Test
public void testSelectObjs() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//queryWrapper.in("id", 1, 2, 3);
queryWrapper.inSql("id", "select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(queryWrapper);//返回值是Object列表
objects.forEach(System.out::println);
}
SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 AND id IN (select id from user where id < 3)
7、or、and
注意:这里使用的是 UpdateWrapper
不调用or则默认为使用 and 连
@Test
public void testUpdate1() {
//修改值
User user = new User();
user.setAge(99);
user.setName("Andy");
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
.or()
.between("age", 20, 30);
int result = userMapper.update(user, userUpdateWrapper);
System.out.println(result);
}
UPDATE user SET name=?, age=?, update_time=? WHERE deleted=0 AND name LIKE ? OR age BETWEEN ? AND ?
8、嵌套or、嵌套and
这里使用了lambda表达式,or中的表达式最后翻译成sql时会被加上圆括号
@Test
public void testUpdate2() {
//修改值
User user = new User();
user.setAge(99);
user.setName("Andy");
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
.or(i -> i.eq("name", "李白").ne("age", 20));
int result = userMapper.update(user, userUpdateWrapper);
System.out.println(result);
}
UPDATE user SET name=?, age=?, update_time=?
WHERE deleted=0 AND name LIKE ?
OR ( name = ? AND age <> ? )
9、orderBy、orderByDesc、orderByAsc
@Test
public void testSelectListOrderBy() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 ORDER BY id DESC
10、last
直接拼接到 sql 的最后
注意:只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用
@Test
public void testSelectListLast() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.last("limit 1");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 limit 1
11、指定要查询的列
@Test
public void testSelectListColumn() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "name", "age");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
SELECT id,name,age FROM user WHERE deleted=0
12、set、setSql
最终的sql会合并 user.setAge(),以及 userUpdateWrapper.set() 和 setSql() 中 的字段
@Test
public void testUpdateSet() {
//修改值
User user = new User();
user.setAge(99);
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
.set("name", "老李头")//除了可以查询还可以使用set设置修改的字段
.setSql(" email = '[email protected]'");//可以有子查询
int result = userMapper.update(user, userUpdateWrapper);
}
UPDATE user SET age=?, update_time=?, name=?, email = '[email protected]' WHERE deleted=0 AND name LIKE ?
至于一些其他复杂的高级SQL语句,建议使用mapper映射文件的方式来写;