SpringBoot到Kotlin血泪史

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot到Kotlin血泪史相关的知识,希望对你有一定的参考价值。

参考技术A 如果原先有 @Bean(name="xxx") 直接用方法名即可(我原先的 name 和方法名不一样就很尴尬)

hashedCredentialsMatcher 中的内容可以直接 new ,所以不需要注入

如果SpringBoot的版本从 1.5.x 变成了 2.0.x , shiroDialect 或者 shiroFilter 可能会报如下错误

把 thymeleaf-extras-shiro 的版本号改成 2.0.0 即可(原先是 1.2.1 )

在 SpringBoot 版本中,在 Realm 中注入 Service 时,为了启用缓存,需要在前面加上 @Lazy 注解,如下

在 Kotlin 版本中我不知道发了啥疯就把它去掉了(可能是看到前面类型是 lateinit var ,自以为是的觉得可以代替 @Lazy ),然后改成了改成如下形式

拷贝完整个项目之后,测试功能的时候,发现缓存没了……然后就开始疯狂DEBUG,从版本问题,到 jar 包冲突问题,经历了很漫长的一段时间后,我定位到了 ShiroConfiguration ,只要把 shiro aop 注解关闭就可以开启缓存了

WHAT??

疯狂谷歌一个小时无果(因为一直以为是 Kotlin 不兼容啥的,或者是 shiro 在 boot2.x 之后需要修改相应的配置)

然后又疯狂DEBUG,把 Kotlin 版和 springboot 版进行对比,最后。定位到了 @Lazy (还好只是改成了注释,没把它给直接删了)

果然。加了 @Lazy ,整个天都亮了

最后顺便提一句 Realm 认证超级管理员的问题,可以直接在 Realm 中加上超级管理员的特别认证,就不用去方法级别上区分这个权限可以超级管理和XX管理员都可用了

springboot+kotlin+gradle+hibernate学习笔记

Hibernate 将 Java 类映射到数据库表中,从 Java 数据类型中映射到 SQL 数据类型中,并把开发人员从 95% 的公共数据持续性编程工作中解放出来。是传统 Java 对象和数据库服务器之间的桥梁,用来处理基于 O/R 映射机制和模式的那些对象。

Hibernate 优势

Hibernate 使用 XML 文件来处理映射 Java 类别到数据库表格中,并且不用编写任何代码。
为在数据库中直接储存和检索 Java 对象提供简单的 APIs。
如果在数据库中或任何其它表格中出现变化,那么仅需要改变 XML 文件属性。
抽象不熟悉的 SQL 类型,并为我们提供工作中所熟悉的 Java 对象。
Hibernate 不需要应用程序服务器来操作。
操控你数据库中对象复杂的关联。
最小化与访问数据库的智能提取策略。
提供简单的数据询问。

build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins 
    id("org.springframework.boot") version "2.3.7.RELEASE"
    id("io.spring.dependency-management") version "1.0.10.RELEASE"
    kotlin("jvm") version "1.3.72"
    kotlin("plugin.spring") version "1.3.72"


group = "com"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8

configurations 
    compileOnly 
        extendsFrom(configurations.annotationProcessor.get())
    


repositories 
    mavenLocal()   //配置先从本地仓库寻找jar包,优先寻找上一个配置,找到不执行下面的配置
    mavenCentral() //配置从中央仓库寻找
    google()       //第三方仓库


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-jdk8")
    // https://mvnrepository.com/artifact/org.hibernate/hibernate-core
    implementation("org.hibernate:hibernate-core:5.6.10.Final")
    runtimeOnly("mysql:mysql-connector-java:5.1.6")
    compileOnly("org.projectlombok:lombok")
    annotationProcessor("org.projectlombok:lombok")
    testImplementation("org.springframework.boot:spring-boot-starter-test") 
        exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
    
// https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter
    implementation("com.alibaba:druid-spring-boot-starter:1.2.8")
    // https://mvnrepository.com/artifact/org.springframework/spring-orm
    implementation("org.springframework:spring-orm:5.3.21")
// https://mvnrepository.com/artifact/cn.hutool/hutool-core
    implementation("cn.hutool:hutool-core:5.8.4")



tasks.withType<Test> 
    useJUnitPlatform()


tasks.withType<KotlinCompile> 
    kotlinOptions 
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "1.8"
    


application.yml

server:
  port: 8080

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    name: defaultDataSource
    url: jdbc:mysql://localhost:3306/people?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
    password: 123456
    username: root

配置数据源

package com.spring_hibernate.config

import com.alibaba.druid.pool.DruidDataSource
import org.springframework.beans.factory.annotation.Configurable
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import javax.sql.DataSource


@Configurable
class DataSourceConfig 
    @Bean
    @ConfigurationProperties("spring.datasource")
    fun dataSource(): DataSource? 
        val source = DruidDataSource()
        //source.setDriverClassName("com.mysql.cj.jdbc.Driver");
        source.isBreakAfterAcquireFailure = true
        source.name = "root"
        source.initialSize = 1
        //最大活动
        source.maxActive = 10
        source.maxWait = 60000
        //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        source.timeBetweenEvictionRunsMillis = 60000
        //配置一个连接在池中最小生存的时间,单位是毫秒
        source.minEvictableIdleTimeMillis = 300000
        //每分钟打印一次连接状态日志
        //source.setTimeBetweenLogStatsMillis(60000);
        return source
    


配置hibernate

package com.spring_hibernate.config

import com.alibaba.druid.pool.DruidDataSource
import org.springframework.context.annotation.Bean
import org.springframework.orm.hibernate5.LocalSessionFactoryBean
import org.springframework.stereotype.Component
import java.util.*
import javax.annotation.Resource
import javax.sql.DataSource


@Component
class HibernateToConfig 
    @Resource
    private val dataSource: DataSource? = null

    /**
     * 此处bean为根据 hibernate 官网配置文件 hibernate.cfg.xml 改造的
     * https://docs.jboss.org/hibernate/orm/5.5/quickstart/html_single/hibernate-tutorials.zip
     *
     * @return
     */
    @Bean
    fun sessionFactory(): LocalSessionFactoryBean? 
        val bean = LocalSessionFactoryBean()
        if (dataSource != null) 
            bean.setDataSource(dataSource)
        
        // 扫描实体类
        bean.setPackagesToScan("com.spring_hibernate.entity")
        val properties = Properties()
        properties.setProperty("current_session_context_class", "thread")
        val druidDataSource = dataSource as DruidDataSource?
        properties.setProperty("connection.pool_size", druidDataSource!!.maxActive.toString())
        // 配置方言 mysql 5.7.34
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL57Dialect")
        //        <!-- 控制台打印SQL -->
        properties.setProperty("hibernate.show_sql", "true")
        // 制台打印SQL格式化
        //properties.setProperty("hibernate.format_sql", "true");
        /**
         * create:表示启动的时候先drop,再create
         * create-drop: 也表示创建,只不过再系统关闭前执行一下drop
         * update: 这个操作启动的时候会去检查schema是否一致,如果不一致会做scheme更新
         * validate: 启动时验证现有schema与你配置的hibernate是否一致,如果不一致就抛出异常,并不做更新
         */
        // 首次启动使用 create 让bean自动生成表,之后使用 update模式即可
        properties.setProperty("hibernate.hbm2ddl.auto", "update")
        bean.setHibernateProperties(properties)
        return bean
    


在配置hibernate时,需要配置实体类的包,然后在 properties.setProperty(“hibernate.hbm2ddl.auto”, “create”) 在第一次启动时,就会根据类对象,创建数据表,但是在这之前需要指定数据库,在application.yml中进行指定,本demo使用的是MySQL5.1.6的驱动,所以在application.yml中是com.mysql.jdbc.Driver,要是使用更高版本的驱动,需要使用com.mysql.cj.jdbc.Driver

自定义主键生成

package com.spring_hibernate.util

import cn.hutool.core.lang.Snowflake
import cn.hutool.core.util.IdUtil;
import org.hibernate.HibernateException
import org.hibernate.MappingException
import org.hibernate.engine.spi.SharedSessionContractImplementor
import org.hibernate.id.Configurable
import org.hibernate.id.IdentifierGenerator
import org.hibernate.service.ServiceRegistry
import org.hibernate.type.Type
import java.io.Serializable
import java.util.*


/**
 * 采用雪花算法生成主键
 * 2021年7月11日22:11:55
 */
class PrimaryGenerator : Configurable, IdentifierGenerator 

    var snowflake: Snowflake? = IdUtil.getSnowflake(1, 1)
    private var pre = ""

    @Throws(MappingException::class)
    override fun configure(type: Type, params: Properties, serviceRegistry: ServiceRegistry) 
        val prefix = params.getProperty("prefix")
        if (prefix != null) pre = prefix
    

    @Throws(HibernateException::class)
    override fun generate(session: SharedSessionContractImplementor, `object`: Any): Serializable 
        //雪花算法生成ID
        return pre + snowflake!!.nextIdStr()
    



实体类

package com.spring_hibernate.entity

import lombok.Data
import org.hibernate.annotations.GenericGenerator
import java.util.*
import javax.persistence.*


@Data
@Entity
@Table(name = "t_user")
class UserBean 
    // 主键采用string为了兼容更多数据库
    @Id
    @Column(length = 20)
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "gg") //自定义生成主键
    @GenericGenerator(name = "gg", strategy = "com.spring_hibernate.util.PrimaryGenerator")
    val id: String? = null

    @Column(length = 40)
    var username: String? = null

    @Column(length = 64)
    var password: String? = null

    @Column(length = 20)
    var nickname: String? = null

    @Column(name = "create_time")
    var createTime: Date? = null

在第一次运行完之后,就会在指定数据库创建表,该表的字段和上面的实体类一一对应

controller

package com.spring_hibernate.controller

import com.spring_hibernate.entity.UserBean
import com.spring_hibernate.service.UserBeanService
import com.spring_hibernate.util.R
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/user")
class UserBeanController 

    @Autowired
    lateinit var userBeanService: UserBeanService

    @Transactional
    @PostMapping
    fun insertOne(@RequestBody userBean: UserBean): R<*> 
        return userBeanService.insertOne(userBean)
    

    @Transactional
    @PutMapping
    fun updateOne(@RequestBody userBean: UserBean):R<*>
        return userBeanService.updateOne(userBean)
    

    @Transactional
    @DeleteMapping
    fun deleteOne(@RequestBody userBean: UserBean): R<*>
        return userBeanService.deleteOne(userBean)
    

    @Transactional
    @GetMapping
    fun getOne(id:String):R<*>
        return userBeanService.getOne(id)
    

    @Transactional
    @GetMapping("/all")
    fun getAll():R<*>
        return userBeanService.getAll()
    


这里的R还是自己封装好的返回值类型

dao层

package com.spring_hibernate.service.impl

import com.spring_hibernate.entity.UserBean
import com.spring_hibernate.service.UserBeanService
import com.spring_hibernate.util.R
import org.hibernate.SessionFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Repository
import java.util.*

@Repository
class UserBeanServiceImpl : UserBeanService 

    @Autowired
    lateinit var sessionFactory:SessionFactory

    override fun insertOne(userBean: UserBean): R<*> 
        userBean.createTime = Date()
        val line = sessionFactory.currentSession.save(userBean)
        println("line::$line")
        return R.ok(line,"插入成功")
    

    override fun updateOne(userBean: UserBean): R<*> 
        val user:UserBean = sessionFactory
                .currentSession
                .createQuery("from UserBean where id=:id")
                .setParameter("id", userBean.id).uniqueResult() as UserBean
        user.nickname = userBean.nickname
        user.username = userBean.username
        user.password = userBean.password
        sessionFactory.currentSession.update(user)
        return R.ok(null,"修改数据")
    

    override fun deleteOne(userBean: UserBean): R<*> 
        val res = sessionFactory.currentSession
                .createQuery("delete from UserBean  where id=:id")
                .setParameter("id", userBean.id).executeUpdate()
        return R.ok(res,"删除成功")
    

    override fun getOne(id: String): R<*> 
        val user:UserBean = sessionFactory
                .currentSession
                .createQuery("from UserBean where id=:id")
                .setParameter("id", id).uniqueResult() as UserBean
        return R.ok(user,"找到一条记录")
    

    override fun getAll(): R<*> 
        val list = sessionFactory
                .currentSession
                .createQuery("from UserBean")
                .list()
        return R.ok(list,"查询所有")
    


上述是一些基本的CRUD操作,需要查找更多的模板代码,可以去如下地址
https://www.w3cschool.cn/hibernate/hvqb1ie5.html

以上是关于SpringBoot到Kotlin血泪史的主要内容,如果未能解决你的问题,请参考以下文章

Gradle kotlin Springboot多模块导致无法引用kotlin的类文件(BootJar)

springboot+kotlin+gradle+hibernate学习笔记

springboot+kotlin+gradle+hibernate学习笔记

整合springboot+kotlin+gradle+jpa的demo学习笔记

一个普通投资者期货开户到现在的血泪史

Java执行jar总结 ( 血泪史 )