Mybatis Plus 入坑(含最新3.X配置)
简介
Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
使用它可以简化单表的操作, 节省开发时间, 国人写的文档已经非常通俗易懂了, 所以这里只是对其进行一些规范,便于多人协作开发
如果不了解mp, 请先阅读官方文档, 大约耗时半小时以内
mp最新的mybatis版本是3.4.6, mybatis-spring版本是1.3.2 比我们的全局配置稍微高了一些, 看了下相关的更新以及自测,发现是兼容的, 但是不排除有些操作不支持, 支持的操作已经足够简化开发了
现在mp最新的版本已经升级到3.X, 所以这里2.X的文档不在维护了
名词解释
GlobalConfig -> 指的是mp的全局配置文件, 版本2.X和 3.X的变化还是比较大, 可以直接看源码的注释
BaseMapper -> BaseMapper里面维护了许多常用的方法, 例如根据主键查询记录, 根据根据主键修改记录等等, 普通mapper只需要继承这个BaseMapper即可获得通用的方法
Wrapper -> 条件构造抽象类, 用来生成sql的条件
LogicSqlInjector -> 逻辑sql处理器, (逻辑删除)
PaginationInterceptor -> 分页插件, 底层是物理分页
实体类命名
GlobalConfig 中默认表名、字段名、使用下划线命名
eg. TrainCourse 对应的表名为 train_course
成员 courseType 对应表字段名 course_type
这些可以通过注解覆盖
类 : @TableName("对应的表名")
字段: @TableField("对应的字段名")
@Data
public class Course {
<span class="hljs-comment">/**
* 主键
*/</span>
<span class="hljs-meta">@TableId</span>(value = <span class="hljs-string">"course_id"</span>, type = IdType.AUTO)
<span class="hljs-keyword">private</span> Long courseId;
<span class="hljs-keyword">private</span> Integer courseType;
<span class="hljs-keyword">private</span> String title;
<span class="hljs-comment">/**
* 城市
*/</span>
<span class="hljs-keyword">private</span> String city;
<span class="hljs-comment">// 因为mp在使用的时候如果直接使用str, 等于就是使用了魔法值, 不便于维护, 所以统一在实体类中维护</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String COURSE_ID = <span class="hljs-string">"course_id"</span>;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String COURSE_TYPE = <span class="hljs-string">"course_type"</span>;
}
- 如果是表中不存在的字段 , 务必打上@TableField(exist = false)
- 如果是表示是否删除的字段, 如deleted, 则也要打上@TableLogic 名称和全局一样, 如果和数据库字段名不符合全局的映射, 则需要手动指定
- 数据库中只有0,1的值, 使用Boolean类型
XML和接口
xml和接口是完全兼容之前mybatis的写法, 所以不会影响之前的逻辑。如果xml手动定义的名称和mp的BaseMapper中定义的一样, 则手动指定的会覆盖BaseMapper中的。
即顺序为 自己在xml中定义的方法 > BaseMapper中的方法
如果仅仅使用到了BaseMapper中的方法, 则可以不配置XML配置文件
使用示例
在构建好实体类后, 不要在条件查询器中直接使用魔法值, 不容易维护
// 根据主键id查询
Coursecourse = mCourseMapper.selectById(id);
// 使用查询器
Wrapper<Course> query = new EntityWrapper<>();
if (courseType != null) {
// 这里的写法其实有点像jooq, 生成的sql为: where course_type = courseType
query.eq(Course.COURSE_TYPE, courseType);
}
query.eq(Course.NAME, name);
// deleted 使用 true 或者 false , 尽量不要使用0,1 因为0,1是魔法值
query.eq(Course.DELETED, true);
// 查询count的数量
int result = mCourseMapper.selectCount(query);
逻辑删除插件
一旦使用了逻辑删除插件, 以后默认的select都会带上 deleted = 0, 即只会查出不标记为删除的数据, 所以不需要手动在写
Wrapper<Test> query = new EntityWrapper<>();
query.eq(Test.DELETED, false); //这句话是多余的, 因为配置了逻辑删除插件, 默认在select的时候会加上 deleted = 0 这个条件
// 经过测试发现, 这会带来一个问题, 比如就是要查询删除的记录, 使用下面的这句话是无效的, 所以一旦使用了逻辑删除插件, 如果要查询删除了的记录, 请手写xml
query.eq(Test.DELETED, true); // 效果就是 WHERE deleted=0 AND (deleted = 1) 这样明显是查不出任何一条记录的
2.X版本
示例配置
mp只是用自己的 MybatisSqlSessionFactoryBean 替代了 mybatis-spring的 SqlSessionFactoryBean 其余的配置都不需要改动
再 globalConfig 中, 务必加入逻辑删除插件, 因为我们的大多数操作都是逻辑删除。物理删除请手动写xml。
3.X 版本
3.X版本和2.X版本的差别还是比较大的, 主要的差别如下
升级指南可以参考 https://my.oschina.net/u/241218/blog/1838534
其中有一点, 就是下面这个方法让我有点困惑
UpdateWrapper
方法名 | 说明 |
---|---|
set | SQL SET 字段(一个字段使用一次) |
因为发现在使用中并没有使用到这个方法, 连BaseMapper里面的update, 根据下面的解释, 也用不到, 猜测是要自己实现一个注入方法了, 这里没有深究
配置示例
这里采用java config的方式来配置(个人喜好, 加上java config可以做一些环境判断, 上面那个打印sql每次都需要手动打开注释, 麻烦)
Spring Boot的方式非常简单, 参考官网即可, 所以这里给出的是Spring mvc的配置方式, 使用的DataSource是阿里的druid
这里就直接贴配置文件了, 下面的包含了多数据源的配置
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.config.GlobalConfig.DbConfig;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
- @author yanghuan
*/
@Configuration
public class MybatisPlusConfig {
// 环境标志, 区分dev or prod
@Autowired
private String projectStage;
<span class="hljs-comment">/**
* 数据源a 相关信息
*/</span>
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${a.url}"</span>)
<span class="hljs-keyword">private</span> String aUrl;
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${a.username}"</span>)
<span class="hljs-keyword">private</span> String aUsername;
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${a.password}"</span>)
<span class="hljs-keyword">private</span> String aPassword;
<span class="hljs-comment">/**
* 数据源b 相关信息
*/</span>
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${b.url}"</span>)
<span class="hljs-keyword">private</span> String bUrl;
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${b.username}"</span>)
<span class="hljs-keyword">private</span> String bUsername;
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${b.password}"</span>)
<span class="hljs-keyword">private</span> String bPassword;
<span class="hljs-comment">// 创建数据源a</span>
<span class="hljs-meta">@Bean</span>(initMethod = <span class="hljs-string">"init"</span>, destroyMethod = <span class="hljs-string">"close"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> DruidDataSource <span class="hljs-title">aDataSource</span><span class="hljs-params">()</span></span>{
DruidDataSource d = <span class="hljs-keyword">new</span> DruidDataSource();
d.setUrl(aUrl);
d.setUsername(aUsername);
d.setPassword(aPassword);
<span class="hljs-keyword">return</span> d;
}
<span class="hljs-meta">@Bean</span>(initMethod = <span class="hljs-string">"init"</span>, destroyMethod = <span class="hljs-string">"close"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> DruidDataSource <span class="hljs-title">bDataSource</span><span class="hljs-params">()</span></span>{
DruidDataSource d= <span class="hljs-keyword">new</span> DruidDataSource();
d.setUrl(bUrl);
d.setUsername(bUsername);
d.setPassword(bPassword);
<span class="hljs-keyword">return</span> d;
}
<span class="hljs-comment">// 创建全局配置</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> GlobalConfig <span class="hljs-title">mpGlobalConfig</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// 全局配置文件</span>
GlobalConfig globalConfig = <span class="hljs-keyword">new</span> GlobalConfig();
DbConfig dbConfig = <span class="hljs-keyword">new</span> DbConfig();
<span class="hljs-comment">// 默认为自增</span>
dbConfig.setIdType(IdType.AUTO);
<span class="hljs-comment">// 手动指定db 的类型, 这里是mysql</span>
dbConfig.setDbType(DbType.MYSQL);
globalConfig.setDbConfig(dbConfig);
<span class="hljs-keyword">if</span> (!ProjectStageUtil.isProd(projectStage)) {
<span class="hljs-comment">// 如果是dev环境,则使用 reload xml的功能,方便调试</span>
globalConfig.setRefresh(<span class="hljs-keyword">true</span>);
}
<span class="hljs-comment">// 逻辑删除注入器</span>
LogicSqlInjector injector = <span class="hljs-keyword">new</span> LogicSqlInjector();
globalConfig.setSqlInjector(injector);
<span class="hljs-keyword">return</span> globalConfig;
}
<span class="hljs-meta">@Bean</span>(name = <span class="hljs-string">"aSqlSessionFactory"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> MybatisSqlSessionFactoryBean <span class="hljs-title">aSqlSessionFactory</span><span class="hljs-params">(
DruidDataSource aDataSource,
GlobalConfig globalConfig)</span> </span>{
<span class="hljs-keyword">return</span> getSessionFactoryBean(aDataSource, globalConfig);
}
<span class="hljs-comment">/**
* MapperScannerConfigurer 是 BeanFactoryPostProcessor 的一个实现,如果配置类中出现 BeanFactoryPostProcessor ,会破坏默认的
* post-processing, 如果不加static, 会导致整个都提前加载, 这时候, 取不到projectStage的值
*
* <span class="hljs-doctag">@return</span>
*/</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> MapperScannerConfigurer <span class="hljs-title">aMapperScannerConfigurer</span><span class="hljs-params">()</span> </span>{
MapperScannerConfigurer configurer = <span class="hljs-keyword">new</span> MapperScannerConfigurer();
configurer.setBasePackage(<span class="hljs-string">"com.a"</span>);
<span class="hljs-comment">// 设置为上面的 factory name</span>
configurer.setSqlSessionFactoryBeanName(<span class="hljs-string">"bSqlSessionFactory"</span>);
<span class="hljs-keyword">return</span> configurer;
}
<span class="hljs-meta">@Bean</span>(name = <span class="hljs-string">"bSqlSessionFactory"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> MybatisSqlSessionFactoryBean <span class="hljs-title">bSqlSessionFactory</span><span class="hljs-params">(
DruidDataSource bDataSource,
GlobalConfig mpGlobalConfig)</span> </span>{
<span class="hljs-keyword">return</span> getSessionFactoryBean(bDataSource, mpGlobalConfig);
}
<span class="hljs-function"><span class="hljs-keyword">private</span> MybatisSqlSessionFactoryBean <span class="hljs-title">getSessionFactoryBean</span><span class="hljs-params">(
aDataSource aDataSource,
GlobalConfig globalConfig)</span> </span>{
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = <span class="hljs-keyword">new</span> MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(aDataSource);
sqlSessionFactoryBean.setGlobalConfig(globalConfig);
<span class="hljs-comment">// 源码里面如果有configuration, 不会注入BaseMapper里面的方法, 所以这里要这样写</span>
MybatisConfiguration configuration = <span class="hljs-keyword">new</span> MybatisConfiguration().init(globalConfig);
configuration.setMapUnderscoreToCamelCase(<span class="hljs-keyword">true</span>);
sqlSessionFactoryBean.setConfiguration(configuration);
List<Interceptor> interceptors = <span class="hljs-keyword">new</span> ArrayList<>();
PaginationInterceptor paginationInterceptor = <span class="hljs-keyword">new</span> PaginationInterceptor();
<span class="hljs-comment">// 设置分页插件</span>
interceptors.add(paginationInterceptor);
<span class="hljs-keyword">if</span> (!ProjectStageUtil.isProd(projectStage)) {
<span class="hljs-comment">// 如果是dev环境,打印出sql, 设置sql拦截插件, prod环境不要使用, 会影响性能</span>
PerformanceInterceptor performanceInterceptor = <span class="hljs-keyword">new</span> PerformanceInterceptor();
interceptors.add(performanceInterceptor);
}
sqlSessionFactoryBean.setPlugins(interceptors.toArray(<span class="hljs-keyword">new</span> Interceptor[<span class="hljs-number">0</span>]));
<span class="hljs-keyword">return</span> sqlSessionFactoryBean;
}
<span class="hljs-comment">/**
* b 的mapperscan
* <span class="hljs-doctag">@return</span>
*/</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> MapperScannerConfigurer <span class="hljs-title">bMapperScannerConfigurer</span><span class="hljs-params">()</span> </span>{
MapperScannerConfigurer configurer = <span class="hljs-keyword">new</span> MapperScannerConfigurer();
configurer.setBasePackage(<span class="hljs-string">"com.b"</span>);
<span class="hljs-comment">// 设置为上面的 factory name</span>
configurer.setSqlSessionFactoryBeanName(<span class="hljs-string">"bSqlSessionFactory"</span>);
<span class="hljs-keyword">return</span> configurer;
}
}
使用方式和之前的类似, 只不过 EntityWrapper 改为了 QueryWrapper
以及有一些新的lambda方法
- 不需要定义一个静态变量了, 如果在成员变量上打了@TableField注解, 这里会取注解中的值, 否则就是globalConfig里面的
@Test
public void testPluralLambda() {
TableInfoHelper.initTableInfo(null, BdSystemUser.class);
QueryWrapper<BdSystemUser> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(BdSystemUser::getName,"sss");
queryWrapper.lambda().eq(BdSystemUser::getUserId,1);
BdSystemUser user = mBdSystemUserMapper.selectOne(queryWrapper);
}
- 之前2.X版本对复杂的or查询不能兼容, 即使用 Wrapper 的or方法, 默认是不加括号的
不过对于负责的查询, 还是推荐手写sql, 这里展示一下如何使用mp来构建
@Test
public void test() {
Wrapper<BdSystemUser> wrapper = new QueryWrapper<BdSystemUser>().lambda().eq(BdSystemUser::getName, 123)
.or(c -> c.eq(BdSystemUser::getExternalStaff, 1).eq(BdSystemUser::getUserId, 2))
.eq(BdSystemUser::getUserId, 1);
BdSystemUser user = mBdSystemUserMapper.selectOne(wrapper);
// 对应的sql: SELECT * FROM bdsystem_user WHERE deleted = 0 AND NAME = ? OR ( externalstaff = ? AND userid = ? ) AND userid = ?
// 可以看到, or中的语句是在括号里的
}
3.可以把where 后的条件防止在一个map中
@Test
public void testCompare() {
Map<String, Object> map = new HashMap<>();
map.put("userid", 1);
map.put("ldap", "luoshu");
QueryWrapper<BdSystemUser> queryWrapper = new QueryWrapper<BdSystemUser>()
.allEq(true, map, false);
BdSystemUser user = mBdSystemUserMapper.selectOne(queryWrapper);
// SELECT userid,name,ldap,stafftype,leader,departmenttype,groupid,externalstaff,mobile,created,lastmodified,deleted FROM bdsystem_user WHERE deleted=0 AND ldap = ? AND userid = ?
// 可以看到, map中对应的值, 是where后的条件
}
更详细的demo可以参照 demo
3.X 注意事项
- BaseMapper的selectOne() 现在在返回多条语句的时候,会抛异常, 需要手动在条件中处理, 使用wrapper.last()方法.
- 如果mp和tddl这样的中间件进行集成, 在分页插件的创建时候, 需要指定数据库方言, 之前可以不指定的原因是mp通过解析jdbc url的时候可以获取到对应的方言
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 这边因为接了tddl, 必须手动设置方言, 否则分页会抛异常
paginationInterceptor.setDialectType(DbType.MYSQL.getDb());
原文地址:https://www.cnblogs.com/hinsy/p/9668684.html