Mybatis-plus
mybatis-plus 是在mybatis的基础上做增强不做改变 , 简化了CRUD操作 . 如何体现: 我们新建一个项目试试
-
新建springboot项目, 导入依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.3</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> <scope>provided</scope> </dependency> </dependencies>
-
配置数据库四要素, 注: 需要加上时区配置, 因为加入的mysql依赖版本是8的
#mysql spring.datasource.url=jdbc:mysql://localhost/mybatis-plus?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=admin spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 配置slq打印日志 logging.level.cn.wolfcode.mp.mapper=debug
-
编写domain和mapper
@Setter @Getter @ToString public class Employee { private Long id; private String name; private String password; private String email; private int age; private int admin; private Long deptId; }
/** * mybatis-plus 映射接口自定义 * 1. 自定义一个接口, 继承 BaseMapper * 2. 指定接口泛型: 当前映射接口操作实体对象 : Employee * * 自定义EmployeeMapper接口没有自定义crud方法 为什么测试类可以调用 * EmployeeMapper接口继承BaseMapper接口, name就继承父接口定义所有能继承的方法 * * 没有写crud的sql语句, 为什么测试类中可以使用crud操作呢 * crud操作, 核心是拼接sql, 而拼接的核心是 : 表, 列, 参数, 关键字 * 以查询数据为例子, select id,name,password,email,age,admin,deptId from employee * 要拼接表: employee * 关键词: select * 参数: 无 (*) * * * 执行查询mapper映射方法: employeeMapper.selectList(null); * 根据上面分析发现, sql拼接核心其实只需要解析出 要拼接的列, 跟拼接的表即可 * mybatis-plus 怎么解析 列 , 表 呢? * * mybatis-plus 在启动并初始化时 , 会解析所有的mapper映射接口, 获取该接口上面指定的泛型操作实体泛型对象 : * 比如 : Employee , 获取该泛型字节码对象 * 然后通过字节码对象解析该类的类名, 该类的字段名 * 以 employee 为例子 : * Employee.class * 类名: Employee * 字段名: id,name,password,email,age,admin,deptId * 之后mybatis-plus默认,类名就是表名, 字段名就是表的列 * 最终, sql语句就可以拼接成功 * * */ public interface EmployeeMapper extends BaseMapper<Employee> { }
-
编写启动类
@SpringBootApplication @MapperScan("cn.wolfcode.mp.mapper") public class App { public static void main(String[] args) { SpringApplication.run(App.class,args); } }
-
编写测试类 贴SpringBootTest注解
@SpringBootTest public class CRUDTest { @Autowired private EmployeeMapper employeeMapper; @Test public void testSave(){ Employee employee = new Employee(); employee.setAdmin(1); employee.setAge(18); employee.setDeptId(1L); employee.setEmail("dafei@wolfcode"); employee.setName("dafei"); employee.setPassword("111"); employeeMapper.insert(employee); } @Test public void testUpdate(){ Employee employee = new Employee(); employee.setId(1327139013313564673L); employee.setAdmin(1); employee.setAge(18); employee.setDeptId(1L); employee.setEmail("dafei@wolfcode.cn"); employee.setName("xiaofei"); employee.setPassword("111"); employeeMapper.updateById(employee); } @Test public void testDelete(){ employeeMapper.deleteById(1327139013313564673L); } @Test public void testGet(){ System.out.println(employeeMapper.selectById(1327139013313564673L)); } @Test public void testList(){ System.out.println(employeeMapper.selectList(null)); } }
MP中有几个常用的注解需要理解
-
TableName 表名注解 , 用于指定当前实体类映射到数据库的那张表, 默认是根实体类的类名一致
-
TableField 字段注解 , 用于指定当前属性映射到数据库表的哪一列, 默认是根属性名一致
@TableField(value="employename",exist = false) private String name;
-
Tableld 主键注解 , 用于指定当前属性为数据库表的主键
type = IdType.AUTO 指定id生成算法,默认雪花算法
@TableId(value="id", type=IdType.AUTO) private Long id;
Mybatis-plus的mapper的通用方法
insert:
-
int insert(T entity); 新增, 传入的参数为实体对象
update:
-
updateById()
在使用updateById时, 可能会出现设置了ID, 但是其他值未设置, 此时此属性就不会参与sql拼接, 就可能会出现修改完之后, 某个属性值调用修改方法前有值, 但调用方法后这个值变空了.为解决本情况, 有几种解决方式 :
mybatis-plus sql拼接规则: 1. 参数实体属性值如果为null , 不参与sql拼接 2. 如果参数实体属性类型是8基本类型,有默认值, mybatis-plus认为有属性值, 参与sql拼接 基本类型默认值的存在, 导致基本属性参与sql拼接 容易出现参数丢失问题(列值丢失) 方案1: 将基本类型改为包装类型 方案2: 先查询单个,再设置值(修改) 然后更新 方案3: 使用update(null,wrapper) 方法操作 -> 部分字段更新
可以选择使用 update方法
-
update
@Test public void testUpdate2(){ // 条件构造器: 暂时理解 where条件拼接逻辑 UpdateWrapper<Employee> wrapper = new UpdateWrapper<>(); wrapper.eq("id",20); // 等价 where id= 20 wrapper.set("name","fengjianbin"); // 等于set name = fengjianbin employeeMapper.update(null,wrapper); }
如何选择
/** * updateById: * 1> sql的where条件id时, 使用 * 2> 全量字段更新时, 使用 * * update(null,wrapper) * 1> sql 的where条件不一定是id时, eg:where age = 19 * 2> 部分字段更新时使用 */
delete
-
deleteById() 根据ID删除信息
// 需求: 根据ID=21删除员工信息 @Test public void testDeleteById(){ employeeMapper.deleteById(21L); }
-
deleteBatchIds(idList) 删除List集合中所有ID的信息
// 需求 : 批量删除id=20 19 的员工信息 // 底层: where id in (?,?) @Test public void testDeleteBatchId(){ employeeMapper.deleteBatchIds(Arrays.asList(20L,19L)); }
-
deleteByMap(map) 删除集合里满足该集合的所有条件的信息
// 需求: 删除name=fengjianbin 并且age=18的员工信息 // 底层 where name = ? and age = ? @Test public void testDeleteBYMap(){ Map<String,Object> map = new HashMap<>(); map.put("name","fengjianbin"); map.put("age",18); employeeMapper.deleteByMap(map); }
-
delete(wrapper) 删除满足条件构造器条件的信息
// 需求: 删除name=fengjianbin 并且age=18的员工信息 @Test public void testDelete(){ // UpdateWrapper--- 更新操作使用 // QueryWrapper --- 查询/条件操作使用 QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.eq("name","fengjianbin"); wrapper.eq("age",18); employeeMapper.delete(wrapper); }
select
-
selectById
// 需求: 查询 id =1 的员工信息 @Test public void testSelectById(){ Employee employee = employeeMapper.selectById(1L); System.err.println("employee = " + employee); }
-
selectBatchIds ( 查找数组中所有ID的信息, 如果是LONG类型, 可以不加L)
// 需求: 查询id=1 , id=2的员工数据 // 底层: SELECT * FROM employee WHERE id IN ( ? , ? ) @Test public void testSelectBatchIds(){ List<Employee> employees = employeeMapper.selectBatchIds(Arrays.asList(1L, 2L)); System.out.println("employees = " + employees); }
-
selectByMap 查询满足map中的所有信息
// 需求: 查询age=19 , name=fengjianbin的员工数据 // 底层: where age = ? and name = ? @Test public void testSelectByMap(){ Map<String,Object> map = new HashMap<>(); map.put("age",19); map.put("name","fengjianbin"); List<Employee> employees = employeeMapper.selectByMap(map); System.out.println("employees = " + employees); }
-
selectCount (null / wrapper) 传null,则表示查询所有数据的数量, 传值则代表查询符合条件的数据数量
// 需求: 查询所有员工的个数 @Test public void testSelectByCount(){ Integer count = employeeMapper.selectCount(null); // 没有条件的查询( 查所有个数 ) System.out.println("count = " + count); } // 需求: 查询 age = 20的员工的个数 @Test public void testSelectByCount1(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.eq("age",20); employeeMapper.selectCount(wrapper); }
-
selectList 如果加wrapper表示条件查询, 如果null表示查询所有
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name = ? AND age = ?)
// 需求: 查询所有的员工信息, 返回List<Employee> @Test public void testSelectList(){ List<Employee> employees = employeeMapper.selectList(null); // 如果加wrapper表示条件查询, 如果null表示查询所有 for (Employee employee : employees) { System.out.println("employee.toString() = " + employee.toString()); } }
-
selectMap 查询满足条件的信息, 并把每条信息的行列都封装进map中
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name = ? AND age = ?)
@Test public void testSelectMap(){ List<Map<String, Object>> maps = employeeMapper.selectMaps(null);// 如果加wrapper表示条件查询, 如果null表示查询所有 for (Map<String, Object> map : maps) { System.err.println(map); } }
/** * 思考: 什么时候用 selectList 什么时候使用:selectMaps * * 查询数据能封装成domain使用selectList * 查询数据不能封装成domain使用selectMaps 比如: 项目1里面的报表查询 */
-
selectPage 用于分页查询
// 需求: 查询第二页员工数据, 每页显示三条(分页返回的数据是实体对象) @Test public void testSelectPage(){ // mybatis-plus分页的两个步骤 // 步骤1: 配置分页插件(主配置文件) // 步骤2: 编写分页代码 // 参数1: current 当前页 // 参数2: size 每页显示条数 IPage<Employee> page = new Page<>(2,3); // 参数1: 分页参数对象 // 分数2: wrapper条件对象(条件构造器) IPage<Employee> page1 = employeeMapper.selectPage(page, null); System.out.println(page1 == page); // 内存地址:true System.out.println("当前页:" + page.getCurrent()); System.out.println("每页显示条数" + page.getSize()); System.out.println("总页数:" + page.getPages()); System.out.println("总数:" + page.getTotal()); System.out.println("当前页数据:" + page.getRecords()); } /*public IPage<Employee> selectPage(IPage<Employee> page,QueryWrapper wrapper){ // 分页逻辑 return page; }*/
Mybatis-plus 的条件构造器wrapper
wrapper: 条件构造抽象类 , 最顶级父类 常用的子类有:
-
QueryWrapper: Entity 对象封装操作类,不是用lambda语法 ->用于定义查询条件操作
-
UpdateWrapper: Update 条件封装,用于Entity对象更新操作 -> 用于更新操作
修改型子类(UpdateWrapper,LambdaUpadateWrapper )特有方法 : set, setSql
// set name =? where id = ? @Test public void testUpdate(){ UpdateWrapper<Employee> wrapper = new UpdateWrapper<>(); wrapper.eq("id",21L); wrapper.set("name","dafei"); //占位符 (预编译) employeeMapper.update(null,wrapper); } // set name ="dafei" where id =? @Test public void testUpdate2(){ UpdateWrapper<Employee> wrapper = new UpdateWrapper<>(); wrapper.eq("id",21L); wrapper.setSql("name=dafei"); // 拼接sql片段, 直接塞 employeeMapper.update(null,wrapper); } // set name=? where id=? @Test public void testUpdate3(){ LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(Employee::getId,21L); wrapper.set(Employee::getName,"dafei"); employeeMapper.update(null,wrapper); }
查询型子类(QueryWrapper,LambdaQueryWrapper ) 特有方法 select
// select * from employee where name ="dafei" and id = 21 @Test public void testQuery(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.eq("name","dafei").eq("id",21l); List<Employee> employees = employeeMapper.selectList(wrapper); }
用于创建各种类型的wrapper @Test public void testWrappers(){ //update UpdateWrapper<Employee> updateWrapper1 = new UpdateWrapper<>(); UpdateWrapper<Employee> updateWrapper2 = Wrappers.<Employee>update() LambdaUpdateWrapper<Employee> lambdaUpdateWrapper1 = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<Employee> lambdaUpdateWrapper2 = Wrappers.<Employee>lambdaUpdate(); //UpdateWrapper -->LambdaUpdateWrapper LambdaUpdateWrapper<Employee> lambdaUpdateWrapper3 = updateWrapper1.lambda(); //select QueryWrapper<Employee> QueryWrapper1 = new QueryWrapper<>(); QueryWrapper<Employee> QueryWrapper2 = Wrappers.<Employee>query(); LambdaQueryWrapper<Employee> lambdaQueryWrapper1 = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Employee> lambdaQueryWrapper2 = Wrappers.<Employee>lambdaQuery(); //QueryWrapper -->LambdaQueryWrapper LambdaQueryWrapper<Employee> lambdaQueryWrapper3 = QueryWrapper1.lambda(); }
高级查询 :
-
列查询
// 需求: 查询所有员工, 返回员工name,age列 // SELECT id,name,password,email,age,admin,dept_id FROM employee @Test public void testQuery1(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.select("name","age"); // 挑选显示列 List<Employee> employees = employeeMapper.selectList(wrapper); }
-
排序
// 需求:查询所有员工信息按age正序排, 如果age一样, 按id正序排 @Test public void testQuery2(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); // wrapper.orderByAsc("age"); // wrapper.orderByDesc("id"); // 参数1 : 满足什么条件之后, 才执行排序逻辑 // 参数2 : 是否正序排列 // 参数3 : 排序的列 wrapper.orderBy(true,true,"age"); List<Employee> employees = employeeMapper.selectList(wrapper); }
-
分组查询 having优先级最低
// 需求: 以部门ID 进行分组查询, 查各个部门员工个数 // select dept_id count(id) count FROM employee group by dept_id @Test public void testQuery3(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.select("dept_id","count(id) count"); wrapper.groupBy("dept_id"); List<Map<String, Object>> maps = employeeMapper.selectMaps(wrapper); maps.forEach(System.out::println); } // 需求: 以部门ID 进行分组查询, 查每个部门员工个数, 将大于3人的部门过滤出来 @Test public void testQuery4(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.select("dept_id","count(id) count"); wrapper.groupBy("dept_id"); // wrapper.having("count > 3"); //sql片段 wrapper.having("count > {0}",3); // 占位符的方式 List<Map<String, Object>> maps = employeeMapper.selectMaps(wrapper); maps.forEach(System.out::println); }
-
allEq ( 查询满足传入的所有条件的信息 )
@Test public void testQuery5(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); Map<String,Object> map = new HashMap<>(); map.put("name","dafei"); map.put("age",18); // 所有条件必须都相等 wrapper.allEq(map); // where (name =? and age =? ) //wrapper.eq("age",18); // where (age =? ) //wrapper.ne("age",18); // where ( age <> ? ) employeeMapper.selectList(wrapper); }
-
beween , notBetween (字段, val1 ,val2) 指定某列的数值区间
// 需求: 查询年龄介于18-30岁的员工信息 @Test public void testQuery6(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); // age between ? and ? wrapper.between("age",18,30); wrapper.notBetween("age",18,30); employeeMapper.selectList(wrapper); }
-
is null ,not is null 用于查询指定的属性里值为null / 不为null 的信息
// 需求: 查询dept_id 为null 员工信息 @Test public void testQuery7(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.isNull("dept_id"); // dept_id is null wrapper.isNotNull("dept_id"); // where dept_id is not null employeeMapper.selectList(wrapper); }
-
in
// 需求: 查询ID 为1,2 的员工信息, 使用 in where id in (1,2) @Test public void testQuery8(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.in("id",1L,2L); // where (id in (?,?)) // sql 片段 // wrapper.inSql("id","1,2"); // where (id IN(1,2)) employeeMapper.selectList(wrapper); }
-
模糊查询 like likeRight likeLeft
// 需求: 查询名字中有fei字样的员工 @Test public void testQuery9(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); // %fei% wrapper.like("name","fei"); // fei% // wrapper.likeRight("name","fei"); // %fei // wrapper.likeLeft("name","fei"); // wrapper.noLike(); employeeMapper.selectList(wrapper); }
-
or and
// 需求: 查询name 含有fei字样, 或者年龄在18到30之间的用户 //select * from employee where name likek '%fei%' or( age >=18 and age <=30) @Test public void testQuery11(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.like("name","fei"); wrapper.or(wp->wp.ge("age",18).le("age",30)); employeeMapper.selectList(wrapper); } // 需求: 查询name含有fei字样的, 并且年龄在小于18或大于30的用户 //SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name LIKE ? AND (age < ? OR age > ?)) @Test public void testQuery12(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.like("name","fei") .and(wp -> wp.lt("age",18).or().gt("age",30)); employeeMapper.selectList(wrapper); }
注:
/** * mybatis-plus 在写sql情况下,如何进行多表关联查询 * 不能进行多表关联查询,如果硬要处理,只能使用额外sql方法 * */ //多表查询:查询所有员工数据,同事查询他所在部门信息 //重点 @Test public void testQuery13(){ QueryWrapper<Employee> wrapper = new QueryWrapper<>(); List<Employee> employees = employeeMapper.selectList(wrapper); //叫做额外sql方式 for (Employee employee : employees) { employee.setDept(departmentMapper.selectById(employee.getDeptId())); } } //不然就是还是要使用下面的自定义SQL
//不建议使用 //单表 @Select("select e.* from employee e") List<Employee> listByAnnoSingle(); //多表 @Select("select e.*, d.id d_id, d.name d_name, d.sn d_sn from employee e left join department d on e.dept_id = d.id") @Results({ @Result(column="d_id", property = "dept.id"), @Result(column="d_name", property = "dept.name"), @Result(column="d_sn", property = "dept.sn") }) List<Employee> listByAnnoJoin();
通用service接口
service接口继承IService <指定泛型, 即操作实体类型> 接口实现类继承ServiceImpl
/** * 自定义mybatis-plus 服务层接口 * 1> 自定义接口 IEmployeeService * 2> 继承通用接口IService * 3> 指定泛型, 即操作实体类型 */ public interface IEmployeeService extends IService<Employee> { } /** * 自定义mybatis-plus 服务层接口实现类 * 1> 自定义接口 EmployeeServiceImpl * 2> 实现自定义接口 IEmployeeService * 3> 继承通用接口IService实现类 ServiceImpl * 4> 指定2个泛型: 1. 操作实体类mapper接口 2.操作实体对象类型:employee */ @Service public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService { }
模拟分页
public interface IEmployeeService extends IService<Employee> { IPage<Employee> queryPage(EmployeeQuery qo); }
@Service public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService { @Override public IPage<Employee> queryPage(EmployeeQuery qo) { IPage<Employee> page = new Page<>(qo.getCurrentPage(),qo.getPageSize()); QueryWrapper<Employee> wrapper = new QueryWrapper<>(); wrapper.like(StringUtils.hasText(qo.getKeyword()),"name",qo.getKeyword()); return super.page(page,wrapper); } }