写在前面
我使用kotlin
开发已经6
年了,一路被kotlin
坑过来的,不过当下我觉得已经相对完美了(没有什么坑了)。
推荐java
开发者都可以学习一下kotlin
,理由就一个字快
(学的快,开发也快),同样的技术栈,同样的业务,kotlin
开发比java
最少快3
倍。
我不打算科普java
与kotlin
的区别和特性,有兴趣的就继续看。下面我会从java、springboot
等主流技术栈换成kotlin
来展示一个业务开发的过程。
一、开发环境
idea
、kotlin 插件
open jdk 8
maven
或gradle
(推荐用gradle
,它会自动配置帮你解决字节码生成时机的问题,在maven
下要一通配置)
二、创建项目
idea
中创建一个springboot
项目,语言选择kotlin
即可
三、创建数据表实体
我这里使用的是hibernate+jpa
,如果是用mybatis
,去掉相应注解,换成mybatis
注解或申明一个cofig clss
配置xml
即可
import com.fasterxml.jackson.annotation.JsonFormat
import org.hibernate.annotations.DynamicInsert
import org.hibernate.annotations.DynamicUpdate
import java.time.LocalDateTime
import javax.persistence.*
import com.kdj.server.domain.common.dto.ImagesDTO
import com.kdj.server.domain.converter.ListImagesDTOConverter
@Entity
@Table(
name = "user",
indexes = [
Index(name = "deleted", columnList = "deleted")
]
)
@DynamicUpdate
@DynamicInsert
data class UserModel(
@Id
@Column(name = "id", columnDefinition = "bigint(20) COMMENT 'ID,自增'")
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
@Column(name = "images", columnDefinition = "longtext COMMENT '图片'")
@Convert(converter = ListImagesDTOConverter::class)
var images: MutableList<ImagesDTO>? = null,
@Column(name = "deleted", columnDefinition = "bigint(2) DEFAULT 0 COMMENT '是否删除,0未删除 1已删除'", nullable = false)
var deleted: Boolean? = null,
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Column(name = "create_at", columnDefinition = "datetime COMMENT '创建时间'", nullable = false)
var createAt: LocalDateTime? = null
) {
/**
* 触发jpa update代码需要执行的逻辑
*/
@PreUpdate
fun preUpdate() {
}
/**
* 自动设置必要字段的值
*/
@PrePersist
fun prePersist() {
createAt= LocalDateTime.now()
deleted = false
}
}
配置application.yml
内容spring.jpa.hibernate.ddl-auto: update
,会自动根据表更改来建表增加字段。
当前方式需要注意以下问题:
- 索引只会在自动建表时新建,后面的索引更改不会自动应用
- 当前
ddl-auto:update
策略会增加表字段,不会删除和更改表字段,可以在生产使用,但是不建议。我们有必要保证线上的数据库表是非常精准的。 hibernate
的update
操作实际上是查询对比差异后再改动,在Batch update
时会有严重的性能问题以及jdbc
连接池爆满的情况。- 在
java
和kotlin
中都存在pId
这种属性声明会变成PID
的情况,要避免3
个字符的驼峰命名
四、使用spring data jpa
- 在项目的根目录
build.gradle.kts
中dependencies
下增加:
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
- 如果要加入
querydsl
- 在项目的根目录
build.gradle.kts
中plugins
下增加:
kotlin("kapt") version "1.4.20"
- 在项目的根目录
build.gradle.kts
中dependencies
下增加:
implementation("com.querydsl:querydsl-jpa:5.0.0") kapt("com.querydsl:querydsl-apt:5.0.0:jpa")
- 在项目的根目录
- 剩下的就是几乎一样了,按之前
java
使用spring data jpa
的方式,新建一个kotlin interface
来写Repository
即可
五、使用自定义的converter
当我们需要用一个字符串存储一个格式化的json
、数组或者对象时,可以使用这个自定义的converter
例子:在三、创建数据表实体中有一个images
属性
@Convert(converter = ListImagesDTOConverter::class)
var images: MutableList<ImagesDTO>? = null,
- 先编写
baseConverter
,来增加扩展能力
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.util.StringUtils
import java.lang.reflect.ParameterizedType
import javax.persistence.AttributeConverter
abstract class BaseConverter<E> : AttributeConverter<List<E>?, String?> {
private var clazz: Class<E> = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<E>
val objectMapper = ObjectMapper()
override fun convertToDatabaseColumn(list: List<E>?): String {
if (list == null || list.isEmpty()) return ""
return objectMapper.writeValueAsString(list)
}
override fun convertToEntityAttribute(json: String?): MutableList<E> {
if (!StringUtils.hasText(json)) return mutableListOf()
return objectMapper.readValue(json, getCollectionType(List::class.java, clazz))
}
/**
* 获取泛型的Collection Type
*
* @param collectionClass 泛型的Collection
* @param elementClasses 元素类
* @return JavaType Java类型
* @since 1.0
*/
private fun getCollectionType(collectionClass: Class<*>, vararg elementClasses: Class<*>): JavaType {
return objectMapper.typeFactory.constructParametricType(collectionClass, *elementClasses)
}
}
- 编写基于
ImagesDTO
类的List
Converter
import com.kdj.server.domain.common.dto.ImagesDTO
import javax.persistence.Converter
@Converter
class ListImagesDTOConverter : BaseConverter<ImagesDTO>()
到这里就完成了,用法在上面已经贴了2
次了
六、使用mapstruct
kotlin
的data
数据类已经有lombok
的所有功能,并且还有增强,一般情况下不需要使用mapstruct
的
- 在项目的根目录
build.gradle.kts
中plugins
下增加(有就不用加了):
kotlin("kapt") version "1.4.20"
- 在项目的根目录
build.gradle.kts
中dependencies
下增加:
implementation("org.mapstruct:mapstruct:1.5.0.RC1")
kapt("org.mapstruct:mapstruct-processor:1.5.0.RC1")
- 编写自定义
interface Mapstruct
import com.kdj.server.domain.model.UserModel
import com.kdj.server.support.minapp.domain.model.MinUserInfoModel
import org.mapstruct.Mapper
import org.mapstruct.Mapping
import org.mapstruct.MappingConstants
import org.mapstruct.Mappings
/**
* 注意这个类的 pId 要写成 PID 这种规则
*/
@Mapper(
componentModel = MappingConstants.ComponentModel.SPRING
)
interface UserModelMapstruct {
@Mappings(
Mapping(ignore = true, target = "id"),
Mapping(ignore = true, target = "deleted"),
Mapping(ignore = true, target = "createAt"),
Mapping(ignore = true, target = "roles"),
Mapping(source = "nickName", target = "name"),
Mapping(source = "headImgUrl", target = "avatar")
)
fun toUserModel(minUserInfoModel: MinUserInfoModel): UserModel
}
- 关于自定义的转换
场景:有些情况,我们希望在转换的过程中通过调用一些方法来得到一个结果,将这个结果传给目标。
@Mappings(
Mapping(
target = "releaseTime",
expression = "java(new TypeConversionWorker().toStrDate(kHLDetailsVO.getCreateTime(), DateStyle.YYYY_MM_DD))"
)
)
fun toServiceProvidersModel(kHLDetailsVO: KHLDetailsVO): ServiceProvidersModel
说明:
这个expression
评论区