Insert in to join table Spring JPA with Kotlin - 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

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

JPA Specification joins with Kotlin

I have three JPA entities that look a bit like this:
#Entity
#Table(name = "users")
data class UserEntity(
#Id
val id: UUID,
var name: String,
#OneToMany(mappedBy = "user")
val roles: MutableList<UserRoleAssignmentEntity>,
#OneToMany(mappedBy = "user")
val qualifications: MutableList<UserQualificationAssignmentEntity>,
)
data class UserQualificationAssignmentEntity(
#Id
val id: UUID,
#ManyToOne
#JoinColumn(name = "user_id")
val user: UserEntity,
#Enumerated(value = EnumType.STRING)
val qualification: UserQualification
)
#Entity
#Table(name = "user_role_assignments")
data class UserRoleAssignmentEntity(
#Id
val id: UUID,
#ManyToOne
#JoinColumn(name = "user_id")
val user: UserEntity,
#Enumerated(value = EnumType.STRING)
val role: UserRole
)
I have created a JPA Specification that optionally takes a List of UserRoles and / or UserQualifications and builds up some criteria:
class UserFilterSpecification(private val qualifications: List<UserQualification>?, private val roles: List<UserRole>?) : Specification<UserEntity> {
override fun toPredicate(
root: Root<UserEntity>,
query: CriteriaQuery<*>,
criteriaBuilder: CriteriaBuilder
): Predicate? {
val predicates = mutableListOf<Predicate>()
if (qualifications?.isNotEmpty() == true) {
predicates.add(
criteriaBuilder.and(
root.join<UserEntity, MutableList<UserQualificationAssignmentEntity>>("qualifications").`in`(qualifications)
)
)
}
if (roles?.isNotEmpty() == true) {
predicates.add(
criteriaBuilder.and(
root.join<UserEntity, MutableList<UserRoleAssignmentEntity>>("roles").`in`(roles)
)
)
}
return criteriaBuilder.and(*predicates.toTypedArray())
}
}
However, when I try to use this:
userRepository.findAll(UserFilterSpecification(qualifications, roles))
I get the error:
Unable to locate Attribute with the the given name [roles] on this ManagedType [uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.UserEntity]
for roles and:
Unable to locate Attribute with the the given name [qualifications] on this ManagedType [uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.UserEntity]
I'm assuming this is because I'm not doing the join correctly i.e:
root.join<UserEntity, MutableList<UserQualificationAssignmentEntity>>("qualifications").`in`(qualifications)
root.join<UserEntity, MutableList<UserQualificationAssignmentEntity>>("roles").`in`(roles)
I've tried a couple of permutations (role, roles.role etc), but nothing seems to stick. What am I missing?

How do I put JPA annotations on delegated properties in 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
)

findAllBy JPA with embeddedId

I have this Document hibernate jpa entity with an EmbeddedId:
#Entity
data class Document(
#EmbeddedId
#NotNull
val documentExpertId: DocumentExpertId,
// other fields
)
#Embeddable
data class DocumentExpertId(
#Column(nullable = false)
val expertId: String,
#Column(nullable = false)
val name: String
) : Serializable
To get all the documents by expertId, I would like to have my document JPA repository interface method called:
fun findAllByExpertId(String expertId): List<Document>
But, the only way I found to do so is:
fun findAllByDocumentExpertIdExpertId(String expertId): List<Document>
Is there an other way to have a better name for this method?
You could change the ID and column definition to:
#EmbeddedId
#NotNull
val documentExpertKey: DocumentExpertKey,
#Column(name = "expertId", nullable = false)
val id: String,
So that your query could be:
fun findAllByDocumentExpertKeyId(String expertId): List<Document>
That looks a little more normal to me.

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()
}