https://kotlinlang.org logo
Title
d

dave08

03/12/2023, 12:59 PM
Is there a way to add a field to an entity that's lazy? Meaning I don't want it loaded unless I ask for it in a select... @Toshihiro Nakamura
t

Toshihiro Nakamura

03/12/2023, 1:47 PM
Komapper does not have a way to retrieve data for a particular field lazily. However, it is possible to achieve similar results by splitting entity classes, for example.
d

dave08

03/12/2023, 2:06 PM
You mean making another entity class for the same table?
That wouldn't be so nice for schema generation... But I guess possible...
t

Toshihiro Nakamura

03/12/2023, 2:10 PM
You mean making another entity class for the same table?
Yes. Alternatively, you could treat the data type of that field as a Blob or Clob.
d

dave08

03/12/2023, 2:15 PM
Yeah... but it could be nice to have a
@KomapperLazy
or
@KomapperColumn(lazy = true)
, then it wouldn't be included in the select when loading the whole object, and it would have to be nullable to indicate it wasn't loaded, then only if specified specifically in the field list in select would it actually be loaded... it would allow to use that field in a type-safe way in where's inserts etc... w/o having to load it...
t

Toshihiro Nakamura

03/12/2023, 2:28 PM
Thanks for showing me your idea. However, I will not implement lazy loading in Komapper as it tends to cause problems.
d

dave08

03/12/2023, 2:29 PM
It's not really lazy loading... I mean, once the query is run, you can't access that field lazily... it's really like
@KomapperIgnore
but the difference is that it's a real field that is generated in schema generation and can be used in queries...
Maybe my naming is a bit wrong...
But I don't want to retrieve every field in my entity definition...
maybe it would also be ok to have an
excludeFields
in the querydsl, but it would be a bit less convenient.
Would this cause problems @Toshihiro Nakamura?
The only thing is representing the field when it's missing from the query... if it's nullable in the data class and not in the table it might not be so great.
Now that I think of it, this could be possible with an AlternateMapping... a value class could represent it as a Nothing but the mapper would not actually retrieve it... that would work, no?
The only problem would be when I actually need to retrieve it...
But for using it in queries, it should work.
Or maybe I can create my own PropertyMetamodelImpl for the field to use it in queries @Toshihiro Nakamura?
In that case, it might be enough just to have some kind of utility function to do such a thing for these kind of needs... they could even be defined as extension function on the model... then there wouldn't be any problems of initializing them
val MyEntity.address by lazyField("address")
t

Toshihiro Nakamura

03/12/2023, 2:50 PM
excludeFields in the querydsl,
This idea is OK. But wouldn’t having two entity classes for one table be simpler?
d

dave08

03/12/2023, 2:52 PM
No, it would mean unnecessary duplication... the only problem with that is how to represent the missing value in the data class... What do you think about my lazyField extension property idea?
It might even be possible to include it in generation... but it could only be used in queries, not actually retrieved unless retrieving individual fields.
Maybe not lazyField but
virtualField
or just
by field
and the fact that it's a val and just an extension property already indicates it can't be retrieved
t

Toshihiro Nakamura

03/12/2023, 3:13 PM
Can you show an example implementation of the lazyField extension? How do you intend to perform an INSERT or UPDATE on that field?
d

dave08

03/12/2023, 3:14 PM
It would be an extension function from the
Meta
object, not the actual entity... (I got confused at first)... and the same way you'd use a regular Meta definition...
The entity itself could maybe have it just as a placeholder to allow the generation to work like a
@KomapperIgnore
and for schema generation...
But it wouldn't be included in creation (maybe it would be in the class definition NOT the constructor...)
Another idea would be to have two constructors, one with the field and one w/o for the user to decide what to do when the field is NOT excluded...
t

Toshihiro Nakamura

03/12/2023, 3:28 PM
I may not fully understand your idea. It would be great if you could show me more code.
d

dave08

03/12/2023, 3:30 PM
1:
@KomapperEntity
data class Person(
  ...
) {
   @KomapperVirtual
   val address: String
}

// would create that field in the db, but not retrieve it by default
2:
@KomapperEntity
data class Person(
  ...
   @KomapperVirtual
   val address: String?
) {


  constructor(..., /* w/o address */) : this(..., adresss = null)
}

// would create that field in the db and allow using all fields (including address) or just the fields in one of the alternate constructors... then the user would contiously decide what to do to initialize optional fields.
// maybe a constructor could be annotated to help komapper resolve the right one in the query dsl... 
val person = QueryDSL.useConstructor(...)...
The first idea would only provide the ability to use fields that don't need to be retrieved in queries... the second would actually allow to optionally retrieve them (which is probably more complicated to implement...)
It could be that instead of creating a new annotation, it might be enough to reuse existing ones like
@KomapperColumn
since it would be distinguished by the fact that it's not in the constructor...
t

Toshihiro Nakamura

03/13/2023, 11:47 AM
Thanks for sharing the code. I understand that by default you do not want to get the value of the address property. But when you really need it, how are you going to get it?
d

dave08

03/13/2023, 12:33 PM
Just with a
select(...)
and get it as a value like this: https://www.komapper.org/docs/reference/query/querydsl/select/#select but it won't be in an instantiated entity object.
t

Toshihiro Nakamura

03/13/2023, 2:54 PM
@KomapperEntity
data class Person(
    @KomapperId
    val id: Int,
    @KomapperColumn(autoSelect = false)
    val address: String? = null
)
What do you think about the idea of adding an
autoSelect
property to
@KomapperColumn
? If this property is set to
false
, Komapper will check that the default value is specified for the annotated parameter at compile time.
d

dave08

03/13/2023, 3:34 PM
The only problem with that is, how will you differentiate between nullable fields and such defaults when generating schemas...?
That's why I proposed it NOT to be in the constructor, or to have an alternate constructor for such cases that the field isn't requested... then in the query, the user would need to specify which constructor to use somehow.
This is an important feature now that I'm in production... because this field is very big and I don't really need to retrieve it's contents, only to query it or save to it in an update statement...
t

Toshihiro Nakamura

03/15/2023, 3:49 AM
If you create two constructors, that is almost the same as creating two entity classes. Therefore, I still recommend making two entity classes.
@KomapperEntity
data class Person(
    @KomapperId
    val id: Int,
    val name: String,
    val age: Int,
)

@KomapperEntity
@KomapperTable("PERSON")
data class PersonAddress(
    @KomapperId
    val id: Int,
    val address: String // holds large data
)
The second class should only have an ID and a large field.
Ideally, the person table might be split into two
d

dave08

03/15/2023, 9:53 AM
In the end, that's what I did... but it does complicate working with Komapper for I think what is a pretty common use-case...
t

Toshihiro Nakamura

03/15/2023, 11:28 AM
maybe it would also be ok to have an excludeFields in the querydsl, but it would be a bit less convenient.
I see. Then how about achieving it with QueryDsl, as you say? For example,
val p = Meta.person
val query = QueryDsl.from(p).deselect(p.address, "defalt value")
The second argument of the
deselect
function is passed to the constructor of the entity.
d

dave08

03/15/2023, 11:30 AM
That would be a possibility, but it wouldn't express the case where the user is only including the field to be generated in the Meta object and in schema creation... say in a case where that field is NEVER included when retrieving the whole entity class (like in my case)...
Then there would be extra boilerplate in every query to explicitly exclude it.
In my case, that field is a sort of history that I write to, but I don't read from it.
t

Toshihiro Nakamura

03/15/2023, 12:29 PM
The best solution in your case would be to split the table in two.
Then there would be extra boilerplate in every query to explicitly exclude it.
You can reuse a query to reduce boilerplate:
// define a query once
val personQuery = QueryDsl.from(p).deselect(p.address, "defalt value")

// reuse the personQuery
val query1 = personQuery.where { p.id eq 1 }
val query2 = personQuery.where { p.age eq 40 }.orderBy(p.id)
You might as well use an extension function:
// define an extension function
fun <ENTITY : Any, ID : Any, META : EntityMetamodel<ENTITY, ID, META>> QueryDsl.fromPerson(): SelectQueryBuilder<ENTITY, ID, META> {
    val p = Meta.person
    return QueryDsl.from(p).deselect(p.address, "defalt value")
}

// use the extension function
val query = QueryDsl.fromPerson()
d

dave08

03/15/2023, 12:33 PM
So I guess it's much harder to implement what I was suggesting before then... thanks anyways!
You've already done tons to make this library so nice!