MapStruct not injected in Kotlin project - spring

I am trying to create a project with current Kotlin, MapStruct and Java using Spring-Boot folowing some online examples, as I am new to MapStruct, however I am not able to inject the mapper into my service. Both Idea and Gradle (in build task test) complain that no bean has been found (UnsatisfiedDependencyException). Googling didn't help. What am I missing?
MWE:
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.7.4"
id("io.spring.dependency-management") version "1.0.14.RELEASE"
kotlin("jvm") version "1.7.20"
kotlin("plugin.spring") version "1.7.20"
kotlin("plugin.jpa") version "1.7.20"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17
configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
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")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.springframework.boot:spring-boot-starter-test")
implementation("org.mapstruct:mapstruct:1.5.3.Final")
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.3.Final")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
DemoAppllication.kt
package com.example.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
#SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
App.kt
package com.example.demo
import org.mapstruct.Mapper
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
import javax.persistence.*
#Entity
#Table(name = "items")
class Item(#Id var id: Int = 0)
data class ItemDto(val id: Int)
#Repository
interface ItemRepo : JpaRepository<Item, Int>
#Mapper(componentModel = "spring")
interface ItemMapper {
fun entToDto(item: Item) : ItemDto
fun entsToDtos(items: List<Item>) : List<ItemDto>
fun dtoToEnt(itemDto: ItemDto) : Item
}
#Service
class Srvc(private val itemMapper: ItemMapper, // XXX: no bean found for this one
private val repo: ItemRepo)
{
fun items() = itemMapper.entsToDtos(repo.findAll())
}
// controller skipped

Kapt is in maintenance mode! See example: https://github.com/mapstruct/mapstruct-examples/tree/main/mapstruct-kotlin
plugin {
// ...
kotlin("kapt")
// ...
}
dependencies {
// ...
kapt("org.mapstruct:mapstruct-processor:$version")
implementation("org.mapstruct:mapstruct:$version")
// ...
}
EDIT (by mpts.cz — for future me or anyone as new to Mapstruct and/or Gradle as I am):
use plugin kotlin("kapt")
replace annotationProcessor("org.mapstruct:mapstruct-processor:$version")
with kapt("org.mapstruct:mapstruct-processor:$version")
put the previous line before implementation("org.mapstruct:mapstruct:$version")
With these changes all seems to work smoothly. Thanks Numichi!

Related

Why do I get a "No property 'count' found for type..." runtime when running Spring Boot 3 native app written in Kotlin

When creating a minimal Kotlin Spring Boot 3 application, I am get a runtime error when executing the native (GraalVM) image. The error message is:
Failed to create query for method public abstract java.lang.Object org.springframework.data.repository.kotlin.CoroutineCrudRepository.count(kotlin.coroutines.Continuation); No property 'count' found for type 'Customer'
where 'Customer' is a data class entity.
I created a simple gradle based application with SpringBoot v3-RC1 consisting of one class
package org.test.nativedemo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.data.annotation.Id
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
#SpringBootApplication
class NativeDemoApplication
fun main(args: Array<String>) {
runApplication<NativeDemoApplication>(*args)
}
interface CustomerRepository : CoroutineCrudRepository<Customer, Int>
data class Customer(#Id val id: Int?, val name: String)
when running (through Gradle) with the following build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "3.0.0-RC1"
id("io.spring.dependency-management") version "1.1.0"
id("org.graalvm.buildtools.native") version "0.9.16"
kotlin("jvm") version "1.7.20"
kotlin("plugin.spring") version "1.7.20"
kotlin("plugin.serialization") version "1.7.20"
}
group = "org.test"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17
repositories {
mavenCentral()
maven { url = uri("https://repo.spring.io/milestone") }
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
runtimeOnly("org.postgresql:postgresql")
runtimeOnly("org.postgresql:r2dbc-postgresql")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
It compiles and builds native image. There is a runtime error stating
Failed to create query for method public abstract java.lang.Object org.springframework.data.repository.kotlin.CoroutineCrudRepository.count(kotlin.coroutines.Continuation); No property 'count' found for type 'Customer'

How to fix unresolvable circular reference when using Sentry with Spring Boot 2.6.x instead of 2.5.x?

With org.springframework.boot version 2.5.9, thinks work fine, but with 2.6.0 (2.6.1, 2.6.2, 2.6.3), I get the following error:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'sentryOptions': Requested bean is currently in creation: Is there an unresolvable circular reference?
(full log: https://gist.github.com/Dobiasd/be7810282a06b538ccee0078ab2267aa)
Here is my minimal example to reproduce the issue:
Application.kt:
package com.acme.foo.bar
import io.sentry.spring.EnableSentry
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Configuration
#EnableSentry
#Configuration
class SentryConfiguration
#SpringBootApplication
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
IntegrationTest.kt:
package com.acme.foo.bar.integration
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.junit.jupiter.SpringExtension
#ExtendWith(SpringExtension::class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class FullStackTest {
#Test
fun init_context() {
}
}
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.6.3"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.6.10"
kotlin("plugin.spring") version "1.6.10"
}
group = "com.acme"
version = "1.0.0-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
dependencies {
implementation(group = "org.springframework.boot", name = "spring-boot-starter-web")
implementation(group = "io.sentry", name = "sentry-spring", version = "5.5.3")
testImplementation(group = "org.springframework.boot", name = "spring-boot-starter-test")
testImplementation(group = "org.jetbrains.kotlin", name = "kotlin-test-junit")
}
tasks {
withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
withType<Test> {
testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
useJUnitPlatform()
}
}
Any ideas what I'm doing wrong? Or maybe sentry-spring just does work with Spring Boot 2.6.x (yet)?
Found the solution. Instead of io.sentry:sentry-spring one has to use io.sentry:sentry-spring-boot-starter in the dependencies and then remove the following from the code:
#EnableSentry
#Configuration
class SentryConfiguration
I've just tested in my actual project, and logged errors are still sent to Sentry correctly.

Spring Boot Kotlin Cannot Find Repository Beans

I'm just getting started with Spring Boot + Kotlin and I was trying out the PagingAndSortingRepository interface for JPA so I wrote the following interface:
interface CustomerRepository : PagingAndSortingRepository<Customer, Long>
The model for Customer is below:
#Entity
data class Customer(
#Id #GeneratedValue var id: Long,
var name: String
)
Now I'm trying to hook it up with a CustomerService which looks like this:
#Service
class CustomerService(
private val customerRepository: CustomerRepository
) {
fun getAllCustomers(): Collection<Customer> = customerRepository.findAll().toList()
fun addCustomer(customer: Customer) = customerRepository.save(customer)
fun deleteCustomer(customer: Customer) = customerRepository.delete(customer)
fun updateCustomer(customer: Customer) = customerRepository.save(customer)
}
And the Application looks like this:
#SpringBootApplication
#Configuration
#EnableAutoConfiguration
#EnableJpaRepositories
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
I've added the required dependencies I believe, which are shown below:
plugins {
id("org.springframework.boot") version "2.5.0-SNAPSHOT"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.4.30"
kotlin("plugin.spring") version "1.4.30"
}
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.apache.derby:derby:10.15.2.0")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
Spring Boot is not able to find a bean which sort of makes sense as I haven't defined one. However reading the documentation, it looks like one should be generated by Spring Boot here: Spring Boot Data Repositories
Application.properties is
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
The error message I get is:
Description:
Parameter 0 of constructor in com.ubiquifydigital.crm.service.CustomerService required a bean named 'entityManagerFactory' that could not be found.
Action:
Consider defining a bean named 'entityManagerFactory' in your configuration.
I saw a few different posts regarding this and have tried adding the Configuration, AutoConfiguration and EnableJpaRepositories annotations however that has only changed the error to entityManagerFactory not found instead of the CustomerRepository not found.
When using default in-memory db you must define
spring.jpa.hibernate.ddl-auto=update
in application.properties as informed here. You are also missing #Autowired annotation. entityManagerFactory is missing because the default auo configuration is turned off, in that case application is expecting you to do all the necessary configuration which again you are not doing. So keep the default configuration on and change what you need.
This code is assumed in a single file.
If you are having multiple packages then you may need to add as mentioned in this link
Working code:
package com.example.demo
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import lombok.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository
import org.springframework.web.bind.annotation.*
import java.util.*
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id
import javax.persistence.Table
#SpringBootApplication
open class SpringBootDerbyAppApplication
fun main(args: Array<String>) {
runApplication<SpringBootDerbyAppApplication>(*args)
}
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "applog")
internal class AppLog {
#Id
#GeneratedValue
private val id: Long = 0
#JsonProperty
private val name: String? = null
}
#Configuration
open class ObjectMapperConfiguration {
#Bean
#Primary
open fun objectMapper() = ObjectMapper().apply {
registerModule(KotlinModule())
}
}
#RestController
#RequestMapping(path = ["/logs"])
internal class LogController #Autowired constructor(private val appLogRepository: AppLogRepository) {
#GetMapping(path = ["/"])
fun logs(): MutableIterable<AppLog> {
return appLogRepository.findAll()
}
#PostMapping(path = ["/"])
fun add(#RequestBody appLog: AppLog): AppLog {
appLogRepository.save(appLog)
return appLog
}
}
#Repository
internal interface AppLogRepository : CrudRepository<AppLog, Long>
gradle file
plugins {
id 'org.springframework.boot' version '2.4.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.5.0-M1'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.apache.derby:derby'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
}
test {
useJUnitPlatform()
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}

Test EntityManager with Junit5 Kotlin

I am trying to introduce to Spring JPA and I have difficulties running up my tests.
My gradle.build.kts looks like the following
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.4.0"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
kotlin("jvm") version "1.4.10"
kotlin("plugin.spring") version "1.4.10"
kotlin("plugin.jpa") version "1.4.10"
}
group = "com.pluralsight"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8
repositories {
mavenCentral()
jcenter()
google()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
runtimeOnly("com.h2database:h2")
testImplementation(platform("org.junit:junit-bom:5.7.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
I am using Junit5 for my test framework. And what first I need to test is that my Flight Entity is created correctly. I am not using #SpringRunner since we are on Junit5 so I do the following:
package com.pluralsight.springdataoverview
import com.pluralsight.springdataoverview.entity.Flight
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.context.SpringBootTest
import java.time.LocalDateTime
import javax.persistence.EntityManager
#SpringBootTest
#DataJpaTest
class SpringDataOverviewApplicationTests {
#Autowired
private val entityManager: EntityManager? = null
#Test
fun verifyFlighTCanBeSaved() {
var flight = Flight()
flight.origin = "London"
flight.destination = "New York"
flight.scheduledAt = LocalDateTime.parse("2011-12-13T12:12:00")
entityManager!!.persist(flight)
val flights = entityManager
.createQuery("SELECT f FROM Flight f", Flight::class.java)
.resultList
Assertions.assertEquals(flights.first(), flight)
}
}
And I have the following in red
What dependency I am missing ?
You have multiple declarations of configuration (that's what your error message is saying).
It's because you are using #SpringBootTest and #DataJpaTest in the same configuration class i.e SpringDataOverviewApplicationTests.
Use either of it and it should be fine.

Spring Autowire not initialized with KotlinTest 3.1.8

I know this should be possible, as others have reported it working. However, in a simple 'demo' project (Spring Initializer), I can't get the autowired property to be initialized within the test.
When I execute the test in "JpaTestApplicationTests", I can't get past receiving "lateinit property repo has not been initialized
kotlin.UninitializedPropertyAccessException".
Can anyone help me understand what I'm doing wrong?
Below is my application file, bean to be autowired, and test:
Application:
package com.example.jpaTest
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
#SpringBootApplication
class JpaTestApplication
fun main(args: Array<String>) {
SpringApplication.run(JpaTestApplication::class.java, *args)
}
Bean:
package com.example.jpaTest
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository
#Repository
interface FakeRepository : CrudRepository<Fake, String>
Test:
package com.example.jpaTest
import io.kotlintest.*
import io.kotlintest.specs.*
import io.kotlintest.spring.SpringListener
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
#ContextConfiguration(classes = [JpaTestApplication::class])
class JpaTestApplicationTests : StringSpec() {
override fun listeners() = listOf(SpringListener)
#Autowired
lateinit var repo: FakeRepository
init {
"can get by id"{
val it = Fake("Blah","testName1")
val saved = repo.save(it)
repo.findOne(saved.uuid) shouldBe saved
}
}
}
Relevant Gradle entries:
buildscript {
ext {
kotlinVersion = '1.2.61'
springBootVersion = '1.5.16.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion"
}
}
apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: "kotlin-jpa"
apply plugin: 'org.springframework.boot'
test {
useJUnitPlatform()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-web')
compile('com.fasterxml.jackson.module:jackson-module-kotlin')
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
compile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile 'io.kotlintest:kotlintest-runner-junit5:3.1.8'
testCompile 'io.kotlintest:kotlintest-extensions-spring:3.1.8'
}

Resources