Is there a way to add a field to an entity that's ...
# komapper
d
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
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
You mean making another entity class for the same table?
That wouldn't be so nice for schema generation... But I guess possible...
t
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
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
Thanks for showing me your idea. However, I will not implement lazy loading in Komapper as it tends to cause problems.
d
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
excludeFields in the querydsl,
This idea is OK. But wouldn’t having two entity classes for one table be simpler?
d
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
Can you show an example implementation of the lazyField extension? How do you intend to perform an INSERT or UPDATE on that field?
d
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
I may not fully understand your idea. It would be great if you could show me more code.
d
1:
Copy code
@KomapperEntity
data class Person(
  ...
) {
   @KomapperVirtual
   val address: String
}

// would create that field in the db, but not retrieve it by default
2:
Copy code
@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
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
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
Copy code
@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
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
If you create two constructors, that is almost the same as creating two entity classes. Therefore, I still recommend making two entity classes.
Copy code
@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
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
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,
Copy code
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
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
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:
Copy code
// 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:
Copy code
// 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
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!