Closed lnhrdt closed 3 years ago
There might be side-effects when you modify data in a database and expect to see changes in a reference field, but while it was preloaded you will see a previous state.
Could you give a try to such code and write your feedback:
class EagerReferrers<Parent: Entity<Int>, Child: Entity<Int>>(ref: Referrers<Int, Parent, Int, Child>, entity: Parent){
private val cachedReferrers = if (ref.cache) ref else Referrers(ref.reference, ref.factory, true)
init {
cachedReferrers.getValue(entity, EagerReferrers<*,*>::cachedReferrers).toList()
}
operator fun getValue(o: Parent, desc: KProperty<*>): List<Child> = cachedReferrers.getValue(o, desc).toList()
}
fun <Parent: Entity<Int>, Child: Entity<Int>> Referrers<Int, Parent, Int, Child>.eager(e: Parent) = EagerReferrers(this, e)
...
val children by ChildEntity.referrersOn(ChildTable.parent).eager(this)
Interesting approach. I'm sure we can work out the kinks but right now I still get java.lang.IllegalStateException: No transaction in context.
but from the getValue
method in EagerReferrers
.
The side-effects you mentioned are important, however to me they're only a concern while still operating within a transaction. Alternatively, is it possible to keep referrers lazy until returning from the outermost transaction, at which point ensuring they're all fetched?
Sorry, I forgot to add cache:D
class EagerReferrers<Parent: Entity<Int>, Child: Entity<Int>>(ref: Referrers<Int, Parent, Int, Child>, entity: Parent) {
private val cachedReferrers = if (ref.cache) ref else Referrers(ref.reference, ref.factory, true)
private var cachedValue by Delegates.notNull<List<Child>>()
init {
getValue(entity, EagerReferrers<*, *>::cachedReferrers)
}
operator fun getValue(o: Parent, desc: KProperty<*>): List<Child> {
return if (TransactionManager.currentOrNull() == null)
cachedValue
else {
cachedReferrers.getValue(o, desc).toList().apply {
cachedValue = this
}
}
}
}
}
Would be nice if something more permeant made it into the API? If that's planned. I'm not sure how other DAO's handle this?
I too would really appreciate the ability to fetch references eagerly (both Reference and Referrers, optional cases as well). I'm fine defaulting fetching to lazy, but I think whoever is using the API should be able to decide if and when they need it to be eager, accepting the fact that there's a possibility of making the wrong decision which could cause an OOM exception.
toList also works poorly if you have dates: you get the same java.lang.IllegalStateException: No transaction in context.
exception from DateColumnType.valueFromDB
when it checks currentDialect.
I'm struggling to understand how I'm supposed to work with Exposed in these scenarios. Given a DAO class
class PageTemplates(id: EntityID<Long>) : LongEntity(id) {
var refName by PAGE_TEMPLATE.refName
val fields by InputFields optionalReferrersOn INPUT_FIELD.pageTemplate
}
What do I need to do to get a complete PageTemplates
object, including a list of InputFields
, detached from the database transaction and ready to send up to my service or web layer?
I've written a get()
function:
fun get(refName: String): PageTemplates? {
var template: PageTemplates? = null
transaction(dbConnections.connect()) {
addLogger(StdOutSqlLogger)
val result = PAGE_TEMPLATE.select {
PAGE_TEMPLATE.refName eq refName
}.firstOrNull()
if (result != null) {
template = wrapRow(result)
}
return template
}
But the returned object does not contain anything in the fields
list. I'm not even sure what Exposed expects me to expect? Do I need to write a separate function to get the fields
, then join them together up at the service layer? Can I 'attach' the fields to the PageTemplate somehow in the transaction?
Tacking on that the last scenario also still doesn't work for many-to-many relations using via.
My understanding is that all collections in Exposed are fetched lazily using the SizedIterable class. @Tapac has shared some of his reasoning (preventing out of memory errors) here https://github.com/JetBrains/Exposed/issues/131#issuecomment-315642092 and https://github.com/JetBrains/Exposed/issues/158#issuecomment-328367447.
The suggested solution for eager fetching seems to be calling
.toList()
on the result of a query, before returning from the transaction. However this doesn't solve the whole problem. Referrers would also need to be transformed with.toList()
. For example:Sure, one could call
.toList()
on eachchildren
of eachparent
but this starts to become cumbersome. In my case where I'm building things generically, it's become a showstopper.Is there a way to require eager fetching, perhaps at the transaction level? If not, @Tapac do you have any ideas for what the design could look like? Are you open to supporting this type of use case?