jOOQ insert/update from data class - kotlin

jOOQ has this nice feature of letting you map results into a data class:
data class User(id: Int, email: String)
val users: List<User> = ctx.select().from(USERS).fetchInto(User::class.java)
Is there a similar way to write an insert using an automatic data mapping from a data class?
How about an update?

The inverse of calling the various "into" methods, such as ResultQuery.fetchInto(Class) is to load data into a record using various "from" methods, e.g. Record.from(Object) or DSLContext.newRecord(Table, Object), so:
val user: User = ...
val record: UserRecord = ctx.newRecord(USERS, user);
// Using statements
ctx.insertInto(USERS).set(record).execute();
ctx.update(USERS).set(record).where(...).execute();
// Using UpdatableRecord. See Javadoc about each one of these:
record.insert();
record.update();
record.store();
record.merge();
Since your data class has no notion of "dirty flag", this will always set all the values in the UserRecord to Record.changed() == true. You can reset the changed flag if required.

Related

Jooq: How can I map a JSONB column to a Kotlin data class field?

I have this table that has a metadata jsonb column, that's supposed to be a json array of data about other tables/PKs. I am able to insert rows into the database, but am having a hard time mapping the the record into the data class, due to this json column.
CREATE TABLE IF NOT EXISTS tracked_event
(
id uuid primary key,
user_id uuid references "user" not null,
-- other columns
metadata jsonb not null
);
And I have a data class for it:
data class TrackedEvent(
val id: UUID,
val userId: UUID,
// other fields
val metadata: List<Metadata>
)
data class Metadata(
val tableRef: String,
val value: UUID
)
I can create a row just fine for it like so:
fun createTrackedEvent(trackedEvent: TrackedEvent): TrackedEvent {
val record = dslContext.newRecord(TRACKED_EVENT, trackedEvent)
record.metadata = JSONB.jsonb(objectMapper.writeValueAsString(trackedEvent.metadata))
record.store()
return record.into(TrackedEvent::class.java) // issue here
}
However, that last line of code has a serializing issue:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: object is not an instance of declaring class; nested exception is com.fasterxml.jackson.databind.JsonMappingException: object is not an instance of declaring class (through reference chain: com.my.project.TrackedEvent["metadata"]->java.util.ArrayList[0]->java.util.LinkedHashMap["tableRef"])]
Note that if I change the data class to use an Array instead of a List, it works fine. But I think this should be able to work with the Kotlin's List instead?
data class TrackedEvent(
val id: UUID,
val userId: UUID,
// other fields
val metadata: Array<Metadata> // this works but then it asks me the following: Property with 'Array' type in a 'data' class: it is recommended to override 'equals()' and 'hashCode()'
)
The best approach is to attach a Converter directly to your generated code as documented here:
Forced types
Jackson converters (this might work out of the box)
That way, the conversion from/to JSONB / List<MetaData> will be done transparently, whenever you access this information. Code generation configuration from the above documentation:
<configuration>
<generator>
<database>
<forcedTypes>
<forcedType>
<userType><![CDATA[kotlin.Array<com.example.Metadata>]]></userType>
<jsonConverter>true</jsonConverter>
<includeExpression>(?i:tracked_event\.metadata)</includeExpression>
</forcedType>
</forcedTypes>
</database>
</generator>
</configuration>
See the docs for more details, and additional dependencies required.
Edit:
Because we don't use a KotlinGenerator, we have to use a Metadata[] instead: <userType><![CDATA[com.example.Metadata[]]]></userType>
This allows me to fetch data fine from a repo call like this:
fun findAllTrackedEvents(): List<TrackedEvent> {
return dslContext.select(*TRACKED_EVENT.fields())
.from(TRACKED_EVENT)
.fetchInto(TrackedEvent::class.java)
}
However, creating a row now no longer works as it appears the record model cannot be created from the data class model.
// error: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.example.Metadata` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('Metadata(tableRef=assessment, id=febe5f76-c25f-44f2-a501-c0b26e6fd173, extra=This is more data)')
at [Source: (String)"["Metadata(table=assessment, id=febe5f76-c25f-44f2-a501-c0b26e6fd173, extra=This is more data)"]"; line: 1, column: 2] (through reference chain: java.lang.Object[][0])
fun createTrackedEvent(trackedEvent: TrackedEvent): TrackedEvent {
val record = dslContext.newRecord(TRACKED_EVENT, trackedEvent) // code fails on this line
// record.metadata = JSONB.jsonb(objectMapper.writeValueAsString(trackedEvent.metadata))
record.store()
return record.into(TrackedEvent::class.java)
}
Original:
The answer that #Lukas Eder provided me helped me get to the solution! I figured I'd expand on it a bit more here in case anyone came to this problem as well.
We generate Java class models in this project, so I was able to get this working by having the user type be the following:
<userType><![CDATA[java.util.List<com.example.Metadata>]]></userType>
Then in my Repository, I no longer have to map the metadata array to the jsonb:
fun createTrackedEvent(trackedEvent: TrackedEvent): TrackedEvent {
val record = dslContext.newRecord(TRACKED_EVENT, trackedEvent)
// record.metadata = JSONB.jsonb(objectMapper.writeValueAsString(trackedEvent.metadata)) // this is no longer needed! YAY :D
record.store()
return record.into(TrackedEvent::class.java)
}
data class TrackedEvent(
val id: UUID,
val userId: UUID,
// other fields
val metadata: List<Metadata> // this stayed as a List instead of an array!
)

get declared fields in kotlin multiplatform

is there a way, to dynamically get reference to object field in kotlin multiplatform? I am trying to dynamically create instances of generic object from json input, in which I need to define exact field of a data object.
I have tried using reflections, but it is not available in multiplatform.
So I need something like
class User(
var lastName: String? = null,
var firstName: String? = null,
)
val jsonName="firstname"
val jsonModel="User::firstName"
val referenceJvm = User::class.declaredMembers // I cannot use this, only available for JVM
val referenceToField=User::firstName // I need to get this dynamically by using value in jsonModel property
StringField(
fieldName = jsonName,
model = referenceToField //this should be reference to firstName field in User class
)
Only solution I can think of is to have one huge dictionary/map where I would hardcode this. But application has hundreds possible models, so it is not really a solution I am looking for.

How to effectively map between Enum in Kotlin

I have two Enums,
enum class EnumKey
enum class EnumValue
and I already have a mapping from EnumKey to EnumValue.
fun EnumKey.toEnumValue(): EnumValue =
when(this) {
EnumA.KEY1 -> EnumValue.VALUE1
EnumA.KEY2 -> EnumValue.VALUE2
...
...
EnumA.KEY1000 -> EnumValue.VALUE1000
}
Now I need to have an another mapping from EnumValue to EnumKey.
Is using a Map and its reversed map created by associateBy the best way to do it? Or is there any other better ways?
Thanks!
If the enum values are somehow connected by name and they're as large as in your example, then I would advise using something like EnumValue.values().filter { it.name.contains(...) } or using regex.
If they aren't and the connection needs to be stated explicitly then I would use an object (so it's a singleton like the enums themselves) and have this mapping hidden there:
object EnumsMapping {
private val mapping = mapOf(
EnumKey.A to EnumValue.X,
EnumKey.B to EnumValue.Y,
EnumKey.C to EnumValue.Z,
)
....
and next, have the associated values available by functions in this object like:
fun getEnumValue(enumKey: EnumKey) = mapping[enumKey]
and
fun getEnumKey(enumValue: EnumValue) = mapping.filterValues { it == enumValue }.keys.single()
If it's often used or the enums are huge, and you're troubled by the performance of filtering the values every time, then you can create the association in the second way, just like you've proposed:
private val mapping2 = mapping.toList()
.associate { it.second to it.first }
and then have the second function just access this new mapping.
Writing the extension functions like you've provided, but using this object, will result in cleaner code and having the raw association still in one place.

Kotlin multiple class for data storage

I am developing a simple Android app, that will display an icon of a vehicle and the user can click on the icon to display the vehicle information. I want to load the data dynamically when I build the app i.e. the data will come from an external source including the picture for the icon.
I am new to Kotlin and not sure what to search for to understand a suitable solution. What is the correct way to define the data, is it best to create an class as below then create an array of the class (not sure if this is possible)
public class VehicleSpec()
{
var OEM: String? = null
var ModelName: String? = null
var EngineSize: String? = null
}
Or would be better to create a multiple dimension array and then link the data to the cells?
var VehicleSpec = arrayOf(20,20)
VehicleSpec[0][0] = Null //OEM
VehicleSpec[0][1] = Null //ModelName
VehicleSpec[0][2] = Null //EngineSize
What is the best way to set up the data storage, is there any good references to understand how this should be setup?
What is the correct way to define the data, is it best to create an class as below then create an array of the class
Using an array for the properties of an object is not making the full use of the type safety you have in Kotlin (and even Java for that matter).
If what you want to express is multiple properties of an object, then you should use a class to define those properties. This is especially true if the properties have different types.
There is no performance difference between an array and a class, because you'll get a reference to the heap in both cases. You could save on performance only if you convert your multi-dimensional array approach to a single-dimension array with smart indexing. Most of the time, you should not consider this option unless you are handling a lot of data and if you know that performance is an issue at this specific level.
(not sure if this is possible)
Defining lists/arrays of classes is definitely possible.
Usually, for classes that are only used as data containers, you should prefer data classes, because they give you useful methods for free, and these methods totally make sense for simple "data bags" like in your case (equals, hashcode, component access, etc.).
data class Vehicle(
val OEM: String,
val ModelName: String,
val EngineSize: String
)
Also, I suggest using val instead of var as much as possible. Immutability is more idiomatic in Kotlin.
Last but not least, prefer non-null values to null values if you know a value must always be present. If there are valid cases where the value is absent, you should use null instead of a placeholder value like empty string or -1.
First at all, using the "class aprocah" makes it easy for you to understand and give you the full benefits of the language itself... so dont dry to save data in an array .. let the compiler handle those stuff.
Secondly i suggest you have maybe two types (and use data classes ;-) )
data class VehicleListEntry(
val id: Long,
val name: String
)
and
data class VehicleSpec(
val id: Long,
val oem: String = "",
val modelName: String = "",
val engineSize: String = ""
)
from my perspective try to avoid null values whenever possible.
So if you have strings - which you are display only - use empty strings instead of null.
and now have a Model to store your data
class VehicleModel() {
private val specs: MutableMap<Long, VehicleSpec> = mutableMapOf()
private var entries: List<VehicleListEntry> = listOf()
fun getSpec(id: Long) = specs[id]
fun addSpec(spec: VehicleSpec) = specs[spec.id] = spec
fun getEntries(): List<VehicleListEntry> = entries
fun setEntries(data: List<VehicleListEntry>) {
entries = data.toMutableList()
}
}
You could also use a data class for your model which looks like
data class VehicleModel(
val specs: MutableMap<Long, VehicleSpec> = mutableMapOf(),
var entries: List<VehicleListEntry> = listOf()
)
And last but not least a controller for getting stuff together
class VehicleController() {
private val model = VehicleModel()
init{
// TODO get the entries list together
}
fun getEntries() = model.entries
fun getSpec(id: Long) : VehicleSpec? {
// TODO load the data from external source (or check the model first)
// TODO store the data into the model
// TODO return result
}
}

How to make a builder for a Kotlin data class with many immutable properties

I have a Kotlin data class that I am constructing with many immutable properties, which are being fetched from separate SQL queries. If I want to construct the data class using the builder pattern, how do I do this without making those properties mutable?
For example, instead of constructing via
var data = MyData(val1, val2, val3)
I want to use
builder.someVal(val1)
// compute val2
builder.someOtherVal(val2)
// ...
var data = builder.build()
while still using Kotlin's data class feature and immutable properties.
I agree with the data copy block in Grzegorz answer, but it's essentially the same syntax as creating data classes with constructors. If you want to use that method and keep everything legible, you'll likely be computing everything beforehand and passing the values all together in the end.
To have something more like a builder, you may consider the following:
Let's say your data class is
data class Data(val text: String, val number: Int, val time: Long)
You can create a mutable builder version like so, with a build method to create the data class:
class Builder {
var text = "hello"
var number = 2
var time = System.currentTimeMillis()
internal fun build()
= Data(text, number, time)
}
Along with a builder method like so:
fun createData(action: Builder.() -> Unit): Data {
val builder = Builder()
builder.action()
return builder.build()
}
Action is a function from which you can modify the values directly, and createData will build it into a data class for you directly afterwards.
This way, you can create a data class with:
val data: Data = createData {
//execute stuff here
text = "new text"
//calculate number
number = -1
//calculate time
time = 222L
}
There are no setter methods per say, but you can directly assign the mutable variables with your new values and call other methods within the builder.
You can also make use of kotlin's get and set by specifying your own functions for each variable so it can do more than set the field.
There's also no need for returning the current builder class, as you always have access to its variables.
Addition note: If you care, createData can be shortened to this:
fun createData(action: Builder.() -> Unit): Data = with(Builder()) { action(); build() }.
"With a new builder, apply our action and build"
I don't think Kotlin has native builders. You can always compute all values and create the object at the end.
If you still want to use a builder you will have to implement it by yourself. Check this question
There is no need for creating custom builders in Kotlin - in order to achieve builder-like semantics, you can leverage copy method - it's perfect for situations where you want to get object's copy with a small alteration.
data class MyData(val val1: String? = null, val val2: String? = null, val val3: String? = null)
val temp = MyData()
.copy(val1 = "1")
.copy(val2 = "2")
.copy(val3 = "3")
Or:
val empty = MyData()
val with1 = empty.copy(val1 = "1")
val with2 = with1.copy(val2 = "2")
val with3 = with2.copy(val3 = "3")
Since you want everything to be immutable, copying must happen at every stage.
Also, it's fine to have mutable properties in the builder as long as the result produced by it is immutable.
It's possible to mechanize the creation of the builder classes with annotation processors.
I just created ephemient/builder-generator to demonstrate this.
Note that currently, kapt works fine for generated Java code, but there are some issues with generated Kotlin code (see KT-14070). For these purposes this isn't an issue, as long as the nullability annotations are copied through from the original Kotlin classes to the generated Java builders (so that Kotlin code using the generated Java code sees nullable/non-nullable types instead of just platform types).