MyBatis-Plus
(简称MP
)是一个MyBatis
的增强工具,在MyBatis
的基础上只做增强不做改变,为简化开发、提高效率而生。
MyBatisPlus
官网:https://baomidou.com/
CURD
,性能基本无损耗,直接面向对象操作CRUD
操作:内置通用Mapper
、通用Service
,仅仅通过少量配置即可实现单表大部分CRUD
操作,更有强大的条件构造器,满足各类使用需求Lambda
形式调用:通过Lambda
表达式,方便的编写各类查询条件,无需再担心字段写错4
种主键策略(内含分布式唯一ID
生成器-Sequence),可自由配置,完美解决主键问题ActiveRecord
模式:支持ActiveRecord
形式调用,实体类只需继承Model
类即可进行强大的CRUD
操作Maven
插件可快速生成Mapper
、Model
、Service
、Controller
层代码,支持模板引擎,更有超多自定义配置等您来使用MyBatis
物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List
查询MySQL
、MariaDB
、Oracle
、DB2
、H2
、HSQL
、SQLite
、Postgre
、SQLServer
等多种数据库SQL
语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询delete
、update
操作智能分析阻断,也可自定义拦截规则,预防误操作任何能使用MyBatis
进行CRUD
,并且支持标准SQL
的数据库,具体支持情况如下,如果不在下列表查看分页部分教程PR
您的支持。
MySQL
,Oracle
,DB2
,H2
,HSQL
,SQLite
,PostgreSQL
,SQLServer
,Phoenix
,Gauss
,ClickHouse
,Sybase
,OceanBase
,Firebird
,Cubrid
,Goldilocks
,csiidb
创建测试表
DROP TABLE IF EXISTS user;
CREATE TABLE `user`
(
`id` bigint(20) NOT NULL COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`sex` char(1) DEFAULT NULL COMMENT '性别 0:男 1:女',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`birthday` date DEFAULT NULL COMMENT '生日',
PRIMARY KEY (`id`)
);
INSERT INTO `user` VALUES (1, 'Jone', '1', 27, '2001-10-04');
INSERT INTO `user` VALUES (2, 'Jack', '0', 20, '1999-10-04');
INSERT INTO `user` VALUES (3, 'Tom', '1', 28, '1996-08-12');
INSERT INTO `user` VALUES (4, 'Sandy', '1', 21, '2001-10-04');
INSERT INTO `user` VALUES (5, 'Billie', '0', 24, '1992-09-07');
INSERT INTO `user` VALUES (6, 'Jackson', '0', 18, '1996-08-12');
INSERT INTO `user` VALUES (7, 'Hardy', '1', 25, '1992-09-07');
INSERT INTO `user` VALUES (8, 'Rose', '1', 21, '1992-09-07');
INSERT INTO `user` VALUES (9, 'June', '0', 28, '1992-09-07');
INSERT INTO `user` VALUES (10, 'Aidan', '0', 17, '2001-10-04');
引入依赖
4.0.0
com.test
01_MyBatisPlus
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.6.3
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
com.baomidou
mybatis-plus-boot-starter
3.4.2
junit
junit
4.13
test
mysql
mysql-connector-java
org.projectlombok
lombok
实体类
package com.test.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String sex;
private Integer age;
private String birthday;
}
Mapper接口
package com.test.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.test.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper {
}
application.yml
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=GMT%2b8
username: root
password: admin
#配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
启动类
package com.test;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.test.mapper")
public class MyBatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MyBatisPlusApplication.class, args);
}
}
测试类
package com.test;
import com.test.entity.User;
import com.test.mapper.UserMapper;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import javax.annotation.Resource;
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo01 {
@Resource
private UserMapper userMapper;
@Test
public void testSelect() {
// 传递null代表查询全部
List userList = userMapper.selectList(null);
for (User user : userList) {
System.out.println(user);
}
}
}
在MyBatisPlus
中,我们编写的Mapper
接口都继承与MyBatisPlus
提供的BaseMapper
接口,BaseMapper
接口包含了MyBatisPlus
帮我们提供操作数据库的一系列方法;
官网案例:https://baomidou.com/pages/49cc81/#mapper-crud-接口
使用示例:
package com.test;
import com.test.entity.User;
import com.test.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import javax.annotation.Resource;
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo01_BaseMapper {
@Resource
private UserMapper userMapper;
/**
* 新增
* INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
*/
@Test
public void insert() {
User user = new User(100L, "Ken", "0", 20);
userMapper.insert(user);
}
/**
* 修改
* UPDATE user SET name=?, age=?, email=? WHERE id=?
*/
@Test
public void update() {
User user = new User(100L, "Kevin", "0", 25);
userMapper.updateById(user);
}
/**
* 根据id查询
* SELECT id,name,age,email FROM user WHERE id=?
*/
@Test
public void selectById() {
User user = userMapper.selectById(100L);
System.out.println(user);
}
/**
* 根据一批id查询
* SELECT id,name,age,email FROM user WHERE id IN ( ?, ?, ? )
*/
@Test
public void selectBatchIds() {
List userList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
for (User user : userList) {
System.out.println(user);
}
}
/**
* 根据条件查询数据
* SELECT id,name,age,email FROM user WHERE name = ? AND id = ?
*/
@Test
public void selectByMap() {
// 封装条件
HashMap param = new HashMap();
param.put("id", 10L);
param.put("name", "Kevin");
// 根据条件查询,map中的多个条件是并列关系 id=10 and name='小灰灰'
List userList = userMapper.selectByMap(param);
for (User user : userList) {
System.out.println(user);
}
}
/**
* 根据id删除
* DELETE FROM user WHERE id=?
*/
@Test
public void deleteById() {
userMapper.deleteById(100L);
}
/**
* 根据id删除一批
* DELETE FROM user WHERE id IN ( ?, ?, ? )
*/
@Test
public void deleteBatchIds() {
userMapper.deleteBatchIds(Arrays.asList(1, 2, 3));
}
/**
* 根据条件删除数据
* DELETE FROM user WHERE name = ? AND id = ?
*/
@Test
public void deleteByMap() {
// 封装条件
HashMap param = new HashMap();
param.put("id", 100L);
param.put("name", "Kevin");
userMapper.deleteByMap(param);
}
}
通过BaseMapper
提供的一些方法我们可以完成一些基本的CRUD
,但无法完成复杂条件的查询;对于复杂条件的查询,MyBatisPlus
提供了Wrapper
接口来处理;
Wrapper
是MyBatisPlus
提供的一个条件构造器,主要用于构建一系列条件,当Wrapper
构建完成后,可以使用Wrapper
中的条件进行查询、修改、删除等操作;
Wrapper
的继承体系如下:
Wrapper
是条件构造抽象类,最顶端父类,其主要实现类有如下:
AbstractWrapper
是其他常用Wrapper
的父类,用于生成sql
的where
条件
AbstractWrapper
提供了很多公有的方法,其子类全部具备这些方法,方法列表如下:
方法名 | 解释 | 示例 |
---|---|---|
eq | 等于 = | eq(“name”, “老王”)---> name = ‘老王’ |
ne | 不等于 | ne(“name”, “老王”)---> name ‘老王’ |
gt | 大于 > | gt(“age”, 18)---> age > 18 |
ge | 大于等于 >= | ge(“age”, 18)---> age >= 18 |
lt | 小于 | lt(“age”, 18)---> age |
le | 小于等于 | le(“age”, 18)---> age |
between | between 值1 and 值2 | between(“age”, 18, 30)---> age between 18 and 30 |
notBetween | not between 值1 and 值2 | notBetween(“age”, 18, 30)---> age not between 18 and 30 |
like | LIKE ‘%值%’ | like(“name”, “王”)---> name like ‘%王%’ |
notLike | NOT LIKE ‘%值%’ | notLike(“name”, “王”)---> name not like ‘%王%’ |
likeLeft | LIKE ‘%值’ | likeLeft(“name”, “王”)---> name like ‘%王’ |
likeRight | LIKE ‘值%’ | likeRight(“name”, “王”)---> name like ‘王%’ |
isNull | 字段 IS NULL | isNull(“name”)---> name is null |
isNotNull | 字段 IS NOT NULL | isNotNull(“name”)---> name is not null |
in | 字段 IN (v0, v1, …) | in(“age”, 1, 2, 3)---> age in (1,2,3) |
notIn | 字段 NOT IN (v0, v1, …) | notIn(“age”, 1, 2, 3)---> age not in (1,2,3) |
inSql | 字段 IN ( sql语句 ) | inSql(“id”, “select id from table where id ---> id in (select id from table where id |
notInSql | 字段 NOT IN ( sql语句 ) | notInSql(“id”, “select id from table where id ---> id not in (select id from table where id |
groupBy | 分组:GROUP BY 字段, … | groupBy(“id”, “name”)---> group by id,name |
orderByAsc | 排序:ORDER BY 字段, … ASC | orderByAsc(“id”, “name”)---> order by id ASC,name ASC |
orderByDesc | 排序:ORDER BY 字段, … DESC | orderByDesc(“id”, “name”)---> order by id DESC,name DESC |
orderBy | 排序:ORDER BY 字段, … | orderBy(true, true, “id”, “name”)---> order by id ASC,name ASC |
having | HAVING ( sql语句 ) | 例1:having(“sum(age) > 10”)---> having sum(age) > 10 例2:having(“sum(age) > {0}”, 11)---> having sum(age) > 11 |
func | 主要解决条件拼接 | func(i -> if(true) {i.eq(“id”, 1)} else {i.ne(“id”, 1)}) |
or | 拼接 OR | eq(“id”,1).or().eq(“name”,“老王”)---> id = 1 or name = ‘老王’ |
and | AND 嵌套 | and(i -> i.eq(“name”, “李白”).ne(“status”, “活着”))---> and (name = ‘李白’ and status ‘活着’) |
nested | 用于多条件拼接时 | nested(i -> i.eq(“name”, “李白”).ne(“status”, “活着”))---> (name = ‘李白’ and status ‘活着’) |
apply | 用于拼接SQL语句 | 例1:apply(“id = 1”)---> id = 1 例2:apply(“id = {0}”,1)---> id = 1 例3:apply(“name like {0} and age > {1}”,“%J%”,18) ---> name like ‘%J%’ and age > 18 |
last | 无视优化规则直接拼接到 sql 的最后 | last(“limit 1”) ---> 在SQL语句最后面拼接:limit 1 |
exists | 拼接 EXISTS ( sql语句 ) | exists(“select id from table where age = 1”)---> exists (select id from table where age = 1) |
notExists | 拼接 NOT EXISTS ( sql语句 ) | notExists(“select id from table where age = 1”)---> not exists (select id from table where age = 1) |
创建Wrapper对象:
代码示例:
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo02_Wrapper {
@Resource
private UserMapper userMapper;
/**
* QueryWrapper的创建
* SELECT id,name,age,email FROM user
*/
@Test
public void test1() {
// 创建QueryWrapper,默认情况下查询所有数据
QueryWrapper wrapper = Wrappers.query();
QueryWrapper wrapper2 = new QueryWrapper();
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
}
官网案例:https://baomidou.com/pages/10c804/#abstractwrapper
使用示例:
@Test
public void test2() {
QueryWrapper wrapper = Wrappers.query();
// name ='Jack'
// wrapper.eq("name","Jack");
//参数1: 是否要进行name条件的拼接
String name = "Jack";
wrapper.eq(StringUtils.isNotBlank(name), "name", name);
// name != 'Jack'
// wrapper.ne("name","Jack");
// age > 20
// wrapper.gt("age",20);
// age users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
示例代码:
@Test
public void test3() {
// 创建wrapper对象
QueryWrapper wrapper = Wrappers.query();
// 相当于: name in (select name from user where age > 21)
wrapper.inSql("name", "select name from user where age>21");
// 相当于: name not in (select name from user where age > 21)
// wrapper.notInSql("name","select name from user where age>21");
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
1)分组:
通过Wrapper.query()
构建的查询字段默认是表中的所有字段,因此在这种情况下分组是没有意义的,分组具体的用法我们后面再详细介绍;
@Test
public void test4() {
// 创建wrapper对象
QueryWrapper wrapper = Wrappers.query();
// 相当于 select * from user where group sex
// 这是一个没有意义的分组,分组必须结合查询的字段来体现,后续学QueryWrapper的select方法再介绍
wrapper.groupBy("sex");
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
2)having操作
@Test
public void test5() {
// 创建wrapper对象
QueryWrapper wrapper = Wrappers.query();
// group by sex having sex = 0
wrapper.groupBy("sex");
wrapper.having("sex", "0");
List 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 users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
Wrapper
对象在调用每一个方法时都会返回当前对象(Wrapper
),这样可以很好的方便我们链式编程;另外MyBatisPlus
在拼接多个条件时默认使用and
拼接,如果需要使用or
,那么需要显示的调用or()
方法;
1)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 users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
2)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 users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
1)and方法:用于拼接一个其他的整体条件;示例如下:
@Test
public void test1() {
QueryWrapper wrapper = Wrappers.query();
// 生成的SQL为: (age >() {
// @Override
// public void accept(QueryWrapper userQueryWrapper) {
// userQueryWrapper.eq("sex", 0)
// .or()
// .like("name", "J");
// }
// });
// 生成的SQL为: (age users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
2)func方法:用于多条件的拼接,直接使用之前的方法也可以做到这个功能;示例如下:
@Test
public void test2() {
QueryWrapper wrapper = Wrappers.query();
// 生成的SQL语句条件: (age >() {
@Override
public void accept(QueryWrapper userQueryWrapper) {
userQueryWrapper.like("name", "a");
userQueryWrapper.eq("sex", "0");
}
});
// 等价于:
// wrapper.lt("age", 20)
// .like("name", "a")
// .eq("sex", "0");
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
3)nested方法:功能等价于and方法
@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>() {
@Override
public void accept(QueryWrapper userQueryWrapper) {
// 默认情况下是用and来拼接多个条件
userQueryWrapper
.like("name", "a")
.or()
.gt("age", 20);
}
});
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
4)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 users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
5)last方法:无视优化规则直接拼接到 sql 的最后,只能调用一次,多次调用以最后一次为准 有sql注入的风险
Tips:apply方法可以防止SQL注入,但last方法不能;
@Test
public void test5() {
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 users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
6)exists方法:用于exists语句
@Test
public void test6() {
QueryWrapper wrapper = Wrappers.query();
// SQL: (EXISTS (select 1))
wrapper.exists("select 1");
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
7)notExists方法:
@Test
public void test7() {
QueryWrapper wrapper = Wrappers.query();
// SQL: (NOT EXISTS (select 1))
wrapper.notExists("select 1");
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
QueryWrapper
是AbstractWrapper
的子类,主要用于查询指定字段,方法列表如下:
方法名 | 解释 | 示例 |
---|---|---|
select(String… sqlSelect) | 设置查询字段 | 例1:select(“id”, “name”, “age”) 例2:select(i -> i.getProperty().startsWith(“test”)) |
示例代码:
package com.test;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.test.entity.User;
import com.test.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.List;
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo04_QueryWrapper {
@Resource
private UserMapper userMapper;
// 选择查询的字段
@Test
public void test1() throws Exception {
QueryWrapper 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 userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
@Test
public void test2() throws Exception {
User user = new User();
user.setId(1L);
// user当做查询的条件
QueryWrapper wrapper = Wrappers.query(user);
// 指定查询的列
wrapper.select("id", "name", "sex");
// SQL: SELECT id,name,sex FROM user WHERE id=?
List userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
}
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.test;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.test.entity.User;
import com.test.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.List;
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo05_UpdateWrapper {
@Resource
private UserMapper userMapper;
@Test
public void test1() throws Exception {
UpdateWrapper wrapper = Wrappers.update();
// UpdateWrapper也是AbstractWrapper的子类,因此也具备一些基本的查询方法
wrapper.like("name", "J");
List 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 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);
}
}
LambdaQueryWrapper
是QueryWrapper
的子类,具备QueryWrapper
的所有方法,QueryWrapper
的方法上提供了一系列有关于方法引的操作;
使用示例:
package com.test;
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.test.entity.User;
import com.test.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import javax.annotation.Resource;
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo06_LambdaQueryWrapper {
@Resource
private UserMapper userMapper;
/**
* 使用QueryWrapper
* @throws Exception
*/
@Test
public void test1() throws Exception {
QueryWrapper wrapper = Wrappers.query();
wrapper.eq("id", "1");
List userList = userMapper.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
/**
* 使用LambdaQueryWrapper
* @throws Exception
*/
@Test
public void test2() throws Exception {
LambdaQueryWrapper 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 userList = userMapper.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
}
LambdaUpdateMapper
同样是UpdateMapper
的子类,具备UpdateMapper
的所有方法,UpdateMapper
的方法上提供了一系列有关于方法引的操作;
示例代码:
package com.test;
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.test.entity.User;
import com.test.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo07_LambdaUpdateWrapper {
@Resource
private UserMapper userMapper;
/**
* 使用UpdateWrapper
*
* @throws Exception
*/
@Test
public void test1() throws Exception {
UpdateWrapper 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 wrapper = Wrappers.lambdaUpdate();
wrapper.eq(User::getId, "1");
wrapper.set(User::getName, "xiaolan");
wrapper.set(User::getAge, 18);
userMapper.update(null, wrapper);
}
}
在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.test.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;
@Configuration
@MapperScan("com.test.mapper") // mapper接口的所在位置
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
在BaseMapper
中主要提供有如下方法来完成分页查询:
1)无条件分页查询:
package com.test;
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.test.entity.User;
import com.test.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import javax.annotation.Resource;
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo06_BaseMapper {
@Resource
private UserMapper userMapper;
/**
* 无条件分页查询
* @throws Exception
*/
@Test
public void test1() throws Exception {
// 封装分页信息
Page page = new Page(1, 3);
/*
执行分页查询,并将结果封装到page中
参数1: 分页配置
参数2: 查询条件
*/
userMapper.selectPage(page, null);
// 当前页数据
List 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:
Preparing: SELECT id,name,sex,age FROM user LIMIT ?
==> Parameters: 3(Long)
带条件分页查询:
/**
* 带条件分页查询
*
* @throws Exception
*/
@Test
public void test2() throws Exception {
QueryWrapper wrapper = Wrappers.query();
wrapper.like("name", "a");
// 封装分页信息
Page page = new Page(1, 3);
/*
执行分页查询,并将结果封装到page中
参数1: 分页配置
参数2: 查询条件
*/
userMapper.selectPage(page, wrapper);
// 当前页数据
List 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)
Preparing: SELECT id,name,sex,age FROM user WHERE (name LIKE ?) LIMIT ?
==> Parameters: %a%(String), 3(Long)
3)将分页数据的查询结果以Map类型返回
/**
* 查询结果以Map返回
*
* @throws Exception
*/
@Test
public void test3() throws Exception {
// 封装分页信息
Page page = new Page(1, 3);
userMapper.selectMapsPage(page, null);
// 每一条记录都是一个HashMap
List> 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:
Preparing: SELECT id,name,sex,age FROM user LIMIT ?
==> Parameters: 3(Long)
通用Service CRUD
封装IService
接口,进一步封装CRUD
采用get
查询单行、remove
删除、list
查询集合、page
查询分页。
官网案例:https://baomidou.com/pages/49cc81/
使用步骤:
1)定义一个UserService
接口继承与MyBatisPlus
提供的IService
接口:
package com.test.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.test.entity.User;
public interface IUserService extends IService {
}
2)定义一个UserService
的实现类,并且继承与MyBatisPlus
提供的ServiceImpl
:
package com.test.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.test.entity.User;
import com.test.mapper.UserMapper;
import com.test.service.IUserService;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl extends ServiceImpl implements IUserService {
}
新增:
删除:
修改:
查询:
分页:
测试代码:
package com.test;
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.test.entity.User;
import com.test.service.IUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashMap;
import java.util.List;
import javax.annotation.Resource;
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo07_Service {
@Resource
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 wrapper = Wrappers.query();
wrapper.in("id", "1", "2");
// 查询所有
// List userList = userService.list();
// 通过wrapper查询
List userList = userService.list(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
/**
* 查询总记录数
*
* @throws Exception
*/
@Test
public void test7() throws Exception {
QueryWrapper 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 page = new Page(1, 3);
userService.page(page);
// 当前页数据
List 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> 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 chainWrapper = userService.query();
// SQL: SELECT id,name,age FROM user WHERE (id IN (?,?,?) AND name LIKE ?)
List 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 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();
}
}
操作数据库表时,Mapper
接口继承BaseMapper
,泛型名和数据库表名对应,如果数据表名为t_user
,而BaseMapper
的泛型为实体类User
,导致找不到数据库的表。
1)解决方法1:实体类使用@TableName
注解,value
值为表名
@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_
MyBatisPlus
在实现CRUD
默认会将Id
作为主键,在插入数据时,如果主键不叫Id
则添加功能会失败
解决方案:@TableId
注解标识属性,将此属性对应的字段指定为主键
@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.test.entity;
@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)
在枚举类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;
}
}
实体类:
@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.test;
import com.test.entity.User;
import com.test.service.IUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo08 {
@Resource
private IUserService userService;
@Test
public void test1() throws Exception {
User user = new User(111L, "xiaohui", "0", 20);
userService.save(user);
}
}
如果实体类的普通属性名,和数据库非主键的字段名不一致解决方案:@TableField
设置对应字段名
@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;
}
在实际开发中,我们删除一条记录可能只是修改其状态,并不是真正的从数据库中删除掉;这样的删除成为逻辑删除;
准备一张数据表:
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.test.entity;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
/*
* 表示该列为逻辑删除字段
* 0: 表示未删除
* 1: 表示已删除
*/
@TableLogic
private Integer isDelete;
}
测试代码:
@Resource
private EmpMapper empMapper;
@Test
public void test2() throws Exception {
// 只是逻辑删除(把id为1的记录的is_delete改为1)
empMapper.deleteById(1L);
}
执行代码,观察日志:
Tips:本质上就是执行了一个update
当实体类中有标注逻辑删除字段时,在查询时是不会查询被逻辑删除的记录的,示例代码:
@Test
public void test3() throws Exception {
// 不会查询is_delete为1的记录
List empList = empMapper.selectList(null);
for (Emp emp : empList) {
System.out.println(emp);
}
}
执行结果:
在数据库中,经常会有一些字段符合枚举类型;
例如:
我们通常都是将以上字段设置为char或者int类型,然后通过对应的字符/数字代表对应的含义,然后到Java代码中我们通常都需要做类似于如下的判断:
${sex==0?'男':'女'}
未开始
进行中
已结束
if(status=0){
// 未开始
}else if(status==1){
// 进行中
} 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.test.enmu;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum SexEnum {
MAN(0, "男"),
WOMAN(1, "女");
// 这一列的值和数据库表映射
@EnumValue
private Integer sexValue;
// 这个字段就是这个枚举项的字符串含义
private String sexName;
}
扫描枚举包:
#配置日志
mybatis-plus:
# 扫描枚举包
type-enums-package: com.test.enum_
定义实体类:
package com.test.entity;
import com.test.enmu.SexEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer id;
private String name;
/*
使用枚举类型
当从数据库中查询到了0则赋值SexEnum.MAN给sex变量
当从数据库中查询到了1则赋值SexEnum.WOMAN给sex变量
*/
private SexEnum sex;
}
定义Mapper接口:
@Repository
public interface StudentMapper extends BaseMapper {
}
测试代码:
@Resource
private StudentMapper studentMapper;
@Test
public void test4() throws Exception {
System.out.println(studentMapper.selectById(1L));
System.out.println(studentMapper.selectById(2L));
}
虽然sex字段为变为了枚举类型,但是将搜索条件设置为sex=0依旧可以搜索到符合条件的记录:
@Test
public void test5() throws Exception {
QueryWrapper wrapper = Wrappers.query();
// 搜索条件为0依旧可以搜索出来
wrapper.eq("sex", "0");
List stuList = studentMapper.selectList(wrapper);
for (Student student : stuList) {
System.out.println(student);
}
}
1)乐观锁与悲观锁
在多个客户端(线程/进程)操作同一个资源并发生写的操作时,我们就需要保证数据的安全性了。
如图所示:
当我们在购买车票时,首先会进行票数的查询,例如A1在购买车票时查询到票还有100张,准备购买第100张票,与此同时,A2也查询到票数为100,也将购买第100张票;
Tips:上述图中,两个窗口在买票之前分别查询了票,发现票数余额为100,然后都卖了第100张票,出现多卖情况
在并发修改某一资源时,我们必须保证线程安全的问题。在操作之前先加锁,这种方式就是采用悲观锁的方式;
悲观锁虽然保证了程序的安全性,同时效率也降低了很多,在一个客户端操作时,其他客户端均不可操作,降低了系统的并发性能。
我们可以在每条记录中分配一个_version字段,每当我们对记录进行更新时,此版本号都会自增。我们可以借助该字段帮我们完成乐观锁。保证线程安全问题。
实现方式:
-- 要修改数据之前,先查该数据上一次修改的时间戳
select version
from table_
where id = 1;
-- 修改数据时,更新版本号
update table_
set goods_name='小苹果',
version=version + 1
where version = ${version};
2)@Version实现乐观锁
建立数据表:
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`count` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 3
CHARACTER SET = utf8
COLLATE = utf8_general_ci
ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of goods
-- ----------------------------
INSERT INTO `goods` VALUES (1, '手机', 100);
INSERT INTO `goods` VALUES (2, '电脑', 100);
实体类:
package com.test.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer count;
@Version // 乐观锁字段
private Integer version;
}
Mapper接口:
package com.test.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.test.entity.Goods;
import org.springframework.stereotype.Repository;
@Repository
public interface GoodsMapper extends BaseMapper {
}
测试代码:
@Resource
private GoodsMapper goodsMapper;
@Test
public void test6() throws Exception {
Goods goods = goodsMapper.selectById(1L);
goods.setCount(goods.getCount() - 1);
goodsMapper.updateById(goods); // 修改完毕后,version在本次version的基础上+1
}
Tips:修改的时候version自动在原来的基础上+1;
MyBatisPlus
本质上也是MyBatis
,因此也支持我们自定义SQL
语句,编写Mapper.xml
与Mapper
接口进行绑定;
1)在application.yml中扫描Mapper.xml:
#配置日志
mybatis-plus:
# 扫描mapper.xml文件
mapper-locations: classpath:com/test/mapper/*.xml
# 配置包别名
type-aliases-package: com.test.entity
2)在Mapper接口中扩展方法:
@Repository
public interface UserMapper extends BaseMapper {
List findAll();
}
3)编写Mapper.xml:
ActiveRecord
也属于ORM
(对象关系映射)层,由Rails
最早提出,遵循标准的ORM
模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。
ActiveRecord
的主要思想是:
1)每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录,通常表的每个字段在类中都有相应的Field
;
2)ActiveRecord
同时负责把自己持久化,在ActiveRecord
中封装了对数据库的访问,即CURD
;
3)ActiveRecord
是一种领域模型(Domain Model
),封装了部分业务逻辑。
简而言之,AR
建立了Java
对象与数据库表逻辑上的直接映射,方便了程序的编写。而在MyBatisPlus
中使用AR
非常简单,只需让JavaBean
继承Model
类即可。
在MyBatisPlus
中,只要将我们的JavaBean
继承与Model
类即可获取AR
功能,即JavaBean
自身实现自身的CURD
;
让JavaBean
继承Model
:
package com.test.entity;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User extends Model { // 继承Model
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String sex;
private Integer age;
}
测试代码:
package com.test;
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.test.entity.User;
import com.test.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import javax.annotation.Resource;
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo12_ActiveRecord {
@Resource
private UserMapper userMapper;
/**
* 新增
* @throws Exception
*/
@Test
public void test1() throws Exception {
User user = new User(100L, "xiaoming", "0", 20);
user.insert();
}
/**
* 删除
* @throws Exception
*/
@Test
public void test2() throws Exception {
User user = new User();
user.setId(100L);
user.deleteById();
}
/**
* 修改
* @throws Exception
*/
@Test
public void test3() throws Exception {
User user = new User(1L, "xiaohui", "1", 22);
user.updateById();
}
/**
* 根据id查询
* @throws Exception
*/
@Test
public void test4() throws Exception {
User user = new User();
user.setId(100L);
User dbUser = user.selectById();
System.out.println(dbUser);
}
/**
* 查询全部
* @throws Exception
*/
@Test
public void test5() throws Exception {
User user = new User();
List userList = user.selectAll();
for (User dbUser : userList) {
System.out.println(dbUser);
}
}
/**
* 根据Wrapper条件查询
* @throws Exception
*/
@Test
public void test6() throws Exception {
QueryWrapper wrapper = Wrappers.query();
wrapper.like("name", "a");
User user = new User();
// 根据条件查询
List userList = user.selectList(wrapper);
for (User dbUser : userList) {
System.out.println(dbUser);
}
}
/**
* 分页查询
* @throws Exception
*/
@Test
public void test7() throws Exception {
QueryWrapper wrapper = Wrappers.query();
wrapper.like("name", "a");
Page page = new Page(1, 3);
User user = new User();
// 分页查询
user.selectPage(page, wrapper);
List records = page.getRecords();
for (User record : records) {
System.out.println(record);
}
}
}
1)引入多数据源依赖:
com.baomidou
dynamic-datasource-spring-boot-starter
3.5.1
2)准备两个数据库
DROP database IF EXISTS test1;
create database test1;
use test1;
DROP TABLE IF EXISTS user;
CREATE TABLE `user`
(
`id` bigint(20) NOT NULL COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`sex` char(1) DEFAULT NULL COMMENT '性别 0:男 1:女',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`birthday` date DEFAULT NULL COMMENT '生日',
PRIMARY KEY (`id`)
);
INSERT INTO `user` VALUES (1, 'Jone', '1', 27, '2001-10-04');
INSERT INTO `user` VALUES (2, 'Jack', '0', 20, '1999-10-04');
INSERT INTO `user` VALUES (3, 'Tom', '1', 28, '1996-08-12');
-- -----------------------------------------------
DROP database IF EXISTS test2;
create database test2;
use test2;
DROP TABLE IF EXISTS user;
CREATE TABLE `user`
(
`id` bigint(20) NOT NULL COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`sex` char(1) DEFAULT NULL COMMENT '性别 0:男 1:女',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`birthday` date DEFAULT NULL COMMENT '生日',
PRIMARY KEY (`id`)
);
INSERT INTO `user` VALUES (4, 'Sandy', '1', 21, '2001-10-04');
INSERT INTO `user` VALUES (5, 'Billie', '0', 24, '1992-09-07');
INSERT INTO `user` VALUES (6, 'Jackson', '0', 18, '1996-08-12');
3)多数据源配置:
spring:
datasource:
dynamic:
# 没有匹配的数据源时默认匹配的数据源
primary: master
# 当没有匹配的数据源是是否采用默认的数据源,
# true: 严格匹配,如果没有匹配到数据源则抛出异常
# false(默认值): 如果没有匹配到数据源则使用默认的数据源
strict: false
datasource:
# 数据源的名称(名字任意)
master:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test1?serverTimezone=GMT%2b8
username: root
password: admin
# 数据源的名称(名字任意)
slave:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test2?serverTimezone=GMT%2b8
username: root
password: admin
#配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4)启动类:
package com.test;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.test.mapper")
public class MyBatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MyBatisPlusApplication.class, args);
}
}
5)实体类:
package com.test.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User extends Model {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String sex;
private Integer age;
}
6)编写两个Mapper:
package com.test.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.test.entity.User;
import org.springframework.stereotype.Repository;
@DS("master") // 指定数据源的名称(若指定的数据源不存在则使用默认的)
@Repository
public interface UserMapperMaster extends BaseMapper {
}
package com.test.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.test.entity.User;
import org.springframework.stereotype.Repository;
@DS("slave") // 指定数据源的名称(若指定的数据源不存在则使用默认的)
@Repository
public interface UserMapperSlave extends BaseMapper {
}
7)测试类:
package com.test;
import com.test.entity.User;
import com.test.mapper.UserMapperMaster;
import com.test.mapper.UserMapperSlave;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import javax.annotation.Resource;
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo01_Dynamic {
@Resource
private UserMapperMaster userMapperMaster;
@Resource
private UserMapperSlave userMapperSlave;
@Test
public void test1() {
// 使用的是master数据源
List userList = userMapperMaster.selectList(null);
for (User user : userList) {
System.out.println(user);
}
}
@Test
public void test2() {
// 使用slave数据源
List userList = userMapperSlave.selectList(null);
for (User user : userList) {
System.out.println(user);
}
}
}
参与评论
手机查看
返回顶部