基于kotlin的逆向插件

avatar

背景

来到新的公司,被告知要用kotlin开发

b9b521e3-14d6-437e-9ed4-8d866e6e65ef.jpg

看旧代码逻辑的时候,发现pojo dao mapper 每个人都有自己的编码习惯,不够规范,而且xml里面充斥着大量单表操作语句。

心想着入乡随俗吧,直到第一个需求分配下来到我这边,不算复杂,但是设计的表存在比较多的字段且逻辑简单均单表操作比较多。手写虽然不难,但也是挺费时间的。这一刻,我萌生了逆向插件的想法。(其实是想偷懒)

现有插件对比

市面上基于mybatis下的逆向插件有mybatis-generator-maven-plugin和mybatis-plus,生成的文件均是java文件,查阅了一下资料也没找到其他可以生成kotlin文件的插件

3ca40a69-2024-4377-967e-6d014d692796.gif

看了插件源码,发现对底层修改,我们还是可以生成我们需要的kotlin文件,下面对比一下这两款比较热门的插件

mybatis-generator-maven-plugin

mybatis比较官方的代码生成器,生成do,mapper,mapper.xml且生成大量mybatis动态sql。

生成文件方式: 代码操作文本文件生成,可以下载源码修改生成kotlin文件,逻辑较复杂,改动成本比较大

生成的mapper

image-20210911104319492.png

生成的Xml

image-20210911104501468.png

优点

1.生成代码后无需copy

2.配置简单

3.不需要与项目强依赖

缺点

1.所有单表的操作均依据id主键来

2.提供的操作太单一,大部分查询依旧是要写sql

3.不支持分页

4.通过xml来动态生成sql,对表结构修改不友好

5.不容易扩展

mybatis-plus-generator

mybatis-plus-generator是mybatis-plus配套使用的插件,生成丰富的mapper动态sql方法

生成文件方式:模板引擎生成的文件

image-20210913172257502.png

可以通过修改模板文件来自定义生成kotlin文件,只需要学习对应模板语法,修改成本低

image-20210913170130444.png

优点

1.生成代码后无需copy

2.提供了丰富的sql方法

3.提供了wrapper来丰富查询和修改

4.支持分页

5.sql走代码逻辑生成,不直接输出在xml,对后期数据库表结构修改友好

缺点

1.操作依旧是依靠主键,对于一些不以id作为主键操作的不友好

2.与项目强依赖

3.执行逻辑较复杂不好扩展

4.操作字段需要手写字段名,容易书写错误

image-20210913190937203.png

选型

前面两款插件,均都是围绕id为主键来查询以及修改。对我们公司已有逻辑不是很友好,接入并没有很方便。既然这样,为什么不手写一个插件,既能有针对性的定制开发,又能结合这两款的优点,扩展性更高,还相对轻量。想到就动手敲代码。

51a678a7-11f8-46ea-8e95-fdc46ff20b9b

设计思路

1.通过面对对象的编码方式,来操作单表操作数据库

2.在mybatis下AbstractSQL抽象类上层开发,动态生成sql

2.po、mapper、xml、queryWrapper等通过freemarker模板生成

3.封装常用操作方法 ,自定义sql或者多表连表操作依旧可以通过xml手写

生成sql方式

通过生成xml动态sql

优点:

1.实现简单

2.不需要过多的反射去生成sql

缺点:

1.数据库做了修改后,需要修改对应xml和po对象

2.重新生成xml文件又存在自定义sql,无法重置覆盖

3.生成的sql不够灵活

通过代码动态生成sql

优点:

1.数据库修改后,只需要修改对应的po和queryWrapper文件(或重新生成),其他的文件不需要修改

2.生成sql灵活

3.后续扩展新方法只需要扩展baseMapper,baseSqlBuild,不需要重新生成文件

mybatis底层用了Ognl去解析xml if表达式,参照其实现,代替反射获取方案

对比两种方式,优先选择代码动态生成

核心类

baseMapper

image-20210914102301972.png

BaseSqlBuilder

image-20210914102520670.png

BaseQueryWrapper

image-20210914102622195.png

开始使用

引入jar包

<dependency>
    <groupId>io.github.zequnchen</groupId>
    <artifactId>mybatis-component</artifactId>
    <version>1.0.1</version>
</dependency>

注意:插件基于mybatis,默认支持mysql,若需要oracle自行导入驱动

执行生成文件代码

fun main() {
    var myBatisCodeGen = MybatisCodeGen()

    // 数据库信息
    myBatisCodeGen.dbUrl =""
    myBatisCodeGen.dbName =""
    myBatisCodeGen.dbUserName = ""
    myBatisCodeGen.dbUserPassword = ""
    // 选择生成表的前缀
    myBatisCodeGen.tablePrefix = "t_"
    // 包含指定表 可多个表同时生成
    myBatisCodeGen.tableIncludes = arrayListOf("")
   // 实体类基础包名
    myBatisCodeGen.packageName ="com.mybatis.component"
//    myBatisCodeGen.subProjectPath="easiminder-server"
    // 类生成的包默认 com.mybatis.component.po com.mybatis.component.dao com.mybatis.component.vo
    // 可自定义配置包位置
//    myBatisCodeGen.entityPackage
//    myBatisCodeGen.mapperPackage
//    myBatisCodeGen.queryWrapperPackage
//    myBatisCodeGen.xmlPackage = ""
    // 是否重写覆盖po类和queryWarpper,其他类只会生成一次,可以删除后重新生成
    myBatisCodeGen.rewriteFile = true
    myBatisCodeGen.generate()
}

生成对应类

tableNameMapper: 继承baseMapper

tableNameMapperXml: xml

tableNamePo: 数据库po对象

tableNameQueryWarpper: 参数条件包装器(对应生成各个参数equles,in, like, <,<=,>,>=, between 条件控制)

配置

application.yml 配置文件添加数据库相关配置

spring.datasource.url =
spring.datasource.username =
spring.datasource.password =
spring.datasource.driverClassName =

操作说明

1.查询

(selectByKey, selectByWarpper, selectOneByWarpper, selectCountByWarpper)

selectByKey(id: Any): Po

根据唯一键(字段上有@UniqueKey注解)或者主键查找,在有唯一键注解的情况下使用唯一键

eg:

val deviceStrategyGroupInfoPo = deviceStrategyGroupInfoMapper.selectByKey("fc3af6b36d4f4ec99287597fa2c31f39")

生成sqlSELECT * FROM t_device_strategy_group_info WHERE (uid = #{uid})

selectByWarpper(queryWarpper: BaseQueryWarpper): List

根据条件warpper查找list记录

eg:

val deviceStrategyGroupInfoQuery = DeviceStrategyGroupInfoQueryWrapper()
deviceStrategyGroupInfoQuery.andStrategyNameEqules("测试")
deviceStrategyGroupInfoQuery.andIsDeletedEqules(1)
deviceStrategyGroupInfoQuery.andOperatorByEqules("111")
deviceStrategyGroupInfoQuery.selectColumn.addStrategyName().addUid().addStrategyName() 
//select字段做选择
val selectByWrapper = deviceStrategyGroupInfoMapper.selectByWrapper( deviceStrategyGroupInfoQuery)

生成的sql:
SELECT strategy_name, uid, strategy_name FROM t_device_strategy_group_info 
WHERE (strategy_name = #{query.param[0].value} AND 
is_deleted = #{query.param[1].value} AND 
operator_by = #{query.param[2].value} AND 
is_deleted = #{query.param[3].value})

selectOneByWarpper(queryWarpper: BaseQueryWarpper):Po

根据条件warpper查找一条记录

eg:

val deviceStrategyGroupInfoQuery = DeviceStrategyGroupInfoQueryWrapper()
deviceStrategyGroupInfoQuery.andStrategyNameEqules("测试")
deviceStrategyGroupInfoQuery.selectColumn.addStrategyName().addUid().addStrategyName()
deviceStrategyGroupInfoQuery.orderBy = "create_time desc"
val selectByWrapper = deviceStrategyGroupInfoMapper.selectOneByWrapper( deviceStrategyGroupInfoQuery)

生成的sql:
SELECT strategy_name, uid, strategy_name``FROM t_device_strategy_group_info
WHERE (strategy_name = #{query.param[0].value})
ORDER BY create_time desc LIMIT 1

selectCountByWarpper:Long

根据条件warpper获取count

eg:

val deviceStrategyGroupInfoQuery = DeviceStrategyGroupInfoQueryWrapper()
deviceStrategyGroupInfoQuery.andStrategyNameEqules("测试")
val count = deviceStrategyGroupInfoMapper.selectCountByWrapper( deviceStrategyGroupInfoQuery)

生成的sql:SELECT count(1) FROM t_device_strategy_group_info 
WHERE (strategy_name = #{query.param[0].value})

2.插入

(insert,insertOrUpdate,batchInsert)

insert(po:Po): Int

插入记录

eg:

val insert = deviceStrategyGroupInfoMapper.insert(deviceStrategyGroupInfoPo)

生成的sql:
INSERT INTO t_device_strategy_group_info(uid, school_id, strategy_name, strategy_type, create_time, update_time, is_deleted, status_config, operator_by)
VALUES (#{uid}, #{schoolId}, #{strategyName}, #{strategyType}, #{createTime}, #{updateTime}, #{isDeleted}, #{statusConfig}, #{operatorBy})

insertOrUpdate(po: Po): Int

根据id或唯一键(字段上有@UniqueKey注解)是否冲突决定插入或更新(更新会自动去掉create_time字段)

eg:

val insert = deviceStrategyGroupInfoMapper.insertOrUpdate(deviceStrategyGroupInfoPo)

生成的sql:
INSERT INTO t_device_strategy_group_info(uid, school_id, strategy_name, strategy_type, create_time, update_time, is_deleted)
VALUES (#{uid}, #{schoolId}, #{strategyName}, #{strategyType}, #{createTime}, #{updateTime}, #{isDeleted})
ON DUPLICATE KEY UPDATE
school_id = #{schoolId},
strategy_name = #{strategyName},
strategy_type = #{strategyType},
update_time = #{updateTime},
is_deleted = #{isDeleted},
status_config = #{statusConfig},
operator_by = #{operatorBy}

batchInsert(poList: List): Int

批量插入记录

eg:

val insert = deviceStrategyGroupInfoMapper.batchInsert(poList)
生成的sql:

INSERT INTO t_device_strategy_group_info(uid, school_id, strategy_name, strategy_type, create_time, update_time, is_deleted)
VALUES (#{poList[0].uid}, #{poList[0].schoolId}, #{poList[0].strategyName}, #{poList[0].strategyType}, #{poList[0].createTime}, #{poList[0].updateTime}, #{poList[0].isDeleted}),
(#{poList[1].uid}, #{poList[1].schoolId}, #{poList[1].strategyName}, #{poList[1].strategyType}, #{poList[1].createTime}, #{poList[1].updateTime}, #{poList[1].isDeleted}), 
(#{poList[2].uid}, #{poList[2].schoolId}, #{poList[2].strategyName}, #{poList[2].strategyType}, #{poList[2].createTime}, #{poList[2].updateTime}, #{poList[2].isDeleted})

3.更新

(updateByKey, updateByKeyWithNull,updateByWrapper,updateByWrapperWithNull)

updateByKey(po: Po):Int

根据id或唯一键(字段上有@UniqueKey注解)做修改 不包含空

eg:

val deviceStrategyGroupInfoPo = DeviceStrategyGroupInfoPo().apply {
this.uid = "fc3af6b36d4f4ec99287597fa2c31f39"
this.isDeleted = 1
this.updateTime = Date()}
val updateByKey = deviceStrategyGroupInfoMapper.updateByKey(deviceStrategyGroupInfoPo)

生成的sql:
UPDATE t_device_strategy_group_info
SET update_time = #{updateTime}, is_deleted = #{isDeleted}
WHERE (uid = #{uid})

updateByKeyWithNull(po: Po): Int

根据id或唯一键(字段上有@UniqueKey注解)做修改 包含空

eg:

val deviceStrategyGroupInfoPo = DeviceStrategyGroupInfoPo().apply {
this.uid = "fc3af6b36d4f4ec99287597fa2c31f39"
this.isDeleted = 1
this.updateTime = Date()}
val updateByKey = deviceStrategyGroupInfoMapper.updateByKey(deviceStrategyGroupInfoPo)

生成的sql:
UPDATE t_device_strategy_group_info
SET school_id = #{schoolId}, 
strategy_name = #{strategyName}, 
strategy_type = #{strategyType}, 
create_time = #{createTime}, 
update_time = #{updateTime}, 
is_deleted = #{isDeleted}, 
status_config = #{statusConfig}, 
operator_by = #{operatorBy}
WHERE (uid = #{uid})

updateByWrapper(po: Po, baseQueryWarpper: BaseQueryWarpper): Int

根据queryWrapper条件做修改 不包含空

eg:

val deviceStrategyGroupInfoPo = DeviceStrategyGroupInfoPo().apply {
this``.isDeleted = ``1
this``.updateTime = Date()}
val deviceStrategyGroupInfoQuery = DeviceStrategyGroupInfoQueryWrapper()
.andStrategyNameEqules("测试").andIsDeletedEqules(2)
.andStrategyTypeIn(arrayListOf(1,2,3))
val updateByKey = deviceStrategyGroupInfoMapper.updateByWrapper(deviceStrategyGroupInfoPo, deviceStrategyGroupInfoQuery)

生成的sql:
UPDATE t_device_strategy_group_info
SET update_time = #{po.updateTime}, 
is_deleted = #{po.isDeleted}
WHERE (strategy_name = #{query.param[0].value} AND 
is_deleted = #{query.param[1].value} AND 
strategy_type in (#{query.param[2].value[0]}, #{query.param[2].value[1]}, #{query.param[2].value[2]}))

updateByWrapperWithNull(po: Po, baseQueryWarpper: BaseQueryWarpper): Int

根据queryWrapper条件做修改 包含空

eg:

val deviceStrategyGroupInfoPo = DeviceStrategyGroupInfoPo().apply {
this.isDeleted = 1
this.updateTime = Date()}
val deviceStrategyGroupInfoQuery = DeviceStrategyGroupInfoQueryWrapper()
.andStrategyNameEqules("测试").andIsDeletedEqules(2)
.andStrategyTypeIn(arrayListOf(1,2,3))
val updateByKey = deviceStrategyGroupInfoMapper.updateByWrapper(deviceStrategyGroupInfoPo, deviceStrategyGroupInfoQuery)

生成的sql:
UPDATE t_device_strategy_group_info
SET school_id = #{po.schoolId}, 
strategy_name = #{po.strategyName}, 
strategy_type = #{po.strategyType}, 
create_time = #{po.createTime}, 
update_time = #{po.updateTime}, 
is_deleted = #{po.isDeleted}, 
status_config = #{po.statusConfig}, 
operator_by = #{po.operatorBy}
WHERE (strategy_name = #{query.param[0].value} AND 
is_deleted = #{query.param[1].value} AND 
strategy_type in (#{query.param[2].value[0]}, #{query.param[2].value[1]}, #{query.param[2].value[2]}))

4.删除

deleteByKey(id: Any)

根据id或唯一键(字段上有@UniqueKey注解)删除 需要有is_deleted+update_time字段

eg:

val delete = deviceStrategyGroupInfoMapper.deleteByKey("fc3af6b36d4f4ec99287597fa2c31f39") 

生成的sql:
UPDATE t_device_strategy_group_info
SET is_deleted = 1, 
update_time = now()
WHERE (uid = #{uid})

5.关于or

val deviceStrategyGroupInfoQuery = DeviceStrategyGroupInfoQueryWrapper()
.andStrategyNameEqules("测试")
.andIsDeletedEqules(2)
.or()
.andStrategyNameEqules("测试12")
.andIsDeletedEqules(3)
val selectByWrapper = deviceStrategyGroupInfoMapper.selectCountByWrapper( deviceStrategyGroupInfoQuery)

生成的sql:
SELECT * FROM t_device_strategy_group_info
WHERE 
(strategy_name = #{query.param[0].value} AND is_deleted = {query.param[1].value})
OR 
(strategy_name = #{query.param[3].value} AND is_deleted = #{query.param[4].value})