使用 kotlin + jdbcTemplate 写 dao 层
类orm的方案
编写dal层代码,java领域比较火的两个阵营:JPA(Hibernate)和MyBatis。JPA是java制定的一种ORM规范;MyBatis较轻量,还需要开发人员理解sql。
国内的情况,mybatis会更流行一些。能够胜出的点,大概是因为:可定制性更强,更易于进行性能优化。
但是,mybatis用久了,需要不断修改xml,又引起了很多人的不爽。随后基于mybatis之上的工具,如mybatis-plus,tk.mybatis便被提出来。使用lambda的函数,替代动态xml模板。
同时,采用类似做法的也有很多其他语言的框架,比如kotlin的Exposed、ktorm,golang的gorm。
简单介绍下这几个框架的用法:
- mybatis-plus和tk.mybatis比较类似,使用lambda函数描述sql中的一些语法含义,替代了mybatis的动态xml模板层
- Exposed和ktorm,kotlin写的,和上面两个比较像。一个比较明显区别,没有基于mybatis
spring jdbc
除了上面类orm的框架,还有spring提供的jdbcTemplate,对JDBC进行简单的封装,提供了数据层操作最基本的一些能力,诸如:动态数据源、返回结果到java对象的映射等。
spring jdbc的用法,没有动态xml,需要在java代码里手写sql。sql一长,换行拼接的方式,又非常不具有可读性。所以,被大规模正式地使用的情况,还是比较少。
所有,在github也有类似spring-data-mybatis-mini的小工具,基于spring jdbc + mybatis的xml动态模板,实现动态sql的能力。
那么,在上面两种方式之外,有没有一种中间路径,比orm更轻,比spring jdbc更易用的第三个选择?
第三种方案:kotlin + spring jdbc
先上代码:
@Repository
abstract class BaseDAO {
@Resource
private lateinit var sequenceService: SequenceService
fun createEntity(tableName: String, entityDO: EntityDO, jdbcInsert: SimpleJdbcInsert): Long {
if (entityDO.id == null) {
entityDO.id = sequenceService.nextValue(tableName)
}
entityDO.gmtCreate = Date()
entityDO.gmtModified = Date()
val affectRow: Number = jdbcInsert.execute(BeanPropertySqlParameterSource(entityDO))
if (affectRow != 1) {
throw RuntimeException("create jdbcInsert.execute fail")
}
return entityDO.id
}
}
@Repository
class TenantStaffDAO : BaseDAO() {
private val tableName: String = "tenant_staff"
@Resource
private lateinit var jdbcTemplate: JdbcTemplate
private lateinit var jdbcInsert: SimpleJdbcInsert
private lateinit var jdbcUpdate: SimpleJdbcUpdate
@PostConstruct
fun init() {
jdbcInsert = SimpleJdbcInsert(jdbcTemplate).withTableName(tableName)
jdbcUpdate = SimpleJdbcUpdate(jdbcTemplate).withTableName(tableName).ukColumns("tenant_id", "id")
}
fun create(staffDO: TenantStaffDO): Long {
return super.createEntity(tableName, staffDO, jdbcInsert)
}
fun get(tenantId: Long, staffId: Long): TenantStaffDO? {
val results = jdbcTemplate.query("select * from tenant_staff where tenant_id = ? and id = ?",
arrayOf(tenantId, staffId), BeanPropertyRowMapper(TenantStaffDO::class.java))
return DataAccessUtils.singleResult(results)
}
fun getByUid(tenantId: Long, uid: Long): TenantStaffDO? {
val results = jdbcTemplate.query("select * from tenant_staff where tenant_id = ? and uid = ?",
arrayOf(tenantId, uid), BeanPropertyRowMapper(TenantStaffDO::class.java))
return DataAccessUtils.singleResult(results)
}
fun batchGet(tenantId: Long, staffIdList: List<Long>): List<TenantStaffDO>? {
val sql = """
select * from tenant_staff
where tenant_id = ?
and id in ${staffIdList.joinToString(prefix = "(", separator = ", ", postfix = ")") { "?" }}
"""
val args = arrayListOf<Any>()
args.add(tenantId)
args.addAll(staffIdList)
return jdbcTemplate.query(sql, args.toArray(), BeanPropertyRowMapper(TenantStaffDO::class.java))
}
fun listStaffs(tenantId: Long, size: Int, offset: Long): List<TenantStaffDO>? {
val sql = "select * from tenant_staff where tenant_id = ? order by id desc limit ? offset ?"
return jdbcTemplate.query(sql, arrayOf(tenantId, size, offset), BeanPropertyRowMapper(TenantStaffDO::class.java))
}
fun listTenants(uid: Long): List<TenantStaffDO>? {
val sql = "select * from tenant_staff where uid = ?"
return jdbcTemplate.query(sql, arrayOf(uid), BeanPropertyRowMapper(TenantStaffDO::class.java))
}
fun singleUpdate(staffDO: TenantStaffDO): Boolean {
staffDO.gmtModified = Date()
return jdbcUpdate.singleUpdate(staffDO)
}
fun delete(tenantId: Long, id: Long): Int {
return jdbcTemplate.update("delete from tenant_staff where tenant_id = ? and id = ?", tenantId, id)
}
fun count(tenantId: Long): Int {
return jdbcTemplate.queryForObject("select count(*) from tenant_staff where tenant_id = ?",
arrayOf(tenantId), Int::class.java)
}
}
解决了dal层最基本需求
- 数据库结果到java对象映射
- 支持动态sql能力,比如in查询的列表,动态拼接
- 使用SimpleJdbcInsert进行DO对象的写入,可以做到不用关心表的字段
- 另外,spring-jdbc没有提供SimpleJdbcUpdate的实现,简单实现了一个,可以达到一样的效果:传入DO对象进行update
几个好处
- 和java混合编程,基本可以无缝衔接
- 直接编写程序员最熟悉的sql,而不是去理解一个xml或者lambda的转换层
- 不需要了解xml动态模板语法
- 特殊字符无需转义
- 底层基于spring jdbc,简单纯粹
- 借助kotlin的字符串模板功能,基本可以做到mybatis的xml模板同样的效果。支持if、for等
存在的问题
- java编写的DO类,如果使用了lombok,kotlin dao层代码在引用DO对象的属性时,会报编译错误。目前lombok和kotlin还无法在一起完美使用。解决办法是,在DO类里将kotlin代码使用的对象,手动生成setter、getter
另外,附上文中提到的几个框架的介绍链接