概述
- MyBatis-Plus(简称 MP),是一个 MyBatis 的增强工具包,只做增强不做改变.,为简化开发工作、提高生产率而生
- Mybatis-Plus 的集成非常简单,对于 Spring,我们仅仅需要把 Mybatis 自带的MybatisSqlSessionFactoryBean 替换为 MP 自带的即可
- CRUD指在做计算处理时的增加(Create)、查询(Retrieve)、更新(Update)和删除(Delete)几个单词的首字母简写
CRUD实现方式
- 基于 Mybatis
- 需要编写 xxxMapper 接口,并手动编写 CRUD 方法
- 提供 xxxMapper.xml 映射文件,并手动编写每个方法对应的 SQL 语句
- 基于 MP
- 只需要创建 xxxMapper 接口,并继承 BaseMapper 接口,这就是使用 MP需要完成的所有操作,甚至不需要创建 SQL 映射文件
- 重要对象
- Configuration: MyBatis 或者 MP 全局配置对象
- MappedStatement:一个 MappedStatement 对象对应 Mapper 配置文件中的一个select/update/insert/delete 节点,主要描述的是一条 SQL 语句,MP 在启动就会挨个分析 xxxMapper 中的方法,并且将对应的 SQL 语句处理好,保存到 configuration 对象中的mappedStatements 中
- SqlMethod : 枚举对象 ,MP 支持的 SQL 方法
- TableInfo:数据库表反射信息 ,可以获取到数据库表相关的信息
- SqlSource: SQL 语句处理对象
- MapperBuilderAssistant: 用于缓存、SQL 参数、查询方剂结果集处理等,通过 MapperBuilderAssistant 将每一个 mappedStatement添加到 configuration 中的 mappedstatements 中
- 全局策略配置(spring 的配置文件applicationContext.xml中)
注解
- @TableName:表名注解
- value:实体类中指明表名,默认实体类名要与表名一致
- resultMap:xml 中 resultMap 的 id
- @TableId:主键注解
- value:实体类中指明主键列列名,若与表一致则可省略
- type:主键类型(策略)
- @TableField:字段注解(非主键)
- value:实体类中指明数据库字段名
- el:映射为原生 #{ … } 逻辑,相当于写在 xml 里的 #{ … } 部分
- exist:是否为数据库表字段,默认为true
- condition:字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的
- update:字段 update set 部分注入(该属性优先级高于 el 属性)
- jdbcType:JDBC类型
- typeHandler:类型处理器
- numericScale:指定小数点后保留的位数
- @Version:乐观锁注解
- @EnumValue:通枚举类注解(注解在枚举字段上)
代码生成器
- MP 的代码生成器都是基于 java 代码来生成,Mybatis MBG代码生成器基于 xml 文件进行代码生成
- MyBatis 的代码生成器可生成: 实体类、Mapper 接口、Mapper 映射文件,MP 的代码生成器可生成: 实体类(可以选择是否支持 AR)、Mapper 接口、Mapper 映射文件、 Service 层、Controller 层
- 添加代码生成器依赖
com.baomidou
mybatis-plus-generator
3.3.1.tmp
org.apache.velocity
velocity-engine-core
2.2
org.freemarker
freemarker
2.3.30
com.ibeetl
beetl
3.1.1.RELEASE
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;
public class MyTest {
@Test
public void testGenerator() {
//1、此处默认有两个对应的实现类,不要导错包.import com.baomidou.mybatisplus.generator.config.GlobalConfig;
GlobalConfig globalConfig = new GlobalConfig();
//设置全局的配置
globalConfig.setActiveRecord(true) //是否支持AR模式
.setAuthor("lian") //设置作者
.setOutputDir("D:\\Git\\study_mybatis_plus\\1\\src\\main\\java") //设置生成路径
.setFileOverride(true) //设置文件覆盖
.setIdType(IdType.AUTO) //设置主键生成策略
.setServiceName("%sService") //设置生成的serivce接口的名字。 默认的名字首字母为I。 %sService :去掉I
.setBaseResultMap(true) //设置基本的结果集映射
.setBaseColumnList(true); //设置基本的列集合
//2、设置数据源的配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver")
.setUrl("jdbc:mysql://localhost:3306/demo?serverTimezone=UTC")
.setUsername("root")
.setPassword("123456");
// 3、进行策略配置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setCapitalMode(true)//设置全局大写命名
.setNaming(NamingStrategy.underline_to_camel)//数据库表映射到实体的命名策略
.setTablePrefix("tbl_")//设置表名前缀
.setInclude("tbl_emp");//生成的表
// 4、进行包名的策略配置
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("study2")
.setMapper("mapper")
.setService("service")
.setController("controller")
.setEntity("beans")
.setXml("mapper"); //和mapper接口放一起
//5、整合配置
AutoGenerator autoGenerator = new AutoGenerator();
autoGenerator
.setGlobalConfig(globalConfig)
.setDataSource(dataSourceConfig)
.setStrategy(strategyConfig)
.setPackageInfo(packageConfig);
//6、执行
autoGenerator.execute();
}
//可以在Controller层可以直接实现调用,这些调用的实现最核心的功能就在于ServiceImpl类,这个类中自动完成mapper的注入,同时提供了一系列CRUD的方法
}
CRUD 接口
Service CRUD 接口说明:
封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆
泛型 T 为任意实体对象
建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类
对象 Wrapper 为 条件构造器
- Service CRUD 接口
- Save方法
- boolean save(T entity); //插入一条记录(选择字段,策略插入)
- boolean saveBatch(Collection entityList); //插入集合
- boolean saveBatch(Collection entityList, int batchSize); //按批次数量插入集合
- SaveOrUpdate方法
- boolean saveOrUpdate(T entity); //TableId 注解存在则更新记录,不存在则插入记录
- boolean saveOrUpdate(T entity, Wrapper updateWrapper); //根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
- boolean saveOrUpdateBatch(Collection entityList); //批量修改插入
- boolean saveOrUpdateBatch(Collection entityList, int batchSize); //批量修改插入
- Remove方法
- boolean remove(Wrapper queryWrapper); //根据 entity 条件,删除记录
- boolean removeById(Serializable id); //根据 ID 删除
- boolean removeByMap(Map columnMap); //根据 columnMap 条件,删除记录
- boolean removeByIds(Collection extends Serializable> idList); //删除(根据ID 批量删除)
- Update方法
- boolean update(Wrapper updateWrapper); //根据 UpdateWrapper 条件,更新记录 需要设置sqlset
- boolean update(T entity, Wrapper updateWrapper); //根据 whereEntity 条件,更新记录
- boolean updateById(T entity); //根据 ID 选择修改
- boolean updateBatchById(Collection entityList); //根据ID 批量更新
- boolean updateBatchById(Collection entityList, int batchSize); //根据ID 批量更新
- Get方法
- T getById(Serializable id); //根据 ID 查询
- T getOne(Wrapper queryWrapper); //根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last(“LIMIT 1”)
- T getOne(Wrapper queryWrapper, boolean throwEx); //根据 Wrapper,查询一条记录
- Map getMap(Wrapper queryWrapper); //根据 Wrapper,查询一条记录
- V getObj(Wrapper queryWrapper, Function super Object, V> mapper); //根据 Wrapper,查询一条记录
- List方法
- List list(); //查询所有
- List list(Wrapper queryWrapper); //查询列表
- Collection listByIds(Collection extends Serializable> idList); //查询(根据ID 批量查询)
- Collection listByMap(Map columnMap); //查询(根据 columnMap 条件)
- List
- List
- List listObjs(); //查询全部记录
- List listObjs(Function super Object, V> mapper); //查询全部记录
- List listObjs(Wrapper queryWrapper); //根据 Wrapper 条件,查询全部记录
- List listObjs(Wrapper queryWrapper, Function super Object, V> mapper); //根据 Wrapper 条件,查询全部记录
- Page方法
- IPage page(IPage page); //无条件分页查询
- IPage page(IPage page, Wrapper queryWrapper); //条件分页查询
- IPage
- IPage
- Count方法
- int count(); //查询总记录数
- int count(Wrapper queryWrapper); //根据 Wrapper 条件,查询总记录数
- Chain方法
- QueryChainWrapper query(); //链式查询 例:query().eq(“column”, value).one();
- LambdaQueryChainWrapper lambdaQuery(); //链式查询 lambda 式 例:lambdaQuery().eq(Entity::getId, value).list();
- UpdateChainWrapper update(); //链式更改
- LambdaUpdateChainWrapper lambdaUpdate(); //链式更改 lambda 式
Service CRUD 接口说明:
通用 CRUD 封装BaseMapper接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器
泛型 T 为任意实体对象
参数 Serializable 为任意类型主键 Mybatis-Plus 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键
对象 Wrapper 为 条件构造器
- Mapper CRUD 接口
- Insert方法
- 支持主键自增的数据库插入数据获取主键值,主键自动与实体类主键属性映射,使用get方法即可获取
- int insert(T entity); //插入一条记录,根据实体类每个属性进行非空判断,只有字段不为空,才会写入sql语句中
- Delete方法
- int delete(@Param(Constants.WRAPPER) Wrapper wrapper); //根据 entity 条件,删除记录
- int deleteBatchIds(@Param(Constants.COLLECTION) Collection extends Serializable> idList); //删除(根据ID 批量删除)
- int deleteById(Serializable id); //根据 ID 删除
- int deleteByMap(@Param(Constants.COLUMN_MAP) Map columnMap); //根据 columnMap 条件,删除记录
- update方法
- int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper updateWrapper); //根据 whereEntity 条件,更新记录,updateWrapper实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
- int updateById(@Param(Constants.ENTITY) T entity); //根据 ID 修改,根据实体类每个属性进行非空判断
- Select方法
- T selectById(Serializable id); //根据 ID 查询
- T selectOne(@Param(Constants.WRAPPER) Wrapper queryWrapper); //根据 entity 条件,查询一条记录
- List selectBatchIds(@Param(Constants.COLLECTION) Collection extends Serializable> idList); //查询(根据ID 批量查询)
- List selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper); //根据 entity 条件,查询全部记录
- List selectByMap(@Param(Constants.COLUMN_MAP) Map columnMap); //查询(根据 columnMap 条件)
- List
- List selectObjs(@Param(Constants.WRAPPER) Wrapper queryWrapper); //根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
- IPage selectPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper); //根据 entity 条件,查询全部记录(并翻页)
- IPage
- Integer selectCount(@Param(Constants.WRAPPER) Wrapper queryWrapper); //根据 Wrapper 条件,查询总记录数
条件构造器
AbstractWrapper是QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类,用于生成 sql 的 where 条件,entity 属性也用于生成 sql 的 where 条件
以下参数boolean condition表示该条件是否加入最后生成的sql中
通过链式编程的方式拼接出sql语句
- AbstractWrapper
- allEq方法:全部eq(或个别isNull)
- allEq(Map params) //r为数据库字段名,v为字段值
- 例:allEq({id:1,name:“老王”,age:null}) → id = 1 and name = ‘老王’ and age is null
- allEq(Map params, boolean null2IsNull) //null2IsNull : 为true则在map的value为null时调用isNull方法,为false时则忽略value为null的
- 例:allEq({id:1,name:“老王”,age:null}, false) → id = 1 and name = ‘老王’
- allEq(BiPredicate filter, Map params) //filter : 过滤函数,是否允许字段传入比对条件中
- 例:allEq((k,v) -> k.indexOf(“a”) >= 0, {id:1,name:“老王”,age:null}) → name = ‘老王’ and age is null
- allEq(BiPredicate filter, Map params, boolean null2IsNull)
- 例:allEq((k,v) -> k.indexOf(“a”) >= 0, {id:1,name:“老王”,age:null}, false) → name = ‘老王’
- allEq(boolean condition, BiPredicate filter, Map params, boolean null2IsNull)
- isNull:字段 IS NULL
- isNotNull:字段 IS NOT NULL
- eq方法:等于 =
- eq(R column, Object val)
- eq(boolean condition, R column, Object val)
- ne方法:不等于 <>
- gt方法:大于 >
- ge方法:大于等于 >=
- lt方法:小于 <
- le方法:小于等于 <=
- between方法:BETWEEN 值1 AND 值2
- notBetween方法:NOT BETWEEN 值1 AND 值2
- like方法:LIKE '%值%
- like(R column, Object val)
- 例:like(“name”, “王”) → name like ‘%王%’
- notLike方法:NOT LIKE '%值%
- likeLeft方法:LIKE ‘%值’
- likeRight方法:LIKE ‘值%’
- in方法
- in(R column, Collection> value) //字段 IN (value.get(0), value.get(1), …)
- in(R column, Object… values) //字段 IN (v0, v1, …)
- notIn方法:字段 NOT IN …
- inSql方法
- inSql(R column, String inValue):字段 IN ( sql语句 )
- 例:inSql(“id”, “select id from table where id < 3”) → id in (select id from table where id < 3)
- notInSql方法:字段 NOT IN ( sql语句 )
- groupBy方法:GROUP BY 字段, …
- orderByAsc方法:ORDER BY 字段, … ASC
- orderByDesc方法:ORDER BY 字段, … DESC
- orderBy方法:ORDER BY 字段, …
- having方法
- having(String sqlHaving, Object… params):HAVING ( sql语句 )
- 例:having(“sum(age) > 10”) → having sum(age) > 10
- 例:having(“sum(age) > {0}”, 11) → having sum(age) > 11
- func方法:方便在出现if…else下调用不同方法能不断链
- func(Consumer consumer)
- 例: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(Consumer consumer)
- 例:and(i -> i.eq(“name”, “李白”).ne(“status”, “活着”)) → and (name = ‘李白’ and status <> ‘活着’)
- nested方法:
- nested(Consumer consumer)
- 例:nested(i -> i.eq(“name”, “李白”).ne(“status”, “活着”)) → (name = ‘李白’ and status <> ‘活着’)
- apply方法
- apply(String applySql, Object… params):拼接 sql
- 例:apply(“date_format(dateColumn,‘%Y-%m-%d’) = ‘2008-08-08’”) → date_format(dateColumn,‘%Y-%m-%d’) = ‘2008-08-08’")
- last方法
- last(String lastSql):无视优化规则直接拼接到 sql 的最后
- 只能调用一次,多次调用以最后一次为准 有sql注入的风险
- exists方法
- exists(String existsSql):拼接 EXISTS ( sql语句 )
- exists(“select id from table where age = 1”) → exists (select id from table where age = 1)
- notExists方法
- notExists(String notExistsSql):拼接 NOT EXISTS ( sql语句 )
- QueryWrapper
- 继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件及 LambdaQueryWrapper, 可以通过 new QueryWrapper().lambda() 方法获取
- select方法:设置查询字段
- select(String… sqlSelect)
- select(Predicate predicate)
- select(Class entityClass, Predicate predicate)
- 问题总结
QueryWrapper<SysUserInfo> query = new QueryWrapper<>();
query.eq("user_id", sysUserInfo.getUserId()).eq("state", "1");
SysUserInfo sysUserInfo = new SysUserInfo();
sysUserInfo.setState("5");
userInfoMapper.update(sysUserInfo,query);
userInfoMapper.update(null, new UpdateWrapper<SysUserInfo>().lambda()
.set(SysUserInfo::getState, "5")
.eq(SysUserInfo::getState, "1")
.eq(SysUserInfo::getUserId, sysUserInfo.getUserId())
);
插件扩展
- MyBatis 允许在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
- 插件原理
- 四大对象的每个对象在创建时,都会执行interceptorChain.pluginAll(),会经过每个插件对象的plugin()方法,目的是为当前的四大对象创建代理。代理对象就可以拦截到四大对象相关方法的执行,因为要执行四大对象的方法需要经过代理
分页插件
- 在spring.xml的sqlSessionFactoryBean添加如下配置引入插件
@Test
public void TestPage(){
Page page = new Page(1,2);
Page selectPage = empDao.selectPage(page, null);
List records = selectPage.getRecords();
System.out.println();
for (Emp e : records){
System.out.println(e);
}
System.out.println("获取总条数:"+page.getTotal());
System.out.println("当前页码:"+page.getCurrent());
System.out.println("总页码:"+page.getPages());
System.out.println("每页显示的条数:"+page.getSize());
System.out.println("是否有上一页:"+page.hasPrevious());
System.out.println("是否有下一页:"+page.hasNext());
//还能把查询到的结构封装到page对象中
page.setRecords(records);
}
乐观锁插件
- 当要更新一条记录的时候,希望这条记录没有被别人更新
- 实现方式
- 取出记录时,获取当前version,更新时,带上这个version,执行更新时, set version = yourVersion+1 where version = yourVersion,如果version不对,就更新失败
- 添加配置
- 修改实体类添加version字段、@Version注解、添加setter、getter方法并在表中添加version字段
@Version
private Integer version;
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
@Test
public void testOptimisticLocker(){
Emp emp = new Emp();
emp.setEmpno(1);
emp.seteName("老王");
emp.setVersion(1);
int i = empDao.updateById(emp);
System.out.println(i);
}
Sql 注入器
- 根据MybatisPlus 的 DefaultSqlInjector 和 AbstractMethod 可以自定义各种你想要的 sql ,在加载 mybatis 环境时就注入到全局中,相当于自定义 Mybatisplus 自动注入的方法(像BaseMapper的内置方法)
- 步骤
- 在 Mapper 接口中定义相关的 CRUD 方法
- 扩展 AbstractMethod 的 injectMappedStatement 方法,实现 Mapper 接口中方法要注入的 SQL
- 扩展 DefaultSqlInjector ,重写 getMethodList 方法,添加自定义方法
- 修改applicationContext.xml文件,在 MP 全局策略中,配置自定义注入器
- EmpDao类
package study.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import study.bean.Emp;
public interface EmpDao extends BaseMapper {
int deleteAll();
}
package study.injector;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
//这一步实现了mapper中对应方法的sql的功能实现。
public class deleteAll extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class> mapperClass, Class> modelClass, TableInfo tableInfo) {
/* 执行 SQL ,动态 SQL 参考类 SqlMethod */
String sql = "delete from " + tableInfo.getTableName();
/* mapper 接口方法名一致 */
String method = "deleteAll";
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return this.addDeleteMappedStatement(mapperClass, method, sqlSource);
}
}
package study.injector;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import java.util.List;
public class mySqlInjector extends DefaultSqlInjector {
@Override
public List getMethodList(Class> mapperClass) {
List methodList = super.getMethodList(mapperClass);
//增加自定义方法
methodList.add(new deleteAll());
return methodList;
}
}
@Test
public void testInjector(){
int ret = empDao.deleteAll();
System.out.println(ret);
}
公共字段填充
- metaobject: 元对象,是 Mybatis 提供的一个用于更加方便,更加优雅的访问对象的属性,给对象的属性设置值 的一个对象. 还会用于包装对象. 支持对 Object 、 Map、 Collection等对象进行包装,本质上 metaObject 获取对象的属性值或者是给对象的属性设置值,最终是要通过 Reflector 获取到属性的对应方法的 Invoker, 最终 invoke
- 步骤
- 注解填充字段 @TableFile(fill = FieldFill.INSERT)
- 自定义公共字段填充处理器MetaObjectHandler
- MP 全局注入 自定义公共字段填充处理器
- 添加 @TableFile注解
@TableField(fill = FieldFill.INSERT_UPDATE)
private String job;
- 自定义公共字段填充处理器MetaObjectHandler
package study.metaObjectHandler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
public class myMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//获取到需要被填充的字段的值
this.strictInsertFill(metaObject, "job", String.class, "IT"); // 起始版本 3.3.0(推荐使用)
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "job", String.class,"Teacher"); // 起始版本 3.3.0(推荐使用)
}
}
@Test
public void testMeta(){
int insert = empDao.insert(new Emp());
System.out.println(insert);
}
MyBatisPlus官方地址 http://mp.baomidou.com