How can i extract a DTO from multiple tables in a micronaut application in Kotlin? - 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>

Related

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?

Kotmin Room crossRef Entity with keys having the same name

I'm creating a CRUD factory. Basically all my entites will inherit from a BaseEntity with id as a primary key
I'm trying to understand how to create a cross ref table for a M2M relationship.
Here is a simplifyied example, without inheritance. ArticlesEntity have many MagasinsEntity and MagasinsEntity many ArticlesEntity. The Entity ArticlesMagasinsCrossRef is the junction
But both ArticlesEntity and MagasinsEntity have id as the primaryKey.
#Entity(
tableName = "articles"
)
data class ArticlesEntity(
#PrimaryKey(autoGenerate = false) val id: UUID = UUID.randomUUID(),
val title: String,
)
#Entity(tableName = "magasins")
data class MagasinsEntity(
#PrimaryKey(autoGenerate = false) val id: UUID = UUID.randomUUID(),
val nomMagasin: String
)
#Entity(
tableName = "articles_magasins"
)
data class ArticlesMagasinsCrossRefEntity(
val id: UUID, // how is it possible here to have the id of Articles ?
val id: UUID // how is it possible here to have the id of Magasins ?
)
Edit
I tried of course to change the name of the columns:
data class ArticlesMagasinsCrossRefEntity(
val articleRd: UUID
val magasinId: UUID
)
but the build failed for the relation data class :for example
data class RelMagasinWithArticles(
#Embedded val magasin: MagasinsEntity,
#Relation(
parentColumn = "magasinId",
entityColumn = "id",
associateBy = Junction(ArticlesMagasinsCrossRefEntity::class)
)
val articles: List<ArticleEntity>
)
You need to use the Junction's parentColumn and entityColumn parameters e.g.
data class RelMagasinWithArticles(
#Embedded val magasin: MagasinsEntity,
#Relation(
parentColumn = "id", /* The column in the #Embedded table (articles) */
entityColumn = "id", /* The column in the Related table (magasins) */
associateBy = Junction(
ArticlesMagasinsCrossRefEntity::class,
parentColumn = "magasinId", /* The column in the junction table that maps to the #Embedded table */
entityColumn = "articleRd" /* The column in the junction table that maps to the #Relation Table */
)
)
val articles: List<ArticlesEntity>
)
Note
You will also need to define a primary key for the ArticlesMagasinsCrossRefEntity class e.g. :-
#Entity(
tableName = "articles_magasins",
/*<<<<< all Room table MUST have a primary key */
/* as primary key on a single column would be restrictive use a composite
primary key
*/
primaryKeys = ["articleRd","magasinId"]
)
data class ArticlesMagasinsCrossRefEntity(
/* Cannot have identical member names - i.e. only 1 could be id
val id: UUID, // how is it possible here to have the id of Articles ?
val id: UUID // how is it possible here to have the id of Magasins ?
*/
val articleRd: UUID,
/* Room will issue warning if the is no index on the 2nd column */
#ColumnInfo(index = true)
val magasinId: UUID
)
note that you can use the #ColumnInfo name parameter to specify column names.
Demonstration
So using you code with the suggested code overriding your code and with the following #Dao interface:-
#Dao
interface AllDao {
#Insert
fun insert(articlesEntity: ArticlesEntity)
#Insert
fun insert(magasinsEntity: MagasinsEntity)
#Insert
fun insert(articlesMagasinsCrossRefEntity: ArticlesMagasinsCrossRefEntity)
#Query("SELECT * FROM magasins")
#Transaction
fun getMWA(): List<RelMagasinWithArticles>
}
a suitable #Database annotated class and code in the activity:-
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
val a1uuid = UUID.randomUUID()
val a2uuid = UUID.randomUUID()
val a3uuid = UUID.randomUUID()
dao.insert(ArticlesEntity(a1uuid, "Article1"))
dao.insert(ArticlesEntity(a2uuid,"Article2"))
dao.insert(ArticlesEntity(a3uuid,"Article3"))
val m1uuid = UUID.randomUUID()
val m2uuid = UUID.randomUUID()
val m3uuid = UUID.randomUUID()
dao.insert(MagasinsEntity(m1uuid,"Magasin1"))
dao.insert(MagasinsEntity(m2uuid,"Magasin2"))
dao.insert(MagasinsEntity(m3uuid,"Magasin3"))
dao.insert(ArticlesMagasinsCrossRefEntity(a1uuid,m2uuid))
dao.insert(ArticlesMagasinsCrossRefEntity(a1uuid,m3uuid))
dao.insert(ArticlesMagasinsCrossRefEntity(a2uuid,m1uuid))
dao.insert(ArticlesMagasinsCrossRefEntity(a3uuid,m1uuid))
dao.insert(ArticlesMagasinsCrossRefEntity(a3uuid,m2uuid))
dao.insert(ArticlesMagasinsCrossRefEntity(a3uuid,m3uuid))
val sb = StringBuilder()
for(mwa in dao.getMWA()) {
sb.append("\nMagasin is ${mwa.magasin.nomMagasin}. ID is ${mwa.magasin.id} it has ${mwa.articles.size} articles. They are:-" )
for (article in mwa.articles) {
sb.append("\n\tArticle is ${article.title} ID is ${article.id}")
}
}
Log.d("DBINFO",sb.toString())
}
The output to the log is:-
D/DBINFO: Magasin is Magasin1. ID is 0f3384ee-6232-423e-b2f1-a12ebdac6487 it has 2 articles. They are:-
Article is Article2 ID is 2729d017-de05-41d2-8de3-8351dfca0a6b
Article is Article3 ID is 42057ea7-bc03-409f-b2b8-2dc3fa5def19
Magasin is Magasin2. ID is ba649833-a8ce-4cf2-a1b8-bcab8f7a7d0a it has 2 articles. They are:-
Article is Article1 ID is 8763421d-b86d-4725-8e6b-65570958ebdc
Article is Article3 ID is 42057ea7-bc03-409f-b2b8-2dc3fa5def19
Magasin is Magasin3. ID is eed6f0a5-0825-4cda-9eb4-c4e973a49738 it has 2 articles. They are:-
Article is Article1 ID is 8763421d-b86d-4725-8e6b-65570958ebdc
Article is Article3 ID is 42057ea7-bc03-409f-b2b8-2dc3fa5def19

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.

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