背景
来到新的公司,被告知要用kotlin开发
看旧代码逻辑的时候,发现pojo dao mapper 每个人都有自己的编码习惯,不够规范,而且xml里面充斥着大量单表操作语句。
心想着入乡随俗吧,直到第一个需求分配下来到我这边,不算复杂,但是设计的表存在比较多的字段且逻辑简单均单表操作比较多。手写虽然不难,但也是挺费时间的。这一刻,我萌生了逆向插件的想法。(其实是想偷懒)
现有插件对比
市面上基于mybatis下的逆向插件有mybatis-generator-maven-plugin和mybatis-plus,生成的文件均是java文件,查阅了一下资料也没找到其他可以生成kotlin文件的插件
看了插件源码,发现对底层修改,我们还是可以生成我们需要的kotlin文件,下面对比一下这两款比较热门的插件
mybatis-generator-maven-plugin
mybatis比较官方的代码生成器,生成do,mapper,mapper.xml且生成大量mybatis动态sql。
生成文件方式: 代码操作文本文件生成,可以下载源码修改生成kotlin文件,逻辑较复杂,改动成本比较大
生成的mapper
生成的Xml
优点
1.生成代码后无需copy
2.配置简单
3.不需要与项目强依赖
缺点
1.所有单表的操作均依据id主键来
2.提供的操作太单一,大部分查询依旧是要写sql
3.不支持分页
4.通过xml来动态生成sql,对表结构修改不友好
5.不容易扩展
mybatis-plus-generator
mybatis-plus-generator是mybatis-plus配套使用的插件,生成丰富的mapper动态sql方法
生成文件方式:模板引擎生成的文件
可以通过修改模板文件来自定义生成kotlin文件,只需要学习对应模板语法,修改成本低
优点
1.生成代码后无需copy
2.提供了丰富的sql方法
3.提供了wrapper来丰富查询和修改
4.支持分页
5.sql走代码逻辑生成,不直接输出在xml,对后期数据库表结构修改友好
缺点
1.操作依旧是依靠主键,对于一些不以id作为主键操作的不友好
2.与项目强依赖
3.执行逻辑较复杂不好扩展
4.操作字段需要手写字段名,容易书写错误
选型
前面两款插件,均都是围绕id为主键来查询以及修改。对我们公司已有逻辑不是很友好,接入并没有很方便。既然这样,为什么不手写一个插件,既能有针对性的定制开发,又能结合这两款的优点,扩展性更高,还相对轻量。想到就动手敲代码。
设计思路
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
BaseSqlBuilder
BaseQueryWrapper
开始使用
引入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")
生成sql:
SELECT * 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})