前言
- 我自己的业务项目,先用kotlin+springboot 搭建, 发现gradle支持kts脚本,于是我就搭建试试。
- 我就选用了最流行的Sqlite内嵌数据库,虽然H2也不错,但是Sqlite才是最流行的。
- orm框架我还是选择了Mybatis-Plus ,为此中间踩了坑。
- 项目支持java+kotlin 混合编程, 没有配置好Lombok, 所以就没有集成了。
整个项目的代码我放在github中了,各位可以下载下来看看。
https://github.com/blanexie/vxpt
项目模块结构
项目由两个小模块组成,分别是 vxpt-bbs
和 vxpt-tracker
setting.gradle.kts 文件
kts脚本本质还是kotlin代码, 不支持单引号字符串。
gradle项目的组织结构文件, 选用的是kotlin的kts脚本。 内容如下:
rootProject.name = "vxpt"
include(":vxpt-bbs")
include(":vxpt-tracker")
父项目的build.gradle.kts 文件
需要注意的是kotlin的版本和gradle很多插件之间都有兼容关系, 版本号要对应上才能使用, 因此我只能使用1.5.10版本。 jdk使用的是11版本。
plugins {
id("org.springframework.boot") version "2.5.0"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.5.10"
kotlin("plugin.spring") version "1.5.10"
id("org.jetbrains.kotlin.plugin.noarg") version "1.4.20"
}
repositories {
mavenLocal()
maven {
setUrl("https://maven.aliyun.com/nexus/content/groups/public/")
}
mavenCentral()
}
subprojects {
apply(plugin = "java")
apply(plugin = "kotlin")
apply(plugin = "idea")
apply(plugin = "org.springframework.boot")
apply(plugin = "io.spring.dependency-management")
apply(plugin = "org.jetbrains.kotlin.plugin.spring")
apply(plugin = "org.jetbrains.kotlin.jvm")
apply(plugin = "org.jetbrains.kotlin.plugin.noarg")
repositories {
mavenLocal()
maven {
setUrl("https://maven.aliyun.com/nexus/content/groups/public/")
}
mavenCentral()
}
java.sourceCompatibility = JavaVersion.VERSION_11
configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib")
developmentOnly("org.springframework.boot:spring-boot-devtools")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
}
vxpt-bbs的子模块的build.gradle.kts文件
- 这里需要注意noArg的配置,是为了解决Mybtatis需要的对象的无参构造方法的问题。这个问题官方有解决方案,但是都是gradle的Groovy的脚本配置, 为此我各种尝试,才改成kts脚本的方式。
group = "com.github.blanexie.vxpt.bbs"
version = "0.0.1"
plugins {
}
// 必需的,为了解决kotlin数据类无参构造方法的问题,
noArg {
"com.baomidou.mybatisplus.annotation.TableName"
}
dependencies {
implementation("org.xerial:sqlite-jdbc:3.21.0.1")
implementation("com.baomidou:mybatis-plus-boot-starter:3.5.0")
implementation("org.mybatis:mybatis-typehandlers-jsr310:1.0.2")
implementation("com.alibaba:fastjson:2.0.23")
}
Mybatis数据对象
这里由于Mybatis的无参构造方法的问题,使用都报找不到UserDO的无参构造方法错误, 为此我尝试了多种方案,尝试方案如下:
- Spring的allOpen方案,配置无效,应该还是kts脚本的问题
- Lombok方案,配置无法成功,放弃。
- Java+Kotlin 混合方案,UserDO使用Java编写。 这个方案刚开始都无法编译Java类,尝试好久最后发现是我把
java
代码写在src/main/kotlin
目录下了,导致无法识别, 最后我把所有代码都放入src/main/java
目录下才成功,src/main/java
目录可以识别kotlin
代码和java
代码,但是src/main/kotlin
目录只能识别kotlin代码。 - Kotlin官方的noArg方案,刚开始也无法成功,计划要使用混合编程方案, 最后改了下配置成功了,官方教程中只有Groovy脚本,无kts脚本,我自己尝试出来的。
package com.github.blanexie.vxpt.bbs.user.meta.entity
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import java.time.LocalDateTime
@TableName("user")
class UserDO(
@TableId(type = IdType.AUTO)
var id: Long?,
var email: String,
var nickName: String,
var password: String,
var coverImg: String?,
var role: String,
var sex: Int,
var status: Int,
var createTime: LocalDateTime,
var updateTime: LocalDateTime,
) {
}
Mybtatis的数据对象的LocalDateTime序列化问题
LocalDateTime 是java8出来的时间对象,用于替换性能不好的Date类, 按理来说早就应该支持了, 但是我引入的最新的Mybatis-Plus包中的Mybatis就是无法识别,我尝试的解决方案如下:
- 自定义TypeHandler方案, 尝试到一半放弃,因为发现更好的方案, 但是这个方案应该也是行的通的
- 引入Mybatis的补丁包,
implementation("org.mybatis:mybatis-typehandlers-jsr310:1.0.2")
Http的HttpMessageConverters转换器
这个是在对象放回给浏览器端的时候,json序列化对象的设置类,我选用了fastjson,虽然他安全系数不高,但是他API好用啊,我业务项目,无所谓。 配置如下:
package com.github.blanexie.vxpt.bbs.util.config
import com.alibaba.fastjson.serializer.SerializeConfig
import com.alibaba.fastjson.serializer.SerializerFeature
import com.alibaba.fastjson.serializer.ToStringSerializer
import com.alibaba.fastjson.support.config.FastJsonConfig
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.http.HttpMessageConverters
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.http.MediaType
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import java.math.BigInteger
import java.nio.charset.Charset
@Configuration
@ConditionalOnClass(WebMvcConfigurer::class)
@Order(Ordered.HIGHEST_PRECEDENCE)
open class WebConfig : WebMvcConfigurer {
constructor() : super()
@Bean
open fun customConverters(): HttpMessageConverters {
//创建fastJson消息转换器
var fastJsonConverter = FastJsonHttpMessageConverter()
//创建配置类
var fastJsonConfig = FastJsonConfig()
//修改配置返回内容的过滤
fastJsonConfig.setSerializerFeatures(
// 格式化
SerializerFeature.PrettyFormat,
// 可解决long精度丢失 但会有带来相应的中文问题
//SerializerFeature.BrowserCompatible,
// 消除对同一对象循环引用的问题,默认为false(如果不配置有可能会进入死循环)
SerializerFeature.DisableCircularReferenceDetect,
// 是否输出值为null的字段,默认为false
SerializerFeature.WriteMapNullValue,
// 字符类型字段如果为null,输出为"",而非null
SerializerFeature.WriteNullStringAsEmpty,
// List字段如果为null,输出为[],而非null
SerializerFeature.WriteNullListAsEmpty
)
// 日期格式
fastJsonConfig.dateFormat = "yyyy-MM-dd HH:mm:ss"
// long精度问题
var serializeConfig = SerializeConfig.globalInstance
serializeConfig.put(Integer::class.java, ToStringSerializer.instance)
serializeConfig.put(BigInteger::class.java, ToStringSerializer.instance)
serializeConfig.put(Long::class.java, ToStringSerializer.instance)
serializeConfig.put(Long::class.javaObjectType, ToStringSerializer.instance)
fastJsonConfig.setSerializeConfig(serializeConfig)
//处理中文乱码问题
var fastMediaTypes = ArrayList<MediaType>()
fastMediaTypes.add(MediaType.APPLICATION_JSON)
fastMediaTypes.add(MediaType(MediaType.TEXT_HTML, Charset.forName("UTF-8")))
fastMediaTypes.add(MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8")))
fastMediaTypes.add(MediaType(MediaType.APPLICATION_FORM_URLENCODED, Charset.forName("UTF-8")))
fastMediaTypes.add(MediaType.MULTIPART_FORM_DATA)
fastJsonConverter.setSupportedMediaTypes(fastMediaTypes)
fastJsonConverter.setFastJsonConfig(fastJsonConfig)
//将fastjson添加到视图消息转换器列表内
return HttpMessageConverters(fastJsonConverter)
}
}
Sqlite配置问题
- Sqlite使用很方便,只要使用DBeaver工具创建一个sqlite数据,建好边,最后把数据库文件复制到resource目录下,就能直接使用了。
- Sqlite是内嵌数据库,会充分使用内存的,不用考虑数据库链接池(druid)等,并且druid还有一些配置不兼容sqlite, 所以我直接使用了默认的HikariPool连接池。
配置目录截图如下:
配置文件内容如下:
# Tomcat
server:
port: 8899
#spring
spring:
profiles:
active: dev
datasource:
#引用项目中的数据库文件
driver-class-name: org.sqlite.JDBC
url: jdbc:sqlite::resource:sqlite/bbs.db
username:
password:
#mybatis-plus配置控制台打印完整带参数SQL语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
UserMapper和UserController类如下
- UserMapper按照说明文档来,没有啥问题
- UserController类有个依赖注入的问题。 依赖注入有多种方式,我选择了
lateinit var
的方式
package com.github.blanexie.vxpt.bbs.user.controller
import com.github.blanexie.vxpt.bbs.user.dao.UserMapper
import com.github.blanexie.vxpt.bbs.user.meta.entity.UserDO
import com.github.blanexie.vxpt.bbs.util.WebResp
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDateTime
import javax.annotation.Resource
@RestController
@RequestMapping("/api/user")
class UserController {
//使用lateinit关键字的方式可以直接注入对象,也可以使用UserController的构造方法的方式注入对象
@Resource
lateinit var userMapper: UserMapper
@GetMapping("/login")
fun login(): WebResp {
var userDO = UserDO(
12, "12@vxpt", "12vxpt", "sagsdg", "https://21gsfsa", "user", 1, 1, LocalDateTime.now(), LocalDateTime.now()
)
userMapper.insert(userDO)
return WebResp.success(userDO)
}
@GetMapping("/logout")
fun logout(): WebResp {
var selectById = userMapper.selectById(12)
return WebResp.success(selectById)
}
@GetMapping("/register")
fun register(): WebResp {
return WebResp.success()
}
}
启动截图
启动控制台如下:
postman调用如下: