JPA Specification joins with Kotlin - 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?

Related

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

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

Get HashMap<Model1, List<Model2>> in an MVVM+Repostiory+LiveData setting?

So I was working on this silly little app for practicing MVVM and Repository Pattern. I have two model classes at the moment. They are Category and SubCategory for which I have defined the following data classes:
#Entity(tableName = "categories")
data class Category(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
val id: Int,
#ColumnInfo(name = "name")
val name: String
) {
}
And
/**
* One to many Relationship from Category to SubCategory
*/
#Entity(
tableName = "sub_categories", foreignKeys = arrayOf(
ForeignKey(
entity = Category::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("category_id")
)
)
)
data class SubCategory(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
val id: Int,
#ColumnInfo(name = "name")
val name: String,
#ColumnInfo(name = "category_id")
val categoryId: Int
) {
}
As you can see, I have modeled the resources such that we will need categoryId to be passed to get SubCategories related to a Category.
Now I am pretty new with this MVVM and LiveData and Repository Pattern.
My Problem is that I am using an ExpandableListView to populate SubCategories under Categories and the Adapter for it requires a HashMap<Category, List<SubCategory> for it to display the expandable listview.
So my question is how do I get a HashMap<Category, List<SubCategory> from my database using an approach of db->dao->repository->viewmodel and wherever the adpater goes.
I suppose creating a separate repository like CategorySubCategoryRespository whereby I can do something like following is not going to help?:
class CategorySubCategoryRepository(
private val categoryDao: CategoryDao,
private val subCategoryDao: SubCategoryDao
) {
val allCategoriesSubCategories: LiveData<HashMap<Category, List<SubCategory>>>
get() {
val hashMap: HashMap<Category, List<SubCategory>> = hashMapOf()
for (category in categoryDao.getList()) {
hashMap[category] = subCategoryDao.getSubCategoriesListForCategory(category.id)
}
return hashMap
}
}
}
PS: I think I want to use LiveData wherever possible
So what I ended up doing was, in my CategorySubcategoryRepository I constructed the Hashmap from the CategoryDao and SubcategoryDao as follows:
class CategorySubCategoryRepository(
private val categoryDao: CategoryDao,
private val subCategoryDao: SubCategoryDao
) {
fun getHashMap(): LiveData<HashMap<Category, List<SubCategory>>> {
val data = MutableLiveData<HashMap<Category, List<SubCategory>>>()
val hashMap: HashMap<Category, List<SubCategory>> = hashMapOf()
Executors.newSingleThreadExecutor().execute {
for (category in categoryDao.getList()) {
hashMap[category] = subCategoryDao.getSubCategoriesListForCategory(category.id)
}
}
data.value = hashMap
return data
}
}
Then I used this in my viewmodel's init{} block like:
hashMap = categorySubCategoryRepository.getHashMap()
Then I observed it in my Fragment's onCreateView as:
myViewModel.hashMap.observe(this, Observer {
adapter.setCategoryList(it.keys.toList())
adapter.setCategorySubCategoriesMap(it)
elv_categories.setAdapter(adapter)
adapter.notifyDataSetChanged()
})
Do Comment if this is not the right thing to do. I am doing this only to increase my skills and would love to hear if there's a better way to go about things or if my approach is completely absurd.
Edit:
As per #SanlokLee's comment. The getHashMap function has been changed to:
fun getHashMap(): LiveData<HashMap<Category, List<SubCategory>>> {
val data = MutableLiveData<HashMap<Category, List<SubCategory>>>()
Executors.newSingleThreadExecutor().execute {
val hashMap: HashMap<Category, List<SubCategory>> = hashMapOf()
for (category in categoryDao.getList()) {
hashMap[category] = subCategoryDao.getSubCategoriesListForCategory(category.id)
}
data.postValue(hashMap)
}
return data
}

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