Kotlin Exposed: Proper way to create a One To Many Relationship? - sql

I want to create an One-To-Many Relationship from the Order Entity to the OrderProductAmount Entity.
I need this, because for each Order I need to know which Product's it contains and what the amount of each Product in the Order is, as one order can contain multiple products.
When I fetch Order Entities from the database, I want to be able to access all rows from OrderProductAmount that contain the corresponding orderId from it.
But whenever I access order.products from the result of findOrder I get null as the result.
I suppose there is some mistake in my setup of the entities or I am not inserting the entities into the database in the right way.
Order Entity
class Order(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Order>(Orders)
var orderStatus by Orders.orderStatus
val products by OrderProductAmount referrersOn OrderProductAmounts.order
}
object Orders : IntIdTable() {
var orderStatus = enumeration("orderStatus", OrderStatus::class)
}
OrderProductAmount Entity
class OrderProductAmount(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<OrderProductAmount>(OrderProductAmounts)
var order by Order referencedOn OrderProductAmounts.order
var product by OrderProductAmounts.product
var amount by OrderProductAmounts.amount
}
object OrderProductAmounts : IntIdTable() {
var order = reference("order", Orders)
var product = integer("product")
var amount = integer("amount")
}
Product Entity
class Product(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Product>(Products)
var name by Products.name
var price by Products.price
}
object Products : IntIdTable() {
val name = varchar("name", length = 256)
val price = decimal("price", precision = 10, scale = 2)
}
Inserting new entities ...
override suspend fun addOrder(productIdToAmount: Map<Int, Int>) = dbQuery {
val orderId = Orders.insertAndGetId {
it[Orders.orderStatus] = OrderStatus.CREATED
}
productIdToAmount.entries.forEach { (productId, amount) ->
val product = productDAO.findProduct(productId)!!
OrderProductAmounts.insert {
it[OrderProductAmounts.order] = orderId.value
it[OrderProductAmounts.product] = product.id.value
it[OrderProductAmounts.amount] = amount
}
}
}
Fetching an order from the database ...
override suspend fun findOrder(id: Int): Order? = dbQuery {
Orders.select { Orders.id eq id }.map { Order.wrapRow(it) }.singleOrNull()
}

It seems that your mapping for products is a bit off.
It should be:
val products by Product via OrderProductAmounts
First comes the type of the collection, then the connection table.

Related

Room query with id doesn't return the right list RxJava MVVM architecture

I have a problem concerning my query returns, I have a student class that contains a string id from another table
data class StudentEntity(
#PrimaryKey
val idStudent: String,
val classId: String,
val name: String,
val notes: Note?,
)
I also created a room database that I'm populating from my api call
#Database(entities = [Student::class, Note::class], version = 14, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun programDAO(): ProgramDAO
companion object{
#Volatile
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context) : AppDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext, AppDatabase::class.java, "student-database"
).fallbackToDestructiveMigration().build()
INSTANCE = instance
}
return instance
}
}
}
}
and for that, I have a programDao that helps me to run my queries
#Dao
interface ProgramDAO {
#Transaction
#Query("select * from studentEntity")
fun getStudents(): Single<List<StudentEntity>>
#Transaction
#Query("select * from studentEntity where classId = :classid")
fun getStudentsWithId(classid: String): Single<List<StudentEntity>>
}
In order to execute those queries, I have my Repository:
class ProgramRepository(val api: ApiService, val programDAO: ProgramDAO) {
fun getStudentsFromDbWithId(idClass: String) : Observable<StudentEntity>{
return programDAO.getStudentsWithId(idClass).toObservable()
}
fun getStudentsFromDb() : Observable<StudentEntity>{
return programDAO.getStudents().toObservable()
}
}
The MV allows me to connect the data and the view:
class ProgramListViewModel(private val programRepository: ProgramRepository) {
fun getListFromDBWithId(classID: String): Observable<List<StudentEntity>> {
return programRepository.getStudentsFromDbWithId(deliverySiteId)
}
fun getListFromDB(): Observable<List<StudentEntity>> {
return programRepository.getStudentsFromDb()
}
}
So in order to use the data and get the adapter and the KPIs on my fragment, I don't receive the right list from the database, I did log the results to see what I get, to start, I do log the whole list without id, and I have the list, but when I use an ID like:
subscribe(programListViewModel.getListFromDBWithId("111").subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Log.e("list of students", it.toString())
})
I got an empty list, so I thought the problem was from the id, I tried to use the id that has been logged from the whole list and didn't work, I also used S to launch my sql query with the same id and I got the results.
Any help please?
Thanks
Is your AircraftEntity supposed to be StudentEntity? If yes (and I think it is, according to the rest of your code examples), then please update your question.
By default, Room uses the class name as the database table name, and I think it's case-sensitive, so your queries should have been "select * from StudentEntity". The better approach would be giving it a name explicitly:
#Entity(tableName = "students")
data class StudentEntity (
// ...
)
Then, your ProgramDAO would look like follow:
#Dao
interface ProgramDAO {
#Query("select * from students")
fun getStudents(): Single<List<StudentEntity>>
#Query("select * from students where classId = :classid")
fun getStudentsWithId(classid: String): Single<List<StudentEntity>>
}
You said: ... a room database that I'm populating from my api call
Where and how do you populate your database? I don't see any #Insert functions in your DAO, unless you just left them out from your code snippet. If your room DB is not populated, of course you will get no data.

can you join two tables and result with an obj (from first table) containing a list of obj(from the second table)

First of all my code:
Table 1:
object Company : Table() {
val name = varchar("pk_name", 250)
override val primaryKey = PrimaryKey(name, name = "pk_company_constraint")
}
Table 2&3:
object Sector : IntIdTable() {
val name = varchar("fk_name", 50).references(MainSector.name)
val alias = varchar("alias", 50).nullable()
val companyName = varchar("fk_company_name", 250).references(Company.name, onDelete = ReferenceOption.CASCADE)
}
object MainSector : Table() {
val name = varchar("pk_name", 50)
override val primaryKey = PrimaryKey(name, name = "pk_main_sector_constraint")
}
My Problem:
I need to parse the result into a DTO that looks like this:
data class CompanyDTO (
val companyName: String,
val sectorList: List<SectorDTO>
)
data class SectorDTO (
val mainSectorName: String,
val sectorAlias: String
)
I am able to get a Company with the first Sector from the database, but i have no idea how to get a list of them.
My try:
override fun retrieveCompanies(vararg names: String): List<CompanyDTO> {
var retlist: List<CompanyDTO> = emptyList()
if (names.isEmpty()){
retlist = transaction {
(Company innerJoin Sector)
.select{Company.name eq Sector.companyName}
.map { CompanyDTO(it[Company.name], listOf(
SectorDTO(it[Sector.name], it[Sector.alias]?: "")
)) }
}
} else {
//return specific
}
return retlist
}
If no arguments are given i want to return all companies from the database, if arguments are given i want to return only companies with given name.
I canĀ“t find anything about this topic in the official documentation, please send help
If Company could not have any Sector you need to use leftJoin and then your code could be like:
Company.leftJoin.Sector.selectAll().map {
val companyName = it[Company.name]
val sector = it.tryGet(Sector.name)?.let { name ->
SectorDTO(name, it[Sector.alias].orEmpty())
}
companyName to sector
}.groupBy({ it.first }, { it.second }).map { (companyName, sectors) ->
CompanyDTO(companyName, sectors.filterNotNull())
}

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
}

Kotlin Exposed Many-to-Many Jackson Infinite recursion (StackOverflowError)

I have done the many-to-many reference in ExposedBD (kotlin) as follows in the wiki:
https://github.com/JetBrains/Exposed/wiki/DAO#many-to-many-reference
However, there is a problem of Infinite Recursion (Jackson) when I am trying to return a list of objects in my API (Javalin).
So, I would like to know how to put the annotation #jsonIgnore or if there are other alternative solutions in this case. Here is the mapping:
// many-to-many Actor--StarWarsFilms
// Actor Entity
object Actors: IntIdTable() {
val firstname = varchar("firstname", 50)
val lastname = varchar("lastname", 50)
}
class Actor(id: EntityID<Int>): IntEntity(id) {
companion object : IntEntityClass<Actor>(Actors)
var firstname by Actors.firstname
var lastname by Actors.lastname
}
// StarWarFilm Entity
object StarWarsFilms : IntIdTable() {
val sequelId = integer("sequel_id").uniqueIndex()
val name = varchar("name", 50)
val director = varchar("director", 50)
}
class StarWarsFilm(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<StarWarsFilm>(StarWarsFilms)
var sequelId by StarWarsFilms.sequelId
var name by StarWarsFilms.name
var director by StarWarsFilms.director
var actors by Actor via StarWarsFilmActors
}
// Intermediate table
object StarWarsFilmActors : Table() {
val starWarsFilm = reference("starWarsFilm", StarWarsFilms).primaryKey(0)
val actor = reference("actor", Actors).primaryKey(1)
}

How do I search from multiple tables with DAO?

Say I have tables like this:
object Leagues : IntIdTable() {
val name = varchar("name", 50).uniqueIndex()
}
object Matches: IntIdTable() {
val game = reference("game", Games)
}
object Users: IntIdTable() {
val name = varchar("name", 50).uniqueIndex()
}
object Bets: IntIdTable() {
val match = reference("match", Matches)
val user = reference("user", Users)
}
Daos are in the lines of:
class Bet(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Bet>(Bets)
var match by Bets.match
var user by Bets.user
}
How do I write the dao or the query for the Bets class so I can query "give me all bets player X has made in league Y". Bet.find { (user eq X) and (/* what here to get the leagues table ? */) }
val bets = Bet.wrapRows(
Bets.innerJoin(Matches).innerJoin(Leagues).select {
Bets.user eq X.id and (Leagues.name eq "Y"
}
).toList()