springcloud里有关mybatis踩过的坑
springcloud里有关mybatis踩过的坑前言
java是进行应用开发非常好的一门语言,有很多成熟的开发框架。
springcloud是java语言实现微服务的最好的框架和微服务化的实践,很多中小厂都是使用springcloud来实现微服务的体系, 而mybatis作为这个解决方案中object mapping组件也是非常成熟的一种选择, 今天这个文章就来拆一拆,在mybatis和springcloud/springboot集成中遇到过的一些坑。
依赖包的引用
要做到autoconfiguration,自动去扫描Objec Map的classes文件和xml文件;必须引用 mybatis-spring-boot-starter, 否则不能扫描生效。可以用 mybatis-plus 的 mybatis-plus-boot-starter 替代。 (当然如果不是通过autoconfiguration的方式,自己去写starter或者通过显示API调用的方式的话除外。)
com.baomidou mybatis-plus-boot-starter
@MapperScan 的原罪
有关MapperScan支持多个包的问题,网上大部分说是通过 basePackage 指定扫描多个包,本身没问题,但不是一个完美的方案。原因如下:
(1)如果扫描的包名是一个较顶级的,那么,一些不是 mapper 的 interface 也会被处理成 mapper 而被创建成一个 spring bean,造成系统无法启动。
(2)缩小包的范围,最好是只配置 mapper 对应包,但如果把所有包含 mapper 的包罗列出来, 列表变得比较长,列表虽然可以用通配符解决一部分问题,但需要包包满足一些统一的规则。 同时,对于层级不同的情况,根本还是不能处理。
(3)mapscan写在了@SpringBootApplication这个入口主main类上,降低了扩展性,每增加或调整 mapper 包,都要到 @MapperScan 中去注册。业务代码的变更影响了框架性的代码。尤其是对于多模块的情况,这个问题更加的不好控制。 违背依赖的原则,本来框架不依赖业务,现在变成了框架反过来要因业务变化而调整代码。
如何来解决这个问题, 我在写db-spring-boot-starter这个微服务的底层子框架的时候,选择的方式是:
在 @MapperScan 中,使用 basePackage + annotationClass 组件解决问题。 所有的 mapper 上加上 @Mapper 注解。这是合理的,因为本身 mapper 属于一类特殊的接口,理应该要通过特殊注册进行区分。有了第1条,basePackage 就可以指定一个较顶级的包名了
通过上面两条,一方面解决了 mapper 包动态变化的问题,
因为业务上的代码一般会有一个统一的包前缀,典型的如 com.companyname, 那么就可以把它作为 basePackage,只需要配置一次,后面的变化都不受影响。
import org.apache.ibatis.annotations.Mapper; @Mapper public interface CityDao extends BaseMapper { }
##### mybatis-plus配置 #字段策略 IGNORED:"忽略判断",NOT_NULL:"非 NULL 判断"),NOT_EMPTY:"非空判断" mybatis-plus.global-config.db-config.field-strategy=NOT_NULL #逻辑删除配置 mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0 # 原生配置 mybatis-plus.configuration.map-underscore-to-camel-case=true mybatis-plus.configuration.cache-enabled=false mybatis-plus.mapper-locations=classpath:/mapper/*Mapper.xml mybatis-plus.typeAliasesPackage=${mapper.entity.path:com.joinsunsoft.**.model.entity}
分页的问题
关于mybatis的分页,目前有两个主流派系,一个是使用mybatis的pagehelper,pagehelper实现了一个mybatis的拦截器,在执行mybatis的query时,会使用拦截器的方式来加入分页的代码和处理。 另一个派系,是使用mybatisplus的PaginationInnerInterceptor来实现。 不论用两种方式里的哪一个方式,都必须,1. 要使用starter才能生效, 或者写自己的starter, 2. 只使用一个,要么使用pagehelper,要么就是用mybatisPlus里的PaginationInnerInterceptor。 每种方式都必须对应自己的调用方式,不能错用。
使用PageHelper
依赖包
com.github.pagehelper pagehelper-spring-boot-starter
配置参数
pagehelper.helper-dialect: mysql # 分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页 pagehelper.reasonable: true # 支持通过 Mapper 接口参数来传递分页参数 pagehelper.support-methods-arguments: true
PageHelper.startPage(pageNum, pageSize); // 设定当前页码,以及当前页显示的条数 List list = cityDao.selectByPage(); return list;
使用PaginationInnerInterceptor
依赖包
com.baomidou mybatis-plus-extension
starter加载
org.springframework.boot.autoconfigure.EnableAutoConfiguration= com.joinsun.central..db.config.DBAutoConfiguration, com.joinsun.central..db.config.JdbcSessionConfiguration
/** * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }
@Override public Page listCity(Page myPage) { //Page page = PageHelper.startPage(myPage.getPage(), myPage.getLinage()); Page cityList = cityDao.selectPage(myPage, null); return cityList; }
结束语
通过springcloud/springboot架构,java实现微服务的实施案例目前成功的非常多,目前有很多基于springcloud和spring cloud alibaba的框架衍生出来的springcloud的微服务框架都是把类似很多的各种类似mybatis,redis,es, oauth, jwt等组件而糅合在一起的开发框架,使我们开发微服务起来非常的方便,但是由于封装了这些细节的地方,出现问题,如果对本身这些基础的组件不熟悉的情况下,就很难去定位,导致这样的一些架构仅仅只是在学习springcloud框架有意义,真正的公司的微服务框架更多的是自研究的框架, 例如我们公司的微服务框架都是自行去研发的,这样也就有了很多踩坑的体验,有这样的踩坑的经历千万别害怕,也许这就是让我们对技术更深入研究的机会。
以后还会有更多的文章和大家一起来解析"拆坑"体会的经历。 欢迎大家持续关注。