MyBatis-Plus(简称MP),是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发,提高效率而生
无侵入
损耗小
强大的CRUD操作
支持Lambda形式调用:通过Lambda表达式,方便的编写各类查询条件,无需再担心字段写错
支持多种数据库
支持主键自动生成
支持XML热加载
支持ActiveRecord模式
支持自定义全局通用操作:Write once,use anywhere
支持关键词自动转义
内置代码生成器
内置分页插件
内置性能分析插件
内置全局拦截插件
内置sql注入剥离器:有效预防sql注入攻击
create table tb_user(
id int(20) not null auto_increment,
user_name varchar(20) not null,
password varchar(20) not null,
name varchar(30) default null,
age int(11) default null,
email varchar(50) default null,
primary key(id)
)auto_increment=1 default charset=utf8;
insert into tb_user(id,user_name,password,name,age,email) values
(1,'zhangsan','123456','张三',18,'[email protected]');
insert into tb_user(id,user_name,password,name,age,email) values
(2,'lisi','123456','李四',20,'[email protected]');
insert into tb_user(id,user_name,password,name,age,email) values
(3,'wangwu','123456','王五',42,'[email protected]');
insert into tb_user(id,user_name,password,name,age,email) values
(4,'zhaoliu','123456','赵六',28,'[email protected]');
insert into tb_user(id,user_name,password,name,age,email) values
(5,'xieqi','123456','谢七',43,'[email protected]');
导入依赖
com.baomidou
mybatis-plus
3.1.1
mysql
mysql-connector-java
8.0.28
com.alibaba
druid
1.2.11
org.projectlombok
lombok
true
1.18.24
junit
junit
4.12
test
org.slf4j
slf4j-log4j12
1.6.2
添加log4j.properties
log4j.rootLogger=DEBUG,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String userName;
private String password;
private String name;
private int age;
private String email;
}
public interface UserMapper {
public List findAll();
}
public class TestMyBaits {
@Test
public void testFindAll() throws Exception{
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//测试查询
List users = userMapper.findAll();
for(User user:users){
System.out.println(user);
}
}
}
第一步:将UserMapper继承BaseMapper,将拥有了BaseMapper中的所有方法
public interface UserMapper extends BaseMapper {
public List findAll();
}
第二步:编写测试类
public class TestMP {
@Test
public void test() throws Exception{
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List list = userMapper.findAll();
for(User user:list){
System.out.println(user);
}
}
}
测试MP的功能:
①给实体类添加注解
@TableName("tb_user")
②测试功能
使用MP中的MybatisSqlSessionFactoryBuilder进程构建
public class TestMP {
@Test
public void test() throws Exception{
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List list=userMapper.selectList(null);
for(User user:list){
System.out.println(user);
}
}
}
可以看到,MyBatis-Plus会自动生成SQL语句
pom.xml
org.springframework
spring-webmvc
5.3.9
org.springframework
spring-jdbc
5.3.9
org.springframework
spring-test
5.3.9
第一步,编写jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
第二步,编写applicationContext.xml
第三步,编写User实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_user")
public class User {
private int id;
private String userName;
private String password;
private String name;
private int age;
private String email;
}
第四步,编写UserMapper接口
@Mapper
public interface UserMapper extends BaseMapper {
}
第五步,测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class TestMyBatisSpring {
@Autowired
private UserMapper userMapper;
@Test
public void testSelectList(){
List users = this.userMapper.selectList(null);
for(User user:users){
System.out.println(user);
}
}
}
使用SpringBoot将进一步简化MP的整合,需要注意的是,由于使用SpringBoot需要继承parent,所以需要重新创建工程,而不是创建子Module
第一步、创建工程,导入依赖
org.springframework.boot
spring-boot-starter-parent
2.6.3
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
com.baomidou
mybatis-plus-boot-starter
3.5.2
mysql
mysql-connector-java
第二步,编写application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_springboot?serverTimezone=UTC
username: root
password: root
第三步,创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_user")
public class User {
private Integer id;
private String userName;
private String password;
private String name;
private Integer age;
private String email;
}
第四步,编写Mapper接口
public interface UserMapper extends BaseMapper {
}
第五步,添加@MapperScan注解
@SpringBootApplication
@MapperScan("cn.itcast.mp.mapper")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class,args);
}
}
第六步,编写测试类
@SpringBootTest
public class TestMyBatisSpringBoot {
@Autowired
private UserMapper userMapper;
@Test
public void testSelectList(){
List users = userMapper.selectList(null);
for(User user:users){
System.out.println(user);
}
}
}
/* *插入一条记录 *@Param entity 实体对象 */ int insert(T entity);
@Test
public void testInsert(){
/*
* result返回数据库受影响的行数
* */
User user=new User(null, "jiangting", "123321", "江停", 35, "[email protected]");
int result=userMapper.insert(user);
System.out.println("受影响的行数==>"+result);
//新增数据之后,自增长的id会回填到对象中
System.out.println("id==>"+user.getId());
}
运行结果:
可以看到,id的值是有问题的
这是因为plus插件中有各种id生成的策略,如果想要id自增,需要在自增主键上加注解:
@TableId(type= IdType.AUTO)
private Integer id;
再次运行,成功
在MP中通过@TableField注解可以指定字段的一些属性,常常解决的问题有三个:
①对象中的属性名和字段名不一致的问题(非驼峰)
②对象中的属性字段在表中不存在的问题
③查询时不返回该字段的值
example for ①:
@TableField(value="email") //指定数据表中的字段名
private String mail;
example for ②:
注意,如果对象中没有设置addresss(即为空),则该属性不会填入insert语句
@TableField(exist=false)
private String address; //在数据库表中不存在
example for ③:
@TableField(select=false)
private String password;
查询后发现password为空,不会被查询到:
在MP中,更新操作有2种,一种是根据id更新,另一种是根据条件更新
方法定义:
/*
* 根据ID修改
*
* @Param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity)
测试用例:
@Test
public void testUpdateById(){
User user=new User();
user.setId(7);
user.setMail("[email protected]");
user.setPassword("666666");
int i = userMapper.updateById(user);
System.out.println("i===>"+i);
}
方法定义:
/*
* 根据whereEntity 条件,更新记录
*
*@Param entity 实体对象(set 条件值,可以为null)
*@Param updateWrapper 实体对象封装操作类(可以为null,里面的entity用于生成where语句)
*/
int update(@Param("et") T entity, @Param("ew") WrapperupdateWrapper);
测试用例:
需要注意的是eq方法中要修改的字段名是数据库中的字段名
QueryWrapper版:
@Test
public void testUpdate(){
User user=new User();
user.setMail("[email protected]");
user.setPassword("660006");
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("user_name","zhangsan");
int i = userMapper.update(user,wrapper);
System.out.println("i=>"+i);
}
UpdateWrapper版:
@Test
public void testUpdate2(){
UpdateWrapper wrapper=new UpdateWrapper<>();
wrapper.set("age",21).set("password","999888") //更新的字段
.eq("user_name","zhangsan"); //更新的条件
//根据条件做更新
int i = userMapper.update(null, wrapper);
System.out.println("i==>"+i);
}
方法定义:
/*
* 根据 ID 删除
* @Param id 主键ID
*/
int deleteById(Serializable id);
测试用例:
@Test
public void testDeleteById(){
int i = userMapper.deleteById(8);
System.out.println("delete i==>"+i);
}
方法定义:
/*
* 根据 columnMap 条件,删除记录
*
* @Param columnMap 表字段map对象
*/
int deleteByMap(@Param("cm") MapcolumnMap);
测试用例:
根据map删除数据,多条件之间是and关系
@Test
public void testDeleteByMap(){
Map map=new HashMap<>();
map.put("user_name","zhangsan");
map.put("password","999888");
int i = userMapper.deleteByMap(map);
System.out.println("i=>"+i);
}
方法定义:
/*
* 根据 entity 条件,删除记录
*
* @Param wrapper 实体对象封装操作类(可以为null)
*/
int delete(@Param("ew") WrapperqueryWrapper);
测试用例:
用法1:
@Test
public void testDelete(){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("user_name","jiangting").eq("password","666666");
//根据包装条件做删除
int i = userMapper.delete(wrapper);
System.out.println("i====>"+i);
}
用法2:
@Test
public void testDelete(){
User user=new User();
user.setUserName("jiangting");
user.setPassword("123321");
QueryWrapper wrapper=new QueryWrapper<>(user);
//根据包装条件做删除
int i = userMapper.delete(wrapper);
System.out.println("i====>"+i);
}
方法定义:
/*
* 删除(根据 ID 批量删除)
*
* @Param idList 主键ID列表(不能为null 以及 empty)
*/
int deleteBatchIds(@Param("coll") Collection> idList);
测试用例:
@Test
public void testDeleteBatchIds(){
int i = userMapper.deleteBatchIds(Arrays.asList(5, 6));
System.out.println("i==>"+i);
}
MP提供了多种查询操作,包括根据id查询、批量查询、查询单条数据、查询列表、分页查询等操作
方法定义:
/*
* 根据 ID 查询
*
* @Param id 主键ID
*/
T selectById(Serializable id);
测试用例:
@Test
public void testSelectById(){
User user = userMapper.selectById(3);
System.out.println(user);
}
}
方法定义:
/*
* 查询(根据 ID 批量查询)
*
* @Param idList 主键ID列表(不能为null 以及 empty)
*/
ListselectBatchIds(@Param("coll") Collection extends Serializable> idList);
测试用例:
@Test
public void testSelectBatchIds(){
List users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
方法定义:
/*
* 根据 entity 条件,查询一条记录
*
* @Param queryWrapper 实体对象封装操作类(可以为null)
*/
default T selectOne(@Param("ew") WrapperqueryWrapper);
测试用例:
查询的数据超过一条时,会抛出异常
@Test
public void testSelectOne(){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("user_name","lisi");
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}
方法定义:
/*
* 根据Wrapper 条件,查询总记录数
*
* @Param queryWrapper 实体对象封装操作类(可以为null)
*/
Long selectCount(@Param("ew") WrapperqueryWrapper);
测试用例:
wrapper.gt(column,value); //表示大于value的条目值
同理有 ge 大于等于
le 小于等于
gt 大于
lt 小于
@Test
public void testSelectCount(){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.gt("age",22);
Long count = userMapper.selectCount(wrapper);
System.out.println(count);
}
方法定义:
/*
* 根据 entity条件,查询全部记录
*
* @Param queryWrapper 实体对象封装操作类(可以为null)
*/
ListselectList(@Param("ew") Wrapper queryWrapper);
测试用例:
@Test
public void testSelectList(){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.like("email","itcast");
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
方法定义:
/*
* 根据 entity 条件,查询全部记录(并翻页)
*
* @Param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @Param queryWrapper 实体对象封装操作类(可以为null)
*/
> P selectPage(P page, @Param("ew") Wrapper
queryWrapper);
配置分页插件:
@Configuration
@MapperScan("cn.itcast.mp.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor=new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
}
测试用例:
@Test
public void testSelectPage(){
Page page=new Page<>(3,1); //查询第一页,查询一条数据
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.like("email","itcast");
IPage userPage = userMapper.selectPage(page, wrapper);
System.out.println("数据总条数:"+userPage.getTotal());
System.out.println("数据总页数:"+userPage.getPages());
System.out.println("当前页数:"+userPage.getCurrent());
List records = userPage.getRecords();
records.forEach(System.out::println);
}
MP在启动后会将BaseMapper中的一系列方法注册到mappedStatement中,那么,是如何注入的呢?
在MP中,ISqlInjector负责SQL的注入工作,它是一个接口,AbstractSqlInjector是它的实现类,关系如下:
public interface ISqlInjector {
/*
*检查SQL是否注入(已经注入过不再注入)
*/
void inspectInject(MapperBuilderAssistant builderAssistant, Class> mapperClass);
}
在ISqlInjector的实现类AbstractSqlInjector中,通过下列语句循环注入自定义方法
if (CollectionUtils.isNotEmpty(methodList)) {
methodList.forEach((m) -> {
m.inject(builderAssistant, mapperClass, modelClass, tableInfo);
});
点进inject,在this.injectMappedStatement注入自定义方法
public void inject(MapperBuilderAssistant builderAssistant, Class> mapperClass, Class> modelClass, TableInfo tableInfo) {
this.configuration = builderAssistant.getConfiguration();
this.builderAssistant = builderAssistant;
this.languageDriver = this.configuration.getDefaultScriptingLanguageInstance();
this.injectMappedStatement(mapperClass, modelClass, tableInfo);
}
进入injectMappedStatement查看
public abstract MappedStatement injectMappedStatement(Class> mapperClass, Class> modelClass, TableInfo tableInfo);
是一个抽象方法,鼠标放在injectMappedStatement,点击Ctrl+H
可以看到,最终调用抽象方法injectMappedStatement进行真正的注入:
以SelectById为例:
public class SelectById extends AbstractMethod {
public SelectById() {
super(SqlMethod.SELECT_BY_ID.getMethod());
}
public SelectById(String name) {
super(name);
}
public MappedStatement injectMappedStatement(Class> mapperClass, Class> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
SqlSource sqlSource = new RawSqlSource(this.configuration, String.format(sqlMethod.getSql(), this.sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, true)), Object.class);
return this.addSelectMappedStatementForTable(mapperClass, this.getMethod(sqlMethod), sqlSource, tableInfo);
}
}
可以看到,生成了SqlSource对象,再将SQL通过addSelectMappedStement方法添加到mappedStatements中
在MP中有大量的配置,其中有一部分是MyBatis原生的配置,另一部分是MP的配置
MyBatis配置文件位置,如果你有单独的MyBatis配置,请将其路径配置到configLocation中。
mybatis-plus:
#指定全局的配置文件
config-location: classpath:mybatis-config.xml
mybatis mapper所对应的xml文件位置,如果你在mapper中有自定义方法(xml中有自定义实现),需要进行该配置,告诉mapper所对应的xml文件位置
mybatis-plus:
#指定Mapper.xml文件的路径
mapper-locations: classpath*:mybatis/*.xml
测试用例:
UserMapper.java
public interface UserMapper extends BaseMapper {
User findById(int id);
}
UserMapper.xml
测试类:
@Test
public void testFindById(){
User user = userMapper.findById(2);
System.out.println(user);
}
MyBatis 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后的Mapper对应的XML文件可以直接使用类名,而不用使用全限定的类名(即XML中调用的时候不用包含包名)
mybatis-plus:
#实体对象的扫描包
type-aliases-package: cn.itcast.mp.pojo
本部分(Configuration)的配置大都为MyBatis原生支持的配置,这意味着您可以通过MyBatis XML 配置文件的形式进行配置
类型:boolean
默认值:true
是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名(下划线命名)到经典Java属性名(驼峰命名)的类似映射
注意:
此属性在mybatis中默认值为false,在Mybatis-Plus中,此属性也将用于生成最终的SQL的select body
如果您的数据库命名符合规则,无需使用@TableField注解指定数据库字段名
该参数不能和mybatis-plus.config-location同时存在
mybatis-plus:
configuration:
#禁用自定的驼峰映射
map-underscore-to-camel-case: false
类型:boolean
默认值:true
全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为true
mybatis-plus:
configuration:
cache-enabled: false
全局默认主键类型,设置后,即可省略实体对象中的@TableId(type=IdType.AUTO)配置
全局的id生成策略
mybatis-plus:
global-config:
db-config:
id-type: auto
类型:String
默认值:null
表名前缀,全局配置后可省略@TableName()配置
mybatis-plus:
global-config:
db-config:
table-prefix: tb_
在MP中,Wrapper接口的实现类关系如下:
可以看到,AbstractWrapper和AbstractLambdaWrapper是重点实现。
接下来我们重点学习AbstractWrapper以及其子类
传入map集合:
@Test
public void testAllEq(){
Map params=new HashMap<>();
params.put("user_name","jiangting");
params.put("age",35);
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.allEq(params);
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
过滤器用法:
@Test
public void testAllEq(){
Map params=new HashMap<>();
params.put("user_name","jiangting");
params.put("age",35);
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.allEq(((k,v)->(k.equals("age")||k.equals("id"))),params);
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
eq:等于==
ne:不等于<>
gt:大于>
ge:大于等于>=
lt:小于<
le:小于等于<=
between:BETWEEN 值1 AND 值2
notBetween:NOT BETWEEN 值1 AND 值2
in:字段 IN (value.get(0),value.get(1),...)
notIn:字段 NOT IN (v0,v1,...)
测试用例:
@Test
public void testEq(){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("password","123321")
.ge("age",30)
.in("name","江停","张三","王五");
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
like:LIKE '%值%'
例:like ("name","王")==>name like '%王%'
notLike: NOT LIKE '%值%'
例:notLike("name","王")==>name not like '%王%'
likeLieft:LIKE '%值%'
例:likeLeft("name","王")==>name like '%王'
likeRight:LIKE '值%'
例:likeRight("name","王")==>name like '王%'
测试用例:
@Test
public void testEq1(){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.likeRight("name","江").like("email","123");
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
orderBy:
排序:ORDER BY 字段,...
例:orderBy(true,true,"id,"name") ===> order by id ASC,name ASC
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
测试用例:
@Test
public void testOrderByAgeDesc(){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.orderByDesc("age","id");
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
or:
拼接OR
主动调用or表示紧接着下一个方法不使用and连接(不调用or则默认为and连接)
and:
AND嵌套
例:and(i->i.eq("name","李白").ne("status","活着")) ===>and(name='李白' and status<>'活着')
测试用例:
@Test
public void testOr(){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("name","王五").or().eq("age",35);
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
运行结果:
在MP查询中,默认查询所有的字段,如果有需要也可以通过select进行指定字段
@Test
public void testSelect(){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("name","王五")
.or()
.eq("age",35)
.select("user_name","id","age");
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}