MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。MyBatis和JAP各自有自己的优点。MyBatis需要自己写sql,灵活性更强,也方便我们优化,提供动态sql的特性。Spring Data JPA更加面向对象,提供根据方法名自动创建sql的功能。MyBatis-Plus 就是对 Mybatis面向对象方面的一个增强,提供了简单sql的默认实现。
使用springboot来集成mp作为练习。
<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 http://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.1.3.RELEASEversion>
<relativePath/>
parent>
<groupId>com.mybaties-plusgroupId>
<artifactId>demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>demoname>
<description>Demo project for mybaties-plus demodescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.1.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
增加扫描的mapper注解
@SpringBootApplication
@MapperScan("com.mybatiesplus.demo.dao")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Data
@TableName("mpdemo")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;
}
public interface UserMapper extends BaseMapper<User> {
}
CREATE TABLE `mpdemo` (
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(11) DEFAULT NULL COMMENT '姓名',
`age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄',
`email` varchar(11) DEFAULT NULL COMMENT '邮箱',
`manager_id` bigint(20) unsigned DEFAULT NULL COMMENT '领导id',
`create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1149584829487587331 DEFAULT CHARSET=utf8mb4;
INSERT INTO `mpdemo` (`id`, `name`, `age`, `email`, `manager_id`, `create_time`)
VALUES
(1, '大Boss', 35, '[email protected]', NULL, '2019-07-10 12:14:45'),
(2, '王天风', 25, '[email protected]', 1, '2019-07-11 12:14:45'),
(3, '李艺伟', 28, '[email protected]', 1, '2019-07-11 09:09:08'),
(4, '张雨绮', 24, '[email protected]', 1, '2019-07-12 07:00:00'),
(5, '刘宏宇', 32, '[email protected]', 1, '2019-07-10 11:11:11');
mp默认是支持实体和表名、字段名驼峰和下滑线转化。默认通过雪花算法自动生产id。
@Test
public void insert() {
User user = new User();
user.setName("刘明强");
user.setAge(19);
user.setManagerId(2L);
user.setCreateTime(LocalDateTime.now());
int insert = userMapper.insert(user);
System.out.println(insert);
}
@TableName(“mpdemo”): 表名和实体类不一致时使用该注解,指定实体对应的表名。
@TableId: 默认会将实体里的id字段作为主键,使用这个注解可以指定其他字段作为主键。
@TableField(“name”): 当实体字段名和表名不一致时,可以通过这个注解修改对应的数据库字段。
有的时候我们的实体类的变量并不会对应到数据库里的某一个字段,它只是用来临时保存数据。有三种方式可以排除它。
根据主键查找
@Test
public void selectById() {
User user = userMapper.selectById(1149584829487587330L);
System.out.println(user);
}
根据多个主键查询,会转化成in。
@Test
public void selectBatchIds() {
List<User> users = userMapper.selectBatchIds(Lists.newArrayList(1L, 2L, 3L));
users.forEach(System.out::println);
}
会将map中的值转化成where条件的参数,用and连接。这里的key需要是表中的字段名,并不是实体中参数名。
@Test
public void selectByMap() {
Map<String,Object> columnMap = new HashMap<>();
columnMap.put("age", 25);
List<User> users = userMapper.selectByMap(columnMap);
users.forEach(System.out::println);
}
QueryWrappert提供了丰富的方法一遍我们实现不同需求,下面是一些例子。
/**
* 名字中包含oss并且年龄小于40
* name like '%oss%' and age < 40
*/
@Test
public void selectByWrapper() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
//QueryWrapper query = Wrappers.query();
queryWrapper.like("name","oss").lt("age", 40);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
/**
* 名字中包含oss并且年龄大于等于20且小于等于40并且email不为空
* name like '%oss%' and age between 20 and 40 and email is not null
*/
@Test
public void selectByWrapper2() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.like("name", "oss").between("age", 20, 40).isNotNull("email");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
/**
* 创建日期为2019年7月11日并且直属上级为名字以Boss结尾
* date_format(create_time,'%Y-%m-%d') and manager_id in (select id from mpdemo where name like '%Boss')
*/
@Test
public void selectByWrapper4() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.apply("date_format(create_time,'%Y-%m-%d') = {0}", "2019-07-11")
.inSql("manager_id","select id from mpdemo where name like '%Boss'");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
/**
* 名字以Boss结尾并且(年龄小于40或邮箱不为空)
* name like '%Boss' and (age < 40 or email is not null)
*/
@Test
public void selectByWrapper5() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.likeLeft("name", "Boss").and(wq -> wq.lt("age", 40).or().isNotNull("email"));
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
/**
* 名字以Boss结尾或者(年龄小于40并且年龄大于20并且邮箱不为空)
* name like '%Boss' or (age < 40 and age > 20 and email is not null)
*/
@Test
public void selectByWrapper6() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.likeLeft("name", "Boss").or(wq -> wq.lt("age", 40).gt("age", 20).isNotNull("email"));
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
/**
* (年龄小于40或邮箱不为空)并且 名字以Boss结尾
* (age < 40 or email is not null) and name like '%Boss'
*/
@Test
public void selectByWrapper7() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.nested(wq -> wq.lt("age", 40).or().isNotNull("email")).likeLeft("name", "Boss");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
/**
* 年龄为30 31 34 35
* age in (30,31,34,35)
*/
@Test
public void selectByWrapper8() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.in("age", Arrays.asList(30,31,34,35));
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
/**
* 年龄为30 31 34 35 只返回一条
* age in (30,31,34,35) limit 1
*/
@Test
public void selectByWrapper9() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
//有sql注入危险
queryWrapper.in("age", Arrays.asList(30,31,34,35)).last("limit 1");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
只返回特点字段,分成两种情况:1. 选择包含哪些 2.选择排除哪些。
/**
* 只查出固定字段 只查询id name
*/
@Test
public void selectAllSuper1() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.select("id", "name");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
/**
* 查询过滤固定字段 不查询 manager_id create_time
*/
@Test
public void selectAllSuper2() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.select(User.class, info -> !info.getColumn().equals("manager_id") && !info.getColumn().equals("create_time"));
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
很多方法还有一个重载方法,第一个参数是一个boolean类型的condition,只有condition是true的时候,后面的参数才会加入到sql中去。
/**
* 重载方法第一个参数 condition true的时候才会加入到sql中
*/
@Test
public void condition() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
String name = "";
String email = "[email protected]";
queryWrapper.eq(!StringUtils.isEmpty(name), "name", name)
.eq(!StringUtils.isEmpty(email), "email", email);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
QueryWrapper构建的时候可以传入实体,默认其中不是null的属性会以等号连接。如果有需求可以再次追加 其他条件。默认是等号连接,我们也可以使用注解进行修改@TableField(condition = SqlCondition.LIKE),这样就会通过like进行拼接。SqlCondition提供了一些默认的格式。我们也可以自定义,例如:@TableField(condition = “%s<#{%s}”) 这样就会用<号连接。
/**
* QueryWrapper构建的时候可以传入实体,默认其中不是null的属性会以等号连接
*/
@Test
public void selectWrapperEntity() {
User whereUser = new User();
whereUser.setName("张雨绮");
whereUser.setAge(25);
QueryWrapper<User> queryWrapper = new QueryWrapper(whereUser);
//如果有需求可以再次追加 其他条件
queryWrapper.like("name", "Boss").lt("age", 40);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
AllEq是传入一个map,key代表字段,value代表要操作的值,用等号连接。
@Test
public void selectWrapperAllEq() {
Map<String, Object> params = new HashMap<>();
params.put("name", "Boss");
params.put("age", null);
QueryWrapper<User> queryWrapper = new QueryWrapper();
//第一种所有字段都作为条件 WHERE name = ? AND age IS NULL
//queryWrapper.allEq(params);
//第二种是忽略null值
//queryWrapper.allEq(params, false);
//第三种是可以传入 condition , condition是true的时候才进行allEq操作
queryWrapper.allEq(false, params, false);
//第四种可以对key和value进行过滤
//queryWrapper.allEq((k,v) -> !k.equals("name"), params);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
selectMaps 能返回map,字段可以不用和实体类对应,可以在统计时使用。
/**
* 返回map
*/
@Test
public void selectWrapperMaps() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
List<Map<String, Object>> users = userMapper.selectMaps(queryWrapper);
users.forEach(System.out::println);
}
/**
* 统计 返回的不是数据库里的某个字段
* 按照直属上级分组 查询每组的平均年龄 最大年龄 最小年龄 并且只取年龄总和小于500的组
*/
@Test
public void selectWrapperMaps2() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.select("avg(age) as avg_age","min(age) as min_age","max(age) as max_age")
.groupBy("manager_id").having("sum(age) < {0}", 500);
List<Map<String, Object>> users = userMapper.selectMaps(queryWrapper);
users.forEach(System.out::println);
}
selectObjs 只返回表中的第一列
/**
* 只返回表中的第一列
*/
@Test
public void selectWrapperObjs() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
List<Object> users = userMapper.selectObjs(queryWrapper);
users.forEach(System.out::println);
}
selectCount 查询总记录数。
/**
* 查询总记录数
*/
@Test
public void selectWrapperCount() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
Integer count = userMapper.selectCount(queryWrapper);
System.out.println(count);
}
selectOne 查询只返回一条记录,多于1条会报错。
/**
* 查询一条
*/
@Test
public void selectWrapperOne() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.eq("name", "Boss");
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}
Mybatis-plus也提供了支持lambda的方法。它的优点在于不用传入数据库字段名而是调用方法,防止误写。
/**
* lambda表达式 名字中包含oss并且年龄小于40
* name like '%oss%' and age < 40
*/
@Test
public void selectLambda1() {
//创建的三种方式
//LambdaQueryWrapper queryWrapper = new QueryWrapper().lambda();
//LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
LambdaQueryWrapper<User> queryWrapper = Wrappers.<User>lambdaQuery();
queryWrapper.like(User::getName, "oss").lt(User::getAge, 40);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
也提供了lambda链式调用的方法。
/**
* 也提供了lambda链式调用的方法
*/
@Test
public void selectLambda2() {
List<User> users = new LambdaQueryChainWrapper<User>(userMapper)
.like(User::getName, "oss").lt(User::getAge, 40).list();
users.forEach(System.out::println);
}
Mybatis-plus同样也支持自定义方法。
public interface UserMapper extends BaseMapper<User> {
@Select("select * from mpdemo ${ew.customSqlSegment}")
List<User> selectAll(@Param(Constants.WRAPPER)Wrapper<User> wrapper);
}
/**
* 调用自定义方法
*/
@Test
public void selectMy() {
LambdaQueryWrapper<User> queryWrapper = Wrappers.<User>lambdaQuery();
queryWrapper.like(User::getName, "oss").lt(User::getAge, 40);
List<User> users = userMapper.selectAll(queryWrapper);
users.forEach(System.out::println);
}
/**
* 分页查询
*/
@Test
public void selectPage() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.eq("name", "Boss");
Page<User> userPage = new Page<>(1, 2);
//第三个参数默认是true false代表不查询总记录数
//Page userPage = new Page<>(1, 2, false);
//可以选择返回实体还是map
IPage<User> userIPage = userMapper.selectPage(userPage, queryWrapper);
//IPage
System.out.println("总页数:" + userIPage.getPages());
System.out.println("总记录数:" + userIPage.getTotal());
List<User> records = userIPage.getRecords();
records.forEach(System.out::println);
}
查询根据自定义方法,假如多表关联查询分页。例子中是单表但实际可以替换成多表
@Select("select * from mpdemo ${ew.customSqlSegment}")
IPage<User> selectUserPage(Page<User> page, @Param(Constants.WRAPPER)Wrapper<User> wrapper);
/**
* 分页查询 调用自定义方法
*/
@Test
public void selectPageMy() {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.eq("name", "Boss");
Page<User> userPage = new Page<>(1, 2);
//可以选择返回实体还是map
IPage<User> userIPage = userMapper.selectUserPage(userPage, queryWrapper);
System.out.println("总页数:" + userIPage.getPages());
System.out.println("总记录数:" + userIPage.getTotal());
List<User> records = userIPage.getRecords();
records.forEach(System.out::println);
}
更新分三种方式(具体参照下面的例子)。关于Wrapper相关的使用,跟查询类似例如lambda的方式也同样支持。
/**
* 根据id更新用户信息
*/
@Test
public void updateById() {
User user = new User();
user.setId(1149584829487587330L);
user.setAge(26);
user.setEmail("[email protected]");
int count = userMapper.updateById(user);
System.out.println("影响记录数:" + count);
}
/**
* 需要传入两个参数 第一个是要修改的实体 第二个是查询的条件
*/
@Test
public void updateByWrapper() {
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper.eq("name", "Boss").lt("age", 50);
User user = new User();
user.setAge(45);
user.setEmail("[email protected]");
userMapper.update(user, userUpdateWrapper);
}
/**
* 也可以使用userUpdateWrapper都set方法设置要改变都值
*/
@Test
public void updateByWrapper1() {
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper.eq("name", "Boss").lt("age", 50).set("age", "52").set("email", "[email protected]");
userMapper.update(null, userUpdateWrapper);
}
删除也是有几种方式,与更新和查询类似。
/**
* 根据id删除记录
*/
@Test
public void deleteById() {
int rows = userMapper.deleteById(1149584829487587330L);
System.out.println("删除条数:" + rows);
}
/**
* 根据map作为查询条件进行删除,与查询类似
*/
@Test
public void deleteByMap() {
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("name", "Boss");
int rows = userMapper.deleteByMap(columnMap);
System.out.println("删除条数:" + rows);
}
/**
* 根据id批量删除
*/
@Test
public void deleteBatchIds() {
int rows = userMapper.deleteBatchIds(Arrays.asList(1L, 2L, 3L, 1149584829487587330L));
System.out.println("删除条数:" + rows);
}
/**
* 根据mapper删除
*/
@Test
public void deleteByWrapper() {
LambdaQueryWrapper<User> queryWrapper = Wrappers.<User>lambdaQuery();
queryWrapper.like(User::getName, "oss").lt(User::getAge, 40);
int rows = userMapper.delete(queryWrapper);
System.out.println("删除条数:" + rows);
}
Active Record(活动记录),简称AR,是一种领域模型模式,特点就是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一条记录;
开启AR模式需要继承Model类。增加@EqualsAndHashCode(callSuper = false)注解。
@Data
@TableName("mpdemo")
@EqualsAndHashCode(callSuper = false)
public class User extends Model<User> {
private Long id;
private String name;
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;
}
再进行CRUD操作时,就可以通过实体直接进行操作。还提供了insertOrUpdate的方法。
/**
* @program mybaties-plus-demo
* @description: 测试AR
* @author: xyhua
* @create: 2019/07/16 00:06
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestAR {
/**
* 新增不需要使用mapper
*/
@Test
public void insert() {
User user = new User();
user.setName("testAR");
user.setAge(26);
user.setEmail("[email protected]");
user.setManagerId(1149584829487587330L);
user.setCreateTime(LocalDateTime.now());
boolean insert = user.insert();
System.out.println(insert);
}
/**
* 根据id查询,返回结果的实体和查询的实体不同
*/
@Test
public void selectById() {
User user = new User();
user.setId(1149584829487587330L);
User resultUser = user.selectById();
System.out.println(resultUser);
System.out.println(resultUser == user);
}
/**
* 更新
*/
@Test
public void updateById() {
User user = new User();
user.setId(1149584829487587330L);
user.setName("刘大强");
boolean result = user.updateById();
System.out.println(result);
}
/**
* 删除
*/
@Test
public void deleteById() {
User user = new User();
user.setId(1149584829487587330L);
boolean result = user.deleteById();
System.out.println(result);
}
/**
* 查询或更新
* 有id且根据id没有查到则进行更新 没有则进行新增
*/
@Test
public void insertOrUpdate() {
User user = new User();
user.setName("testAR");
user.setAge(26);
user.setEmail("[email protected]");
user.setManagerId(1149584829487587330L);
user.setCreateTime(LocalDateTime.now());
boolean result = user.insertOrUpdate();
System.out.println(result);
}
}
主键策略有6种:
AUTO:是自动递增,需要数据库进行设置。
NONE ID_WORKER ID_WORKER_STR:都是使用雪花算法,默认是long,ID_WORKER_STR则需要使用String。
INPUT:主键又用户输入。
UUID:是通过UUID。
使用 @TableId(type = IdType.AUTO) 可以修改默认的策略,也可以在cofing中修改默认的全局策略,
public enum IdType {
AUTO(0),
NONE(1),
INPUT(2),
ID_WORKER(3),
UUID(4),
ID_WORKER_STR(5);
private final int key;
private IdType(int key) {
this.key = key;
}
public int getKey() {
return this.key;
}
}
通用service需要继承默认对ServiceImpl并传入范型。
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
}
他提供了很多方法。我们找几个特殊的作为例子。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {
@Autowired
private UserService userService;
/**
* getOne 这个方法还可以传入一个boolean 这样查出多条会返回第一个条
*/
@Test
public void getOne() {
User user = userService.getOne(Wrappers.<User>lambdaQuery().gt(User::getAge, 18), false);
System.out.println(user);
}
/**
* 这个方法会遍历集合,对每个元素进行判断和操作
*/
@Test
public void saveOrUpdateBatch() {
User user1 = new User();
user1.setName("tom");
user1.setAge(18);
User user2 = new User();
user1.setId(1149584829487587330L);
user2.setName("jerry");
user2.setAge(25);
userService.saveOrUpdateBatch(Arrays.asList(user1, user2));
}
/**
* lambda链式调用
*/
@Test
public void selectLambda() {
List<User> userList = userService.lambdaQuery().gt(User::getAge, 25).like(User::getName, "Boos").list();
userList.forEach(System.out::println);
}
}