I have started migrating a Spring Boot application from Java to Kotlin. Before, everything worked perfectly. Now I can't update any Entity received through PUT/POST, because the data doesn't contain the default values from the data class
One entity looks like that:
package org.some.soft.domain
import java.util.*
import javax.persistence.*
@Entity
@Table(name = "book_item")
data class BookItem (
@ManyToOne(cascade = [CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH])
var book: Book,
@Column(name = "format", nullable = false)
var format: BookFormat = BookFormat.HARDCOVER,
@Column(name = "barcode", nullable = false)
var barcode: String = "0000000000000",
@Column(name = "label", unique = true)
var label: String? = null,
@Column(name = "isReferenceOnly", nullable = false)
var referenceOnly: Boolean = false,
@Column(name = "borrowed")
var borrowed: Date? = null,
@Column(name = "dueDate")
var dueDate: Date? = null,
@Column(name = "price")
var price: Double? = null,
@Column(name = "status", nullable = false)
var status: BookStatus = BookStatus.AVAILABLE,
@Column(name = "dateOfPurchase")
var dateOfPurchase: Date = Date(),
@Column(name = "publicationDate", nullable = false)
var publicationDate: Date = Date(),
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
var id: Long? = null,
)
I receive the data through following controller:
@PutMapping("/book/items")
fun updateBookItem(@RequestBody book: @Valid BookItem): ResponseEntity<BookItem> {
val result = bookService.updateBook(book)
return ResponseEntity
.ok()
.headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, book.id.toString()))
.body(result)
}
The service just calls the save method from JPA-Repository (bookItemRepository.save(bookItem)
) and that's the step where I get the following error:
NULL not allowed for column "PUBLICATION_DATE"; SQL statement:
update book_item set barcode=?, book_id=?, borrowed=?, date_of_purchase=?, due_date=?, format=?, label=?, price=?, publication_date=?, is_reference_only=?, status=? where id=? [23502-214]
Following this tutorial and by copying gradle build from another JHipster project I added the following plugins:
apply plugin: "kotlin" // Required for Kotlin integration
apply plugin: "kotlin-kapt" // Required for annotations processing
apply plugin: "kotlin-spring" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#spring-support
apply plugin: "kotlin-allopen" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#using-in-gradle
apply plugin: "kotlin-jpa" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#jpa-support
apply plugin: "io.gitlab.arturbosch.detekt"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}"
implementation "org.jetbrains.kotlin:kotlin-reflect:${kotlin_version}"
kapt "org.mapstruct:mapstruct-processor:${mapstructVersion}"
kapt "org.hibernate:hibernate-jpamodelgen:${hibernateVersion}"
kapt "org.glassfish.jaxb:jaxb-runtime:${jaxbRuntimeVersion}"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
}
allOpen {
annotation("javax.persistence.Entity")
annotation("javax.persistence.MappedSuperclass")
annotation("javax.persistence.Embeddable")
}
But still I get the issue indicating me, that Spring Boot isn't using my default values from data class. I also debugged, and the attributes are not initialized with their default values.
I also tried changing var
to val
but the error still persists
Note: I do not send the null values from the client. My request body only contains values that are not null
Thanks very much in advance!
After playing with a few combinations, I finally found the problem.
JPA uses the no-arg constructor generated by the Gradle plugin by default. When setting default values, the primary constructor has to be a no-arg constructor, which means all parameters have to be optional, so it gets used by JPA.
Here is what I changed: (I set null as default for book and added the @NotNull
annotation)
@Entity
@Table(name = "book_item")
data class BookItem (
@ManyToOne(cascade = [CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH])
@NotNull
var book: Book? = null,
@Column(name = "format", nullable = false)
var format: BookFormat = BookFormat.HARDCOVER,
@Column(name = "barcode", nullable = false)
var barcode: String = "0000000000000",
@Column(name = "label", unique = true)
var label: String? = null,
@Column(name = "isReferenceOnly", nullable = false)
var referenceOnly: Boolean = false,
@Column(name = "borrowed")
var borrowed: Date? = null,
@Column(name = "dueDate")
var dueDate: Date? = null,
@Column(name = "price")
var price: Double? = null,
@Column(name = "status", nullable = false)
var status: BookStatus = BookStatus.AVAILABLE,
@Column(name = "dateOfPurchase")
var dateOfPurchase: Date = Date(),
@Column(name = "publicationDate", nullable = false)
var publicationDate: Date = Date(),
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
var id: Long? = null,
)
This solved all my problems.