#CreationTimestamp and #UpdateTimestamp don't work in Kotlin - spring

This is my Tag and Post Entity classes:
#Entity
class Tag(
#get:NotBlank
#Column(unique = true)
val name: String = "",
val description: String = ""
) {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
val id: Int? = null
#ManyToMany(mappedBy = "tags")
val posts: MutableSet<Post> = mutableSetOf()
#CreationTimestamp
lateinit var createDate: Date
#UpdateTimestamp
lateinit var updateDate: Date
fun addPost(post: Post) {
this.posts.add(post)
post.tags.add(this)
}
}
#Entity
class Post(
#get:NotBlank
var name: String = "",
val content: String = ""
) {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
val id: Int? = null
#ManyToMany
#Cascade(CascadeType.ALL)
val tags: MutableSet<Tag> = mutableSetOf()
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
lateinit var createDate: Date
#UpdateTimestamp
#Temporal(TemporalType.TIMESTAMP)
lateinit var updateDate: Date
fun addTag(tag: Tag) {
this.tags.add(tag)
tag.posts.add(this)
}
}
The query:
val post1 = Post( "Post 1", "Post 1 content");
val post2 = Post( "Post 2", "Post 2 content");
var tag = Tag( "news", "Tag description")
post1.addTag(tag)
post2.addTag(tag)
em.persist(post1)
em.persist(post2)
em.remove(post1)
em.flush()
But then, the createDate and updateDate return null (both tag and post):
I converted this code to Java and it works fine
Kotlin version: 1.2-M2
springBootVersion: '2.0.0.M7'

The problem likely exists in the fact that those annotations are not limited to what they should annotate. This means kotlin does not know exactly where to put them in the final bytecode.
To get the same bytecode as would be generated by java, you need to specify the annotation target of the annotation in question:
#field:CreationTimestamp
lateinit var createDate: Date

The context is not enough to provide any meaningful answer.
You have to give more context, in particular the application server, with its version, or even code for this specific use-case. To start with, I'd suggest first checking if the same Java code works.
My best guess so far, you're using Spring Data JPA while #CreationTimestamp and #UpdateTimestamp are Hibernate specific.

Related

How to do update?

I have these entities
#Entity(name = "inspiration")
class InspirationEntity(
#Id
var uuid: UUID? = null,
#Column(name = "display_name")
var displayName: String,
#CreationTimestamp
#Column(name = "created_at")
#Temporal(TemporalType.TIMESTAMP)
var createdAt: Date?,
#UpdateTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "last_modified_date")
var lastModifiedDate: Date?,
#OneToMany(targetEntity = BaseSliderEntity::class)
#Cascade(CascadeType.ALL)
var sliderList: List<BaseSliderEntity>,
)
#Entity(name = "base_slider")
#Inheritance(strategy = InheritanceType.JOINED)
//TODO investigate DiscriminatorColumn anotation and best practices, because it shows some warning in logs
#DiscriminatorColumn(name = "slider_type", discriminatorType = DiscriminatorType.STRING)
abstract class BaseSliderEntity(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
open var id: Long? = null,
)
#Entity(name = "dish_of_the_day")
#DiscriminatorValue("DISH_OF_THE_DAY")
class DishOfTheDayEntity(
#Column(name = "title_en")
var titleEN: String,
#Column(name = "title_de")
var titleDE: String,
) : BaseSliderEntity()
#Entity(name = "inspiration_screen_link")
#DiscriminatorValue("LINK")
class InspirationScreenLinkEntity(
#Enumerated(EnumType.STRING)
var destination: Destination,
) : BaseSliderEntity()
this is my dto
data class InspirationDTO(
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
var uuid: UUID?,
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
var createdAt: Date?,
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
var lastModifiedDate: Date?,
val displayName: String,
val inspirationScreenItemList: List<InspirationScreenItemDTO>,
)
and this is InspirationScreenItemDTO
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
#JsonSubTypes(
JsonSubTypes.Type(value = DishOfTheDayDTO::class, name = "DishOfTheDay"),
JsonSubTypes.Type(value = InspirationScreenContinuousSliderDTO::class, name = "InspirationScreenContinuousSlider"),
JsonSubTypes.Type(value = InspirationScreenLinkDTO::class, name = "InspirationScreenLink"),
JsonSubTypes.Type(value = InspirationScreenPagingSliderDTO::class, name = "InspirationScreenPagingSlider"),
JsonSubTypes.Type(
value = InspirationScreenRecentlyViewedSliderDTO::class,
name = "InspirationScreenRecentlyViewedSlider"
),
JsonSubTypes.Type(value = InspirationScreenTagsSliderDTO::class, name = "InspirationScreenTagsSlider"),
)
open class InspirationScreenItemDTO
when I try to update
like this
#Transactional
fun update(uuid: UUID, inspirationDTO: InspirationDTO): InspirationDTO {
var entity = inspirationRepository.findById(id).get()
val updatedEntity: InspirationEntity = inspirationMapper.convertToEntity(inspirationDTO)
entity.sliderList = updatedEntity.sliderList
val result: InspirationEntity = inspirationRepository.save(entity)
return inspirationMapper.convertToDto(result)
}
this is my swagger post
{
"displayName": "ED",
"inspirationScreenItemList": [
{
"type": "DishOfTheDay",
"titleEN": "GGGGGGG",
"titleDE": "GGGGGGG"
}
]
}
in DishofDto table it creates two rows first and updated, and inspiration remains one row which is okay but dish of the day shouldn't contain two rows, it should be just updated one.
My solution was to add uuid id as primary key in inspiration which works with deleting by id and then put toUpdateDto under the same id and save , but I'm not sure it's good solution.

Spring Data JPA Repository function does not work in Test

I have a question regarding Spring Data JPA.
To make it as simple as possible I made up a very simple example.
We have the TestUser, that can have a FavouriteColor, but his favouriteColor can also be null.
TestUser.kt
#Entity
class TestUser(
#Id
#Column(name = "TestUserId")
var userId: Long,
#Column(name = "Name")
var name: String,
#Column(name = "FavouriteColorId")
var favouriteColorId: Long? = null,
#OneToOne
#JoinColumn(
name = "FavouriteColorId",
referencedColumnName = "FavouriteColorId",
insertable = false,
updatable = false,
nullable = true
)
var favouriteColor: FavouriteColor? = null
)
FavouriteColor.kt
#Entity
class FavouriteColor(
#Id
#Column(name = "FavouriteColorId")
var favouriteColorId: Long,
#Column(name = "ColorCode")
var colorCode: String
)
When I search for the users that have a favourite Color by findTestUsersByFavouriteColorNotNull(), the size of the result is 0. Even if there is an User that has a favourite color. And when I use findAll() and then apply the filter, the result is correct.
StackOverflowTest.kt
#SpringBootTest
#Transactional
class StackOverflowTest {
#Autowired
lateinit var testUserRepository: TestUserRepository
#Autowired
lateinit var favouriteColorRepository: FavouriteColorRepository
#Test
fun testFilter() {
val favouriteColor = FavouriteColor(favouriteColorId = 0L, colorCode = "#000000")
favouriteColorRepository.save(favouriteColor)
val user = testUserRepository.save(TestUser(userId = 0L, name = "Testuser"))
user.favouriteColor = favouriteColor
testUserRepository.save(user)
val usersWithColor1 = testUserRepository.findAll().filter { it.favouriteColor != null }
assert(usersWithColor1.size == 1) // This assertion is correct
val usersWithColor2 = testUserRepository.findTestUsersByFavouriteColorIdIsNotNull()
assert(usersWithColor2.size == 1) // This assertion fails
val usersWithColor3 = testUserRepository.findTestUsersByFavouriteColorIsNotNull()
assert(usersWithColor3.size == 1) // This assertion fails
}
}
Update:
I added the Repository function findTestUsersByFavouriteColorIdNotNull() but it also does not work
Update2:
I updated the functions to findTestUsersByFavouriteColorIdIsNotNull and findTestUsersByFavouriteColorIsNotNull, but the assertions are still failing
Can somebody explain me, why the findTestUsersByFavouriteColorNotNull() does not work ? And is there some way to get this function working in the tests?
Thanks :)
I'm suspecting that happen because you have 2 variables of the same column name
#Column(name = "FavouriteColorId")
var favouriteColorId: Long? = null,
#OneToOne
#JoinColumn(
name = "FavouriteColorId",
referencedColumnName = "FavouriteColorId",
insertable = false,
updatable = false,
nullable = true
)
var favouriteColor: FavouriteColor? = null
Try removing one of the variable, and try again.

Spring boot UUID primary key entity doesn't show right id after created

So I have entity with UUID as primary key. After I create the entity in my service, the response return wrong id.
My Role entity
package denny.study.stock.entity
import denny.study.stock.util.converter.JsonToMapConverter
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.GenericGenerator
import org.hibernate.annotations.Type
import org.hibernate.annotations.UpdateTimestamp
import java.util.*
import javax.persistence.*
#Entity
#Table(name = "roles")
class Role {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "id", columnDefinition = "CHAR(36)")
#Type(type = "uuid-char")
var id: UUID = UUID.randomUUID()
#Column
var name: String = ""
#Column
var slug: String = ""
#Column
#Convert(converter = JsonToMapConverter::class)
var permissions: MutableMap<String, Boolean>? = null
#Column(name = "created_at")
#CreationTimestamp
var createdAt: Date = Date()
#Column(name = "updated_at")
#UpdateTimestamp
var updatedAt: Date? = null
}
My store method in role service
override fun store(roleRequest: RoleRequest): RoleResponse {
val role = Role().apply {
name = roleRequest.name
slug = roleRequest.slug
permissions = roleRequest.permissions
}
roleRepository.save(role)
return RoleResponse(role)
}
My Role response
package denny.study.stock.model.response.role
import denny.study.stock.entity.Role
class RoleResponse(role: Role) {
var id = role.id
var name = role.name
var slug = role.slug
var permissions = role.permissions
}
the json response return id "f95bddf6-eb22-49bb-b8e6-5eb819603fa9"
{
"code": 200,
"status": "OK",
"data": {
"id": "f95bddf6-eb22-49bb-b8e6-5eb819603fa9",
"name": "Role 1",
"slug": "role-1",
"permissions": {
"asd": true,
"dsa": false
}
}
}
while in DB it stored as "87596692-7ee9-4ecb-a425-3b1372d901f4". Do you know why It return the wrong id? Thank you!
You are using #GeneratedValue annotation and also assigning UUID.randomUUID() to the id attribute. Either use one or the other, not both.
If you want the ID to be generated by the persistence provider then keep #GeneratedValue and remove #GenericGenerator (which is a Hibernate annotation) and UUID.randomUUID(). If you want to do that on your own then remove #GeneratedValue and #GenericGenerator.

Spring Hibernate - #Transactional between different transactions

I'm creating a test and basically doing different transactions inside a #Transactional method.
I add a Project, then add a Task to it, and last will fetch the project again from DB to test it has the task saved.
Please note the case I'm showing is a unit test but I'm more interesting in fixing the transactional methods and not the test itself as I already had this in the past in "production code".
Model Classes:
#Entity
#Table(name = "Task")
data class Task(
#Id
#SequenceGenerator(name = "TaskSeq", sequenceName = "TaskSeq", initialValue = 100)
#GeneratedValue(generator = "TaskSeq")
val id: Long = 0,
#Column(nullable = false)
val name: String,
val description: String,
val inZ: LocalDateTime = LocalDateTime.now(),
var outZ: LocalDateTime = JpaConstants.MAX_DATETIME,
var completed: Boolean = false,
#ManyToOne(cascade = [CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH])
#JoinColumn(name = "projectId")
var project: Project? = null
) {
}
#Entity
#Table(name = "Project")
data class Project(
#Id
#SequenceGenerator(name = "ProjectSeq", sequenceName = "ProjectSeq", initialValue = 100)
#GeneratedValue(generator = "ProjectSeq")
val id: Long = 0,
#Column(nullable = false)
var name: String,
#OneToMany(mappedBy = "project", cascade = [CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH])
val tasks: MutableList<Task> = Lists.mutable.empty()
) {
}
Service Classes:
#Service
class ProjectServiceImpl(private val projectRepository: ProjectRepository) : ProjectService {
override fun save(project: Project): Project {
return projectRepository.save(project)
}
}
#Service
class TaskServiceImpl(private val taskRepository: TaskRepository, private val projectRepository: ProjectRepository) : TaskService {
override fun save(task: Task): Task {
return taskRepository.save(task)
}
override fun addTaskToProject(projectId: Long, task: Task): Task {
val project = projectRepository.findById(projectId).orElseThrow { RecordNotFoundException("Couldn't find project with id {$projectId}") }
task.project = project
return save(task)
}
}
The class I'm trying to use the transactional method:
class TaskServiceImplTest : TaskApplicationTests() {
#Autowired
private lateinit var taskService: TaskService
#Autowired
private lateinit var taskRepository: TaskRepository
#Autowired
private lateinit var projectService: ProjectService
#Test
#Transactional
fun canInsertTaskToProject() {
val project = projectService.save(Project(name = "Conquer Paris"))
var task = Task(name = "Check how many people we need to hire", description = "")
task = taskService.addTaskToProject(project.id, task)
assertTrue(task.id > 0)
val projects = projectService.findAll()
assertEquals(1, projects.size())
assertEquals(1, projects[0].tasks.size)
assertEquals(task.id, projects[0].tasks[0].id)
}
If I add a #Transactional(REQUIRES_NEW) to the methods in the service it will work, but I don't want it as if this method is called inside a real transaction I want it to be rolled back accordingly. Also I'd like to avoid using too many REQUIRES_NEW to avoid future problems
If I remove the #Transactional from the test method, it won't work when I test the size of the task list on last two lines as they are lazy.
What is the best way to make it work ? I thought that inside a #Transactional when I used another command from db it would get the latest updates that were not committed yet..
If needed, code in Java is fine too :)
Thanks in advance!
Based on your scenarios, you can use #TestEntityManagerso that each test can be managed in transaction context.
This example can help you,
https://grokonez.com/testing/datajpatest-with-spring-boot

Save child and parent at the same time

I need to save 2 models at the same time in the transaction, but...
org.postgresql.util.PSQLException: ERROR: null value in column "book_id" violates not-null constraint
I don't understand what is going wrong. My models:
a) Chapter
#Entity
class Chapter() : AuditModel() {
constructor(number: Int, title: String) : this() {
this.number = number
this.title = title
}
#Column(nullable = false)
var number: Int? = null
#Column(nullable = false)
lateinit var title: String
#Column(nullable = false)
var progress: Int = 0
#ManyToOne
#JoinColumn(name = "book_id")
lateinit var book: Book
}
b) Book
#Entity
class Book() : AuditModel() {
constructor(title: String, author: String) : this() {
this.title = title
this.author = author
}
#Column(nullable = false)
lateinit var title: String
#Column(nullable = false)
lateinit var author: String
#OneToMany(mappedBy = "book", cascade = [CascadeType.PERSIST])
val chapters: MutableSet<Chapter> = HashSet()
}
And function where I save models:
#Transactional
fun createBook(title: String, author: String): Boolean {
val book = Book(title, author)
val chapter = Chapter(1, "Example - 1")
book.chapters.add(chapter)
return bookRepository.save(book) != null
}
How to fix it? I'm new in Spring and it's totally incomprehensible to me.

Resources