多数据库支持改造指引

背景
改造方案
接口层改造
实体类改造
Dao层改造
Service层改造
配置层改造

MyBatis是一套优秀的轻量级ORM框架,相比JPA而言,用户可以非常灵活地去控制具体运行的SQL,但是对于基本的CRUD操作支持就不如JPA来的便捷。MyBatisPlus这个插件就很好地融合了MyBatis和JPA的优点,既提供了对基本CRUD操作的支持(单表CRUD不需要编写SQL),又不丢失MyBatis的任何特性,可以灵活控制项目中所有用到的SQL。另外,它封装了分页插件,并提供SQL注入拦截等非常实用的功能。具体可以参考MyBatisPlus的官方文档。

改造方案
接口层改造
为了减少代码重构的工作量,我们需要尽可能减少接口层的修改,即xxx-api模块中的代码。Dto类与枚举类一般不做变动,Service接口中方法的参数和返回类型等也尽量不要去修改。唯一需要修改的地方是,引用到Principal参数的方法,需要将Principal的类型由 java.security.Principal 修改为 cn.taifinance.core.security.Principal。在新的Principal类型中,除了基本的用户名、权限列表等数据,还附带了更多请求相关的信息,比如客户端IP地址,这个修改有助于今后向服务层传递更多的非业务数据。而在相关的Resource中,可以使用 SecurityUtils.buildPrincipal() 方法很方便地构造Principal对象,传递到服务层。

实体类改造
在当前主流的使用Spring Data JPA作为数据访问层的应用中,通常都使用JPA注解来建立实体类与数据库表的映射关系,用XML配置映射的很少。JPA注解使用虽然很方便,但是也带来了一定的代码侵入性,这导致了在切换数据库时,会需要对实体类进行修改。比较常见的一个例子是主键。在MySQL中,效率最高的主键生成策略是自增,基于MySQL的应用程序,大多会使用自增的ID作为主键。但是Oracle并不支持自增主键,如果需要达到MySQL类似的效果,在Oracle中就需要使用一个Sequence去生成一个ID。而JPA本身管理了主键的生成,对于这两种主键生成策略有着不同的配置:

// MySQL中ID生成策略配置
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

// Oracle中ID生成策略配置
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = “orgSeq”)
@SequenceGenerator(name = “orgSeq”, sequenceName = “seq_organization”)
MyBatisPlus也支持类似JPA的注解来配置实体类与表的映射以及主键的生成策略,但是它本身是基于MyBatis的,因此支持直接自定义插入操作使用的SQL。所以我们可以很轻易地为插入操作提供两种不同的SQL。

// MySQL版本的插入SQL配置

insert into cmn_organization
(
name,
admin_user_id,
warning_amount
)
values
(
#{name},
#{adminUserId},
#{warningAmount}
)

// Oracle版本的插入SQL配置


select seq_organization.nextval from dual

insert into cmn_organization
(
id,
name,
admin_user_id,
warning_amount
)
values
(
#{id},
#{name},
#{adminUserId},
#{warningAmount}
)

因此,在改造实体类时,我们需要做一定的配置,这些配置是MyBatisPlus注入基本CRUD的SQL时需要的,与MyBatis无关。

实体与表名的映射关系由 @TableName 定义,该注解中还可以定义具体封装数据所使用的ResultMap,这个在一对多关联查询时会用到。
实体ID字段需要用 @TableId 标示。如果该实体的主键生成策略为自增,则不要去指定type,把主键生成交由定制化SQL去完成。如果主键生成策略为UUID,则直接指定type为 IdType.UUID 即可,交给MyBatisPlus来完成主键的生成。
实体属性与表字段的映射关系由 @TableField 定义,若该属性在表中没有对应的字段,则需要将exists设为false,这个在一对多关联查询时会用到。
实体中原先存在多对一关联关系的,需要将该属性调整为简单的外键属性。比如原先User对象中关联了一个Organization对象,现在需要改为长整型的orgId。
实体中原先存在一对多/多对多关联关系的,视情况可保留、简化该属性,并在XML的ResultMap中增加collection配置,提供查询关联数据的SQL。
实体中原先存在多对多关联关系的,需要为中间表额外定义一个实体类。

// 可以根据Dto对象中的情况,简化集合属性的类型,比如原先Organization关联的是Platform对象的集合,
// 现在可以简化为PlatformId的集合,这样映射到Dto时可以更简便






select platform_id from cmn_org_platform where org_id = #{id} Dao层改造 Dao层改造是工作量比较大的部分之一。在原先的版本中,我们的Dao类都统一继承了IBaseRepository接口,并且类的命名约定以Repository结尾。在新的版本中,所有Dao类都需要继承自IBaseMapper接口,并且类的命名约定以Mapper结尾,这个也是MyBatis项目中比较常见的命名规范。

以Mapper来命名Dao类带来一个问题,我们项目中也使用了Mapstruct来映射实体对象与Dto对象,而目前MapStruct转换类也约定使用了Mapper结尾的类名,这样不可避免会造成混淆。为了能清晰地区分两种不同功能的类,在新的版本中,我们定义MapStruct转换器时,统一继承自IBaseConverter接口,并约定以Converter结尾作为类名,相应的,包名也由cn.taifinance.xxx.service.mapper修改为cn.taifinance.xxx.service.converter。在Service的实现类中,实现getMapper()方法来获取Dao类对象,实现getConverter()方法来获取Mapstruct转换器类对象。

另外,Dao类中极少数用到分页功能的方法,原先的分页参数类型由 org.springframework.data.domain.Pageable 改为 com.baomidou.mybatisplus.extension.plugins.pagination.Page,并且根据MyBatisPlus的要求,分页参数必须放在第一个。而返回值类型也需要由 org.springframework.data.domain.Page 改为 com.baomidou.mybatisplus.core.metadata.IPage。Spring分页对象与MyBatisPlus分页对象的转换,在Service父类中有提供相应的方法,可以在Service类中直接使用。

MyBatis的Dao类里的方法,若多于一个参数,则需要使用@Param来为参数命名,这样在具体XML里配置的SQL中,才能使用#{参数名}的语法来注入参数值。若方法只有一个参数,则不需要使用@Param。

最新版本的MyBatis支持以JDK8的Optional类作为返回值,使用了Optional作为返回值的方法不需要调整返回值类型,MyBatis会自动帮你封装。

在旧版本中使用@Query自定义JPQL/SQL的方法,在新版本中需要将SQL写到Mapper.xml中,这个也是实现多数据库切换的关键。MyBatis将数据访问层接口与具体的实现(具体的原生SQL)分离,使得可以通过切换不同数据库版本的Mapper.xml来更换具体调用的原生SQL,非常的清晰和便捷。在新版本中,MySQL的SQL都定义在mapper/mysql/xxxMapper.xml中,相应地,Oracle的SQL都定义在mapper/oracle/xxxMapper.xml中。

在MyBatisPlus的配置中,有一项为mybatis-plus.mapper-locations,它用来定义具体使用的Mapper.xml文件,一般它的值根据具体使用的数据库,设置为mapper/<数据库类型>/*.xml。

Mapper.xml的定义则和普通MyBatis工程没有区别,只是不需要再为基本的CRUD操作定义SQL(也可以自定义SQL来覆盖框架的标准行为)。目前由于时间关系,代码生成器暂时没有用上,后期会根据项目的实际情况定制代码模板,将代码生成器完善后,投入到实际的项目开发中,提高开发效率。

如果,也需要为那些多对多关系的中间实体定义对应的Dao类。大多数情况下会添加一些根据某个外键删除实体的SQL用来在之后维护多对多关联关系。

最后,如果实体ID为自增型的,需要参考上面的例子,在Mapper.xml中自定义insert方法的SQL。

Service层改造
服务层改造首先需要将原来的MapStruct转换器依照上面提到过的规范进行重命名。因为实体类中属性可能在改造中发生了变化,所以还可能需要调整部分@Mapping配置。

然后就是改造服务的实现类,首先之前版本覆盖的getRepository()和getEntityMapper()方法,需要相应地改为getMapper()和getConverter(),实例变量的名称也要做相应的修改。再者就是改造具体的服务方法,调整为调用新的Dao方法。

有一点需要提一下,MyBatis在维护一对多/多对多关联插入时,没有JPA那样智能,需要手动去维护子表的外键以及中间表的关联数据,具体可以参考TiCommon-Member中用户、角色、权限的关系维护逻辑。因此,当实体类中存在一对多和多对多关系时,需要手动覆盖onCreate和onUpdate方法去定义数据插入/更新的逻辑,来保障功能的完整性。

对于列表查询条件的动态构造,原来是通过覆盖createFilterSpec等方法,使用构造JPA Specification对象的方式实现。在新版本中,需要改造为MyBatisPlus提供的Wrapper对象,具体请参考MyBatisPlus文档中条件构造器一章。 有一点需要注意的是,Wrapper查询条件都只能针对单表进行,之前有些需要通过JOIN实现的条件,在改造时都需要调整为单表操作,一般的方法是额外去JOIN的表中查询一次数据。

最后总结一下服务实现类中,同样功能的方法,新旧名称的对应关系。

getRepository() getMapper() 获取Dao类对象
getEntityMapper() getConverter() 获取Mapstruct转换器对象
createFilterSpec() initQueryWrapperByFilters() 根据过滤参数创建查询条件
createUserSpec() initQueryWrapperByUser() 根据请求用户创建查询条件
配置层改造
配置层改造包括基本的Spring Boot配置文件和Liquibase文件。

对于基本的Spring Boot配置文件,只需要在application.yml中添加MyBatisPlus相关配置。

mybatis-plus:
mapper-locations: mapper/oracle/*.xml
type-aliases-package: cn.taifinance.common.member.domain
configuration:
jdbc-type-for-null: ‘null’
其中mapper-locations定义了Mapper.xml文件的路径,type-aliases-package定义了实体类的包名,这样在Mapper.xml中定义resultType时就不需要再把包名附上。最后的jdbc-type-for-null主要定义插入操作时,若值为null时,使用的JDBC类型。

而Liquibase文件则和Mapper.xml类似,需要分为MySQL和Oracle两个版本,相应的配置文件中也需要指定具体的master文件的路径。

liquibase:
change-log: classpath:config/liquibase/changelog/oracle/changelog-master.xml
不同数据库会对应有不同的changelog-master.xml作为入口,而changelog也需要为不同的数据库定义不同的版本。好在Liquibase本身是支持多数据库的,它会将具体的Liquibase类型转换为相应的数据库类型,因此大部分的配置是一致的,除了小部分,比如Oracle中获取日期使用sysdate,MySQL中则使用now()等。

数据文件则是多个数据库版本changelog所公用的,在之前的版本中已经重新规范了数据文件的命名,只保留版本号,不在引入changeset的编号,这样改动的原因是同一个版本不同数据库的changelog中,changeset的数目可能是对不上的。所以只需要知道这个数据文件是在哪个版本中使用即可。

最后,需要在启动类上添加@MapperScan注解指定Dao类的包名。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值