How do I put JPA annotations on delegated properties in Kotlin? - kotlin

So let's say I have a JPA entity like
#Entity
#Table(name = "plasmid_reference")
class PlasmidReference {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int? = null
#Column(name = "project_id")
var projectId: Int? = null
#Column(name = "plasmid_id")
var plasmidId: Int? = null
}
Now, I know that for each and every entry in the plasmid_reference join table none of these properties will be nullable. (In fact, the database enforces this.)
How can I tell Kotlin?
A naive approach would be to try with lateinit...
#Entity
#Table(name = "plasmid_reference")
class PlasmidReference {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
lateinit var id: Int
#Column(name = "project_id")
lateinit var projectId: Int
#Column(name = "plasmid_id")
lateinit var plasmidId: Int
}
This does not even compile.
'lateinit' modifier is not allowed on properties of primitive types
Fine, let's go with the suggested solution and convert to using delegates.
#Entity
#Table(name = "plasmid_reference")
class PlasmidReference {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id by notNull<Int>()
#Column(name = "project_id")
var projectId by notNull<Int>()
#Column(name = "plasmid_id")
var plasmidId by notNull<Int>()
}
This still does not compile because
This annotation is not applicable to target 'member property with delegate'
ok ... that sounds like a kotlin thing and in Java, I would toss the annotation on a field, so maybe ...
#Entity
#Table(name = "plasmid_reference")
class PlasmidReference {
#field:Id
#field:GeneratedValue(strategy = GenerationType.IDENTITY)
var id by notNull<Int>()
#field:Column(name = "project_id")
var projectId by notNull<Int>()
#field:Column(name = "plasmid_id")
var plasmidId by notNull<Int>()
}
No, it turns out, not really. Still does not compile because
'#field:' annotations could be applied only to properties with backing fields
Fine, so let's annotate "the field storing the delegate instance for a delegated property" (cf. https://kotlinlang.org/docs/reference/annotations.html#annotation-use-site-targets )
#Entity
#Table(name = "plasmid_reference")
class PlasmidReference {
#delegate:Id
#delegate:GeneratedValue(strategy = GenerationType.IDENTITY)
var id by notNull<Int>()
#delegate:Column(name = "project_id")
var projectId by notNull<Int>()
#delegate:Column(name = "plasmid_id")
var plasmidId by notNull<Int>()
}
This DOES compile but will throw a runtime exception because
The attribute [plasmidId] is not present in the managed type [EntityTypeImpl#960932033:PlasmidReference
[ javaType: class com.[...].PlasmidReference descriptor: RelationalDescriptor(com.[...].PlasmidReference --> [DatabaseTable(plasmid_reference)]), mappings: 3]].
at org.eclipse.persistence.internal.jpa.metamodel.ManagedTypeImpl.getAttribute(ManagedTypeImpl.java:151)
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:635)
Aaaaaand I'm out of ideas.
How do I resolve this?

Moving it to the constructor definition should do the trick
#Entity
#Table(name = "plasmid_reference")
class PlasmidReference(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int? = null,
#Column(name = "project_id")
var projectId: Int,
#Column(name = "plasmid_id")
var plasmidId: Int
)

Related

assign "val" for unit test in kotlin

I am writing unit tests in kotlin, for this purpose I need to assign value on a "val", here is the simplified version of the code:
#Entity
#Table(name = "Request")
data class Request(
#Column(name = "Name")
val name: String,
) {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
var id: Long? = null
#CreationTimestamp
#Column(name = "Created")
val created: LocalDateTime = LocalDateTime.now()
}
#Test
fun `test one`() {
val name = RandomStringUtils.randomNumeric(10)
val id = Random.nextLong(100)
val created = LocalDateTime.now().minusHours(48)
val request = Request(playerUid = playerUid).apply {
id = id
created = created
}
}
it has an compile error when assigning "created" in the test. How should I manage this unit test since I need to set my desire "created" value? (I can not touch any part of the "Request class")
If you cannot change any part of the Request class then you will not be able to change created.
You will either need to test created by using an approximate test range (created needs to be 0<now<2s sort of thing)
It is a design flaw to encode a static accessor to functions like LocalDateTime.now() - this should be set externally in a service class. If you really cannot do that, then here is another hacky approach:
add a CLOCK object somewhere (does not need to be in a companion object) but ultimately you have to change the created assignment:
#Entity
#Table(name = "Request")
data class Request(
#Column(name = "Name")
val name: String,
) {
companion object {
/** used for carrying a Clock only in the case of tests **/
val CLOCK = ThreadLocal<Clock>()
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
var id: Long? = null
#CreationTimestamp
#Column(name = "Created")
val created: LocalDateTime = LocalDateTime.now(CLOCK.get() ?: Clock.systemUTC())
}
Normally you don't touch that CLOCK but in the Unit Tests you define a
private val fixedClock = Clock.fixed(Instant.parse("2022-08-29T08:20:50Z"), ZoneOffset.UTC)
then you need
#BeforeEach
fun beforeEach() {
// this is necessary because the serialization of MemberMentorCommon.weeksOnPlan uses a clock
CLOCK.getOrSet { fixedClock }
}
#AfterEach
fun afterEach() {
CLOCK.remove()
}
Yes, ThreadLocals are nasty, but this allows you to change the behaviour of the Request class to override the now() function

Property in class header vs in class body

I have an abstract class entity.
abstract class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id")
#SequenceGenerator(name = "id", sequenceName = "id_sequence", allocationSize = 1000)
var id: Long? = null
#Version
private var version: Int = 0
#NotNull
var createdDate = ZonedDateTime.now()!!
}
And I have a class(javax.persistence Entity ) that inherits from AbstarctEntiy
#Entity
#Table(schema = "query")
data class Query(
var name: String?,
) : AbstractEntity()
Is there any difference between using users in class header and in class body as in the following two codes?
1
#Entity
#Table(schema = "query")
data class Query(
var name: String?,
#OneToMany(mappedBy = "id", fetch = FetchType.EAGER)
var users : List<Username> = mutableListOf()
) : AbstractEntity()
2
#Entity
#Table(schema = "query")
data class Query(
var name: String?,
) : AbstractEntity() {
#OneToMany(mappedBy = "id", fetch = FetchType.EAGER)
var users : List<Username> = mutableListOf()
}
There is a difference between passing an item through the constructor, and setting it as a property because you are using a data class to hold those.
While in example 1 and 2 Kotlin is generating a getter and a setter for both the user and name fields, main benefits of using a data class only get leveraged for items passed through the constructor.
In Example 1, because it's a data class Kotlin overrides the 'copy', 'toString', 'hashCode' and 'equals' classes for BOTH the properties you're passing into the constructor. So just as an example, the 'toString' function would look like so in the decompiled java code
#NotNull
public String toString() {
return "Query(name=" + this.name + ", users=" + this.users + ")";
}
In Example 2, you only get this benefit for the name property you are passing into the constructor, but not for the user list. In this case, the toString() and all the other functions I mentioned would only take into consideration name
public String toString() {
return "Query1(name=" + this.name + ")";
}
This is true for all the rest of copy() hashCode(), and equals()
If you care about Kotlin handling these for both user and name then pass both through in the constructor. Otherwise, it doesn't matter.

How can i extract a DTO from multiple tables in a micronaut application in Kotlin?

I am trying to extract into a list of DTO from multiple tables using a Native query but the documentation is just not helping.
Here are the two entity classes :
#Entity
#Table(name="addresses")
data class Addresses (
#Id
#Column(name="address_id")
val address_id:Long=0,
#Column(name="address_name")
val address_name:String="",
#OneToOne
#JoinColumn(name = "emp_id")
private var pos: Employee?
)
import com.fasterxml.jackson.annotation.JsonManagedReference
import javax.persistence.*
#Entity
#Table(name = "employee")
data class Employee (
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "emp_id")
val emp_id:Long=0,
#Column(name = "emp_name")
val emp_name:String="",
#OneToOne(mappedBy = "pos", cascade = [CascadeType.ALL],
orphanRemoval = true, fetch = FetchType.EAGER)
private var posAddress: Addresses,
)
I want to use this CustomDTO to extract the result set
#Introspected
class CustomDTO (
val emp_id:Long?,
val emp_name:String?,
val address_name:String?
)
So i will have to use a native query in order to do that :
#Repository
interface EmployeeRepository : JpaRepository<Employee, Long> {
#Join(value="addresses", type = Join.Type.FETCH)
#Query("select e.emp_id,emp_name,a.address_name from employee e join addresses a on a.emp_id=e.emp_id")
fun getResult():List<CustomDTO>
}
But when i try to run it , i get an error :
Property address_name is not present in entity: com.example.domain.Employee
Any ideas how i can resolve this ?
For anyone struggling :
#Query("select new com.packagename.DeliverySlotDto(ts.active, st.name) from TemplateSlot ts \n" +
" inner join DeliveryServiceType st on st.id=ts.serviceTypeId where ts.posId = :posId ")
fun findServiceTypeAndStatusDto(posId:Long):List<DeliverySlotDto>

Insert in to join table Spring JPA with Kotlin

My entiti of member:
#Entity(name = "Members")
data class Member(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
#Column(unique = true)
#Basic
val uid: String,
#Column(unique = true)
#Basic val email: String,
#ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
#JoinTable(name = "groups_members")
#JsonIgnore
val groups: Collection<Group?> = ArrayList(),
#OneToMany(mappedBy = "member")
#JsonIgnore
val tasks: List<Task?> = ArrayList(),
#OneToMany(mappedBy = "creator")
#JsonIgnore
val createdTasks: List<Task?> = ArrayList(),
#OneToMany(mappedBy = "creator")
#JsonIgnore
val createdMeetings: List<Meeting?> = ArrayList(),
#OneToMany(mappedBy = "creator")
#JsonIgnore
val createdGroups: List<Group?> = ArrayList(),
#ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
#JoinTable(name = "meetings_members")
#JsonIgnore
val meetings: List<Meeting?> = ArrayList()
)
entity of group of members:
package com.kotlincoders.tmsapi.data
import com.fasterxml.jackson.annotation.JsonIgnore
import javax.persistence.*
#Entity(name = "team")
data class Group(
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null,
#Column(unique = true, name = "name")
#Basic
val name: String,
#Basic
val description: String,
#ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], mappedBy = "groups")
#JsonIgnore
val members: List<Member> = ArrayList(),
#OneToMany(mappedBy = "group")
#JsonIgnore
val tasks: List<Task> = ArrayList(),
#JsonIgnore
#ManyToOne val creator: Member
){
override fun toString(): String {
return "Group(id=${this.id},name=${this.name},description=${this.description})"
}
fun addMember(member: Member){
members.plus(member)
member.groups.plus(this)
}
}
Method in controller:
#PostMapping("/add/")
fun add(#RequestBody request: AddGroupRequest) {
val member = memberService.findByUid(request.creatorUid)
var group = Group(name = request.title, creator = member, description = request.description)
group.addMember(member)
groupService.save(group)
}
But when i try add member to group i don't have any exception but nothing is inserted to join table and i don know why.
Groups are added succesfully.
I have the same problem with others entities, and i tryed many ways to solve it and nothing works.
The problem here is related to lazy fetching, to solve that try to use kotlin allOpen plugin (More info in the link at the end of the answer).
Using data classes (with val) with JPA is not the best option, read this quote from spring example:
Here we don’t use data classes with val properties because JPA is not designed to work with immutable classes or the methods generated automatically by data classes. If you are using other Spring Data flavor, most of them are designed to support such constructs so you should use classes like data class User(val login: String, …​) when using Spring Data MongoDB, Spring Data JDBC, etc.
https://github.com/spring-guides/tut-spring-boot-kotlin#persistence-with-jpa

how do i set up a one to one with superclass in kotlin

I have different user types Author and Commentor. I wanted them to have a OneToOne relationship with my User class. The user class would contain spring security logic. I created a super class of BlogUser and my Author and Commenter would extend it. When i try and set up the hibernate mappings I get:
Unknown mappedBy in:
com.legge.blenderBlog.models.security.User.blogUser, referenced
property unknown: com.legge.blenderBlog.models.abstract.BlogUser.user
Is my thinking wrong?
#MappedSuperclass
#EntityListeners(AuditingEntityListener::class)
abstract class BlogUser(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="USER_ID")
val user: User
) : DateAudit()
here is my Author.kt
#Entity
#Table(name = "author")
class Author(user:User
): BlogUser(user = user)
here is part of the User class
#Entity
#Table(name = "sec_user", uniqueConstraints = [(UniqueConstraint(columnNames = arrayOf("username"))), (UniqueConstraint(columnNames = arrayOf("email")))])
open class User(
....
#OneToOne(fetch=FetchType.LAZY, mappedBy="user")
var blogUser: BlogUser?,
...
) : DateAudit()
Here is dateAudit
#MappedSuperclass
#EntityListeners(AuditingEntityListener::class)
#JsonIgnoreProperties(value = ["dateCreated", "dateUpdated"], allowGetters = true)
abstract class DateAudit : Serializable {
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "date_created", nullable = false, updatable = false)
#CreatedDate
var dateCreated: Date = Date()
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "date_updated", nullable = false)
#LastModifiedDate
var dateUpdated: Date = Date()
}