文章目录

  • 前言
  • 一、项目示例代码
  • 1.build.gradle
  • 2.gradle.properties
  • 3.application.yml
  • 4.Application.kt
  • 5.core/api/KtorPlugin
  • 6.core/api/KtorRouter
  • 7.core/api/configuration
  • 8.configuration/MybatisPlus
  • 9.field/entity/UserDo
  • 10.field/dao/mapper/IUserMapper
  • 11.field/handler/MyMetaObjectHandler
  • 12.field/service/IStudentService
  • 13.field/service/impl/StudentServiceImpl
  • 14.plugin/Jackson
  • 15.controller/BaseController
  • 16.controller/Hello
  • 总结

前言

 最近对sprinp data jpa与mybatis-plus做了对比研究,基于流行度与灵活性而言选择了mybatis-plus,jpa个人感觉也还不错不过对于人员要求可能稍微高一点,本篇文章主要记录使用springboot整合ktor的使用,maven做为项目构建很好但是不如gradle简单,由于开发采用kotlin做为开发语言所以使用的技术栈为:springboot + ktor + mybatis-plus + kotlin + gradle,之前打算使用koin替代spring的,但是mybatis-plus与springboot整合比较方便就懒得选择了。

一、项目示例代码

1.build.gradle

plugins {
    id 'org.springframework.boot' version "$spring_version"
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
//    id 'java'

    id 'org.jetbrains.kotlin.jvm' version "$kotlin_version"
    id "org.jetbrains.kotlin.plugin.spring" version "$kotlin_version"
    id "org.jetbrains.kotlin.plugin.noarg" version "$kotlin_version"
    id "org.jetbrains.kotlin.plugin.allopen" version "$kotlin_version"
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

noArg {
//    annotation("org.xunlongkj.fyc.annotation.KtNoArg")
    annotation("com.baomidou.mybatisplus.annotation.TableName")
}
allOpen {
//    annotation("org.xunlongkj.fyc.annotation.KtAllOpen")
}

repositories {
    maven { url 'https://maven.aliyun.com/repository/public' }
    maven { url 'https://maven.aliyun.com/repository/central' }
    maven { url 'https://maven.aliyun.com/repository/google' }
    maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
    maven { url 'https://maven.aliyun.com/repository/spring' }
    maven { url 'https://maven.aliyun.com/repository/spring-plugin' }
    maven { url "https://maven.pkg.jetbrains.space/public/p/ktor/eap" }
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // ktor
    implementation "io.ktor:ktor-server-core-jvm:$ktor_version"
    implementation "io.ktor:ktor-server-content-negotiation-jvm:$ktor_version"
    implementation "io.ktor:ktor-serialization-jackson-jvm:$ktor_version"
    implementation "io.ktor:ktor-server-netty-jvm:$ktor_version"

    // kotlin
    implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
    implementation 'org.jetbrains.kotlin:kotlin-reflect'
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'

    //
    // mybatis
    implementation "com.baomidou:mybatis-plus-boot-starter:$mybatis_version"
    implementation "com.baomidou:mybatis-plus-generator:$mybatis_version" // 代码生成器
    implementation "org.apache.velocity:velocity:$velocity_version" // 代码生成器需要的默认模板引擎
    //mysql数据库依赖
    runtimeOnly 'mysql:mysql-connector-java'
    implementation "com.alibaba:druid-spring-boot-starter:$druid_spring_version"
}

tasks.withType(JavaCompile) {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

bootJar {
    archiveBaseName.set("demo")
    archiveVersion.set("1.0.0")
}

jar {
    enabled = true
}

2.gradle.properties

用于版本管理,gradle会默认加载‘gradle.properties的配置’(示例):

ktor_version=2.0.2
kotlin_version=1.7.0
spring_version=2.7.0
mybatis_version=3.5.2
velocity_version=1.7
druid_spring_version=1.2.11

3.application.yml

程序配置文件

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/fastapi_t1
    username: admin
    password: admin2022
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver


mybatis-plus:
  global-config:
    db-config:
      # 主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID"
      id-type: auto
      # 表名是否使用驼峰转下划线命名,只对表名生效
      table-underline: false
      logic-not-delete-value: 0
      logic-delete-value: 1
  configuration:
    # 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射。
    map-underscore-to-camel-case: false
    # 打印日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4.Application.kt

程序入口文件

package com.example.springbootktor1

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args)
//    initDbConf()
}

// 使用mybatis-plus 自动生成代码
// 需要的依赖 ['com.baomidou:mybatis-plus-generator', 'org.apache.velocity:velocity']
//fun initDbConf() {
//    val path = System.getProperty("user.dir")
//
//    FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/fastapi_t1", "admin", "admin2022")
//        .globalConfig { globalConfig ->
//            globalConfig.outputDir("$path/src/main/java") // 指定生成路径, 由于只能生成java数据, 所以暂时生成在java目录下
//            globalConfig.disableOpenDir() // 不打开输出目录
//            globalConfig.author("fyc") // 设置作者
//        }
//        .packageConfig { packageConfig-> // 配置包相关的信息
//            packageConfig.parent("com.xunlongkj.fyc.ssmt1") // 设置父包名
//            packageConfig.moduleName("mybatisPlus") // 设置父包模块名
//        }
//        .strategyConfig { strategyConfig-> // 配置策略
//            strategyConfig.entityBuilder()
//                .naming(NamingStrategy.underline_to_camel) // 数据库表名映射成java类的名称
//                .columnNaming(NamingStrategy.underline_to_camel) // 数据库字段映射成java 成员变量的名称
//                .enableTableFieldAnnotation() // 开启生成实体时生成字段注解
//                .enableLombok() // 开启lombok注解
//        }
//        .execute() // 执行
//}

5.core/api/KtorPlugin

package com.example.springbootktor1.core.api

import io.ktor.server.application.*

interface KtorPlugin {
    /**
     * 注册中间件
     */
    fun Application.regPlugin()
}

6.core/api/KtorRouter

package com.example.springbootktor1.core.api

import io.ktor.server.routing.*

interface KtorRouter {
    /**
     * 注册路由
     */
    fun Route.regRouter()
}

7.core/api/configuration

package com.example.springbootktor1.core.configuration

import com.example.springbootktor1.core.api.KtorPlugin
import com.example.springbootktor1.core.api.KtorRouter
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.routing.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class Ktor {

    @Autowired
    private lateinit var applicationContext: ApplicationContext

    @Bean
    fun ktorEngine() {
        embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
            val plugins = applicationContext.getBeansOfType(KtorPlugin::class.java).values
            val routes = applicationContext.getBeansOfType(KtorRouter::class.java).values

            // 注册模块
            plugins.forEach {
                it.apply { regPlugin() }
            }

            // 注册路由
            routing {
                routes.forEach {
                    it.apply { regRouter() }
                }
            }
        }.start(wait = true)
    }
}

8.configuration/MybatisPlus

package com.example.springbootktor1.configuration

import com.baomidou.mybatisplus.annotation.DbType
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor
import org.mybatis.spring.annotation.MapperScan
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.transaction.annotation.EnableTransactionManagement

// MybatisPlus 配置注入springboot
@Configuration
@EnableTransactionManagement
@MapperScan("com.example.springbootktor1.**.dao.mapper") // 注入mybatis-plus到spring容器中
class MybatisPlus {

    /**
     * mybatis-plus分页插件, 老版本已经失效
     */
    @Bean
    fun paginationInnerInterceptor(): MybatisPlusInterceptor {
        val interceptor = MybatisPlusInterceptor()
        interceptor.addInnerInterceptor(PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor
    }

    /**
     * 乐观锁mybatis插件
     */
    @Bean
    fun optimisticLockerInnerInterceptor(): MybatisPlusInterceptor {
        val interceptor = MybatisPlusInterceptor()
        interceptor.addInnerInterceptor(OptimisticLockerInnerInterceptor())
        return interceptor
    }
}

9.field/entity/UserDo

package com.example.springbootktor1.field.entity

import com.baomidou.mybatisplus.annotation.*
import java.util.*

enum class EUser(
    @EnumValue
    val code: Int,
    val msg: String
    )
{
    REST(0, "休息"),
    WORK(1, "上班"),
}

@TableName("student")
data class UserDo(
    @TableId
    var id: Long?,
    var name: String?,
    var age: Int?,
    @TableField("is_delete")
    var isDel: Boolean = false,
    @TableField("create_time", fill = FieldFill.INSERT)
    var createAt: Date?,
    @TableField("update_time", fill = FieldFill.INSERT_UPDATE)
    var updateAt: Date?,
    @Version
    var version: Int?,
    var status: EUser = EUser.WORK,
    @TableLogic
    var deleted: Int = 0,
) {
    companion object {
        fun new(name: String, age: Int): UserDo {
            return UserDo(null, name, age, false, null, null, null)
        }
    }
}

10.field/dao/mapper/IUserMapper

package com.example.springbootktor1.field.dao.mapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper
import com.example.springbootktor1.field.entity.UserDo

interface IUserMapper: BaseMapper<UserDo>

11.field/handler/MyMetaObjectHandler

package com.example.springbootktor1.field.handler

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
import org.apache.ibatis.reflection.MetaObject
import org.springframework.stereotype.Component
import java.util.Date

// 处理时间自动插入, 并注入到springboot中
@Component
class MyMetaObjectHandler: MetaObjectHandler {

    override fun insertFill(metaObject: MetaObject?) {
        this.setFieldValByName("createAt", Date(), metaObject)
        this.setFieldValByName("updateAt", Date(), metaObject)
    }

    override fun updateFill(metaObject: MetaObject?) {
        this.setFieldValByName("updateAt", Date(), metaObject)
    }
}

12.field/service/IStudentService

package com.example.springbootktor1.field.service

import com.baomidou.mybatisplus.extension.service.IService
import com.example.springbootktor1.field.entity.UserDo

interface IStudentService: IService<UserDo>

13.field/service/impl/StudentServiceImpl

package com.example.springbootktor1.field.service.impl

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import com.example.springbootktor1.field.dao.mapper.IUserMapper
import com.example.springbootktor1.field.entity.UserDo
import com.example.springbootktor1.field.service.IStudentService
import org.springframework.stereotype.Service

@Service
class StudentServiceImpl: ServiceImpl<IUserMapper, UserDo>(), IStudentService

14.plugin/Jackson

package com.example.springbootktor1.plugin

import com.example.springbootktor1.core.api.KtorPlugin
import com.fasterxml.jackson.databind.SerializationFeature
import io.ktor.serialization.jackson.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import org.springframework.stereotype.Component

@Component
class Jackson: KtorPlugin {
    override fun Application.regPlugin() {
        install(ContentNegotiation) {
            jackson {
                enable(SerializationFeature.INDENT_OUTPUT)
            }
        }
    }
}

15.controller/BaseController

package com.example.springbootktor1.controller

import com.example.springbootktor1.core.api.KtorRouter

abstract class BaseController: KtorRouter

16.controller/Hello

package com.example.springbootktor1.controller

import com.example.springbootktor1.field.service.IStudentService
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Controller

@Controller
class Hello: BaseController() {

    @Autowired
    private lateinit var studentService: IStudentService


    override fun Route.regRouter() {
        get("/") {
            call.respond(studentService.list())
        }
    }
}

总结

 该示例只是用来演示使用ktor替代spring mvc的可能性,当然ktor本质上也可以用于实际生产项目,这得看对ktor的了解程度,ktor官方文档写的还算详细,本例子只是对ktor做了简单的封装,当然也加快了springboot的运行速度。