Mybatis-Plus BaseMapper自动生成SQL及MapperProxy
Spring+Mybatis + Mybatis-Plus 自定义无XML的sql生成及MapperProxy代理生成
问题产生背景
现在新服务ORM框架是使用mybatis3.4.6
、mybatis-plus2.2.0
。
最近在项目中偶然发现CouponRecord
实体类中增加了这样一行代码如下,导致在Service中调用this.selectCount出现NPE。当然出现NPE很好解决,直接判断下是否为null就OK了。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("coupon_record")
public class CouponRecord {
...
@TableField(value = "product_quantity")
private BigDecimal productQuantity;
public BigDecimal getProductQuantity() {
// 提交上的代码
return this.productQuantity.setScale(2, RoundingMode.HALF_DOWN);
// 解决方式如下
//return this.productQuantity == null ? null : this.productQuantity.setScale(2, RoundingMode.HALF_DOWN);
}
...
}
调用链:CouponRecordServiceImpl#count
->ServiceImpl#selectCount
->BaseMapper#selectCount
,主要代码如下:
ServiceImpl
的部分代码如下:
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
@Autowired
protected M baseMapper;
...
@Override
public int selectCount(Wrapper<T> wrapper) {
return SqlHelper.retCount(baseMapper.selectCount(wrapper));
}
...
}
BaseMapper
所有接口如下:
public interface BaseMapper<T> {
Integer insert(T entity);
Integer insertAllColumn(T entity);
Integer deleteById(Serializable id);
Integer deleteByMap(@Param("cm") Map<String, Object> columnMap);
Integer delete(@Param("ew") Wrapper<T> wrapper);
Integer deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
Integer updateById(@Param("et") T entity);
Integer updateAllColumnById(@Param("et") T entity);
Integer update(@Param("et") T entity, @Param("ew") Wrapper<T> wrapper);
T selectById(Serializable id);
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
T selectOne(@Param("ew") T entity);
Integer selectCount(@Param("ew") Wrapper<T> wrapper);
List<T> selectList(@Param("ew") Wrapper<T> wrapper);
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> wrapper);
List<Object> selectObjs(@Param("ew") Wrapper<T> wrapper);
List<T> selectPage(RowBounds rowBounds, @Param("ew") Wrapper<T> wrapper);
List<Map<String, Object>> selectMapsPage(RowBounds rowBounds, @Param("ew") Wrapper<T> wrapper);
}
我们在业务代码CouponRecordServiceImpl#count
中直接调用,可能会产生如下疑问?
- 我们没有配置XML为什么调用selectCount可以查询?既然可以查询那么生成的SQL长成什么样子?
- 通过看ServiceImpl中的代码,会发现是直接注入baseMapper,baseMapper明明是接口咋个就可以使用了呢?
对于工作了这么多年的老司机,猜也猜的出百分之八九十吧。在整理这篇文章之前,以前浏览过,我确实忘记的差不多了。感谢公司能提供给大家不管是组内分享还是部门分享机会,分享总会给自己和他人的很大进步。不扯淡这些了。下面将对此这些疑问来逐一解决。但是这里要说明下,这里只看我们关心的内容,其他比如在与spring整合后有些为什么要这样写,可以找学习spring组来做分享或者后面整理好文章后在分享。
框架是如何使用
任何框架学习,首先要会用,不然就是扯淡。框架都是在实际的应用中逐渐抽象出来的,简化我们工作。
Service主要代码如下:
@Service
public class CouponRecordService extends ServiceImpl<CouponRecordDao, CouponRecord> {
public int count(Date endTime) {
CouponRecord conditionCouponRecord = CouponRecord.builder().status(CouponStatus.USED).isDelete(YesNo.NO.getValue()).build();
return selectCount(new EntityWrapper<>(conditionCouponRecord).le("create_time", endTime).isNotNull("order_no"));
}
}
Dao(或者叫Mapper)
public interface CouponRecordDao extends BaseMapper<CouponRecord> {
}
spring的相关配置如下:
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath*:mapper/**/*.xml"/>
<property name="plugins">
<array>
<!-- 分页插件配置 -->
<bean id="paginationInterceptor" class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">
<property name="dialectType" value="mysql"/>
</bean>
<bean id="limitInterceptor" class="com.common.mybatis.LimitInterceptor"/>
</array>
</property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.merchant.activity.**.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<context:component-scan base-package="com.common.**,com.merchant.activity.**">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
用法+大致配置就是这样的。接下来看看这些无Xml的SQL是怎么生成的以及生成出来的SQL长成什么样?
无Xml的SQL是如何生成生成及SQL长成什么样
在如何使用中,可以看到XML中有如下一段配置:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.merchant.activity.**.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
这段的配置作用就是扫描我们的Mapper或者Dao的入口。
大概类图如下:
接下来对源码做分析
BeanDefinition解析阶段
MapperScannerConfigurer
MapperScannerConfigurer得继承关系如下图:
从图中看出MapperScannerConfigurer实现了我们关注的BeanDefinitionRegistryPostProcessor、InitializingBean接口,Spring在初始化Bean的时候会执行对应的方法。
ClassPathMapperScanner构造
构造ClassPathMapperScanner
扫描类,扫描basePackage包下的Mapper或者Dao并注册我们的Mapper Bean到容器中.
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
...
@Override
public void afterPropertiesSet() throws Exception {
// 验证是否配置了basePackage
notNull(this.basePackage, "Property 'basePackage' is required");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// left intentionally blank
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 是否有占位符,处理之
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 扫描
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
// 注册一些过滤器,包括和不包括。有部分可以在xml中配置,比如:annotationClass、markerInterface
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
...
}
ClassPathMapperScanner#scan
扫描类并生成BeanDefinition注入到Spring容器中,注意这里的ClassPathMapperScanner继承ClassPathBeanDefinitionScanner,在ClassPathMapperScanner中未实现scan,所以直接调用父类的scan方法。为了便于阅读这里将源码中的日志删除了。大致源码如下:
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
...
public int scan(String... basePackages) {
// 获取之前容器中bean的数量
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 真正干事的---扫描, 调用子类ClassPathMapperScanner#doScan(basePackages)方法
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
// 返回注册bean的数量
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
// 真正干事的扫描 生成BeanDefinition集合
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
// BeanDefinitionHolder 的集合
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
// 通过查找候选bean定义
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 遍历进行部分逻辑处理
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
// 设置作用域
candidate.setScope(scopeMetadata.getScopeName());
// 生成beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
// 增加默认值,autowireCandidate
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 注册BeanDefinition到容器中。
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
...
}
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类的ClassPathBeanDefinitionScanner#doScaner(basePackages)方法,扫描生产BeanDefinitionHolder集合
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
// MapperBean 需要一些额外的处理,查看这个方法
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
//对每个Mapper的BeanDefinition定义处理,
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 构造器参数,下一行代码将Bean设置为MapperFactoryBean,MapperFactoryBean的构造器中有个参数是mapperInterface
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
// 这一步非常重要,把我们的Bean设置为MapperFactoryBean,接下来会看到MapperFactoryBean的继承关系
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
// 在bean中增加sqlSessionFactory
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
// 在bean中增加sqlSessionTemplate
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
// 设置自动注入模式
if (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
}
写到代码的注释可能都不怎么关注,这里再次强调下重点,如果不注意后续可能有些会懵逼的。这是怎么来的。
- BeanDefinition的class设置为MapperFactoryBean
- 将原始mapper的接口类型以MapperFactoryBean构造器的参数传入,也就是后面你将看到参数是mapperInterface.
BeanDefinition初始化阶段
MapperFactoryBean
经过上面的扫描并注册,现在容器中已经存在了我们的Mapper Bean了,在上面的说构建Mapper BeanDefinition的时候注意这些BeanDefinition的class类型设置为了MapperFactoryBean,先看看MapperFactoryBean的继承关系如下:
从图中,看出MapperFactoryBean是实现了InitializingBean接口。DaoSupport对afterPropertiesSet()实现了。我们都知道Spring在初始化会Bean的时候将会调用afterPropertiesSet()方法。那么看看这个方法干了什么事
public abstract class DaoSupport implements InitializingBean {
protected final Log logger = LogFactory.getLog(getClass());
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// 检查Dao配置
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
protected abstract void checkDaoConfig() throws IllegalArgumentException;
protected void initDao() throws Exception {
}
}
一看典型的模板设计模式,真正处理在子类中。这里我们关心的是checkDaoConfig(),看看子类MapperFactoryBean#checkDaoConfig实现干了些什么事
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
...
protected void checkDaoConfig() {
super.checkDaoConfig();//调用父类的方法,父类就是检查sqlSession是否为null。null的话抛出异常
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
// 通过sqlSession获取MybatisConfiguration,相当于我们每一个MapperBean都是由SqlSession的,否则你想咋个查询呢
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 将mapperInterface注册到configuration中。
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
...
}
MybatisConfiguration#addMapper干的就是将类型注册到我们Mapper容器中,便于后续取
public class MybatisConfiguration extends Configuration {
...
public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mybatisMapperRegistry.addMapper(type);
}
...
}
接下来就要看看MybatisMapperRegistry#addMapper
注册到底干了何事。猜猜应该就是自定义无XML的sql生产注入。哪些是自定义?就是我们BaseMapper中的那一堆方法。
XXXRegistry 类的名字起的真好,看名字就是一个注册器。这里的注册器有一箭双雕的作用
- 定义了一个Map,缓存所知道的Mapper,后面初始化MapperProxy代理用的着,不然后面不好取哦
- 将解析出来的SQL,注册到Configuration中
public class MybatisMapperRegistry extends MapperRegistry {
...
// 这个knownMappers之前以为起的不够好。。当再次看的时候发现还真不错,known翻译就是众所周知,那么在这里就是我们已经扫描并且已经注册了的Mapper了,在内部来说当然是都知道的。
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
// 注入过就不再执行了。
if (hasMapper(type)) {
return;
}
boolean loadCompleted = false;
try {
// 这里先记着,后面查看我们MapperProxy代理用的着哦
knownMappers.put(type, new MapperProxyFactory<>(type));
// mybatisMapper注解构建器
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
// 解析
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
...
}
MybatisMapperAnnotationBuilder#parse
接下来将是生成无xml对应的SQL了。
Mybatis-Plus BaseMapper自动生成SQL及MapperProxy的更多相关文章
- 基于eclipse的mybatis映射代码自动生成的插件
基于eclipse的mybatis映射代码自动生成的插件 分类: JAVA 数据库 工具相关2012-04-29 00:15 2157人阅读 评论(9) 收藏 举报 eclipsegeneratori ...
- 基于eclipse的mybatis映射代码自动生成的插件http://blog.csdn.net/fu9958/article/details/7521681
基于eclipse的mybatis映射代码自动生成的插件 分类: JAVA 数据库 工具相关2012-04-29 00:15 2157人阅读 评论(9) 收藏 举报 eclipsegeneratori ...
- 使用Java注解开发自动生成SQL
使用注解开发的好处就是减少配置文件的使用.在实际过程中,随着项目越来越复杂,功能越来越多,会产生非常多的配置文件.但是,当配置文件过多,实际维护过程中产生的问题就不容易定位,这样就会徒劳的增加工作量. ...
- 使用Excel自动生成sql语句
在近一段日子里,进入了新的项目组,由于项目需要,经常要将一些Excel表中的数据导入数据库中,以前并没有过多的接触过数据导入与数据处理,对于我来说比较痛苦,今天下午花了几个小时处理数据,但是同事给我提 ...
- Eclipse 使用mybatis generator插件自动生成代码
Eclipse 使用mybatis generator插件自动生成代码 标签: mybatis 2016-12-07 15:10 5247人阅读 评论(0) 收藏 举报 .embody{ paddin ...
- springboot整合mybatis,利用mybatis-genetor自动生成文件
springboot整合mybatis,利用mybatis-genetor自动生成文件 项目结构: xx 实现思路: 1.添加依赖 <?xml version="1.0" e ...
- springboot+mybatis+mysql 利用mybatis自动生成sql语句
工具和环境 idea,mysql,JDK1.8 效果图如下 结构图如下 java resources sql文件 /* Navicat MySQL Data Transfer Source Serve ...
- spring和mybatis集成,自动生成model、mapper,增加mybatis分页功能
软件简介 Spring是一个流行的控制反转(IoC)和面向切面(AOP)的容器框架,在java webapp开发中使用广泛.http://projects.spring.io/spring-frame ...
- MyBatis使用Generator自动生成代码
MyBatis中,可以使用Generator自动生成代码,包括DAO层. MODEL层 .MAPPING SQL映射文件. 第一步: 配置好自动生成代码所需的XML配置文件,例如(generator. ...
随机推荐
- C# 连接 Socks5 代理
public class Socks5ProxyHelp { private Socks5ProxyHelp() { } public static string[] errorMsgs = { &q ...
- C#添加带验证的websevice接口
记录一下,方便下次使用,或者能帮助到别人. 一.添加服务引用,输入WSDL文件地址. 二.代码 public TESTClient TestContext() { var binding = new ...
- JS基础_函数的返回值
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- javaIO——BufferedWriter
[环境] jdk1.8 前面学习过 BufferedReader,是缓冲字符输入流.那么今天来学习对应的缓冲字符输出流类:BufferedWriter.跟 BufferedReader 同理,它也是一 ...
- 微信Emoji表情代码大全
参考网址 因PC端微信表情包不全,部分表情在PC中有显示问题,手机端微信不存在此问题,或者可以使用文字[微笑]这种方式添加微信表情 含义 标准 DoCoMo KDDI 软银 谷歌 微信 ✂复制这列
- fragment概念理解
fragment概念理解知识,fragment概念理解图片 fragment概念理解内容,fragment概念理介绍,fragment概念理正文 Fragment是Android honeycomb ...
- docker 第四篇 网络
安装docker以后自动添加三种网络方式 bridge: 表示桥接网络 (在本地自动创建一个软交换机) host: 表示让容器使用宿主机的网络名称空间 none: 表示没有网络 不能执行网络通信. 创 ...
- 解决Django项目静态资源无法访问的问题
静态资源无法访问 url.py中配置 from django.conf.urls import url from django.views import static from django.conf ...
- gitlab 错误处理
用gitolite新建项目,clone后首次push,可能会出现: $ git push No refs in common and none specified; doing nothing. Pe ...
- jQuery 实现手风琴菜单
main.js $(function(){ var tmp = null, $title = $('.title'), $con = $('.title > ul'); $title.click ...