I'm using JPA as persistence API for a project. It contains an entity which has a Postgres jsonb column, such as below:
#Entity
#TypeDefs(value = [TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)])
data class Post(
//... irrelevant columns
#Type("jsonb") #Column(columnDefinition = "jsonb") val postDefinitions: List<Map<String, Any>>
)
This works perfectly when fetching all data from this entity. But now I don't need to return all of it, but some fields, including the jsonb.
That's where things get ugly. I have this projection object and I'm using the following native query to retrieve the data, but somehow JPA can't map it.
interface PostProjection {
val id: UUID
val postDefinitions: List<Map<String, Any>>
}
#Query("SELECT CAST(id AS VARCHAR) AS id, jsonb_array_elements(post_definitions) AS postDefinitions " +
"FROM post WHERE id = :postId", nativeQuery = true)
fun getPostDefinitionsById(val id: UUID): List<PostProjection>
It already has the getters and setters. I have tried annotating the interface with #TypeDefs, the column with #Type and changing it from interface to data class.
No success. Couldn't find anything about this. I wouldn't like to retrieve a String and then map it to the corresponding data type.
Has someone went through it?
Related
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!
)
I'm trying to implement a way of using a single database table to hold a number of application settings of varying object types.
I figured I would use generics in order to do this, however, I don't think I'm doing it correctly. This is what my entity looks like so far:
#Entity
#Table(name = "table_application_settings")
data class ApplicationSetting<T>(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "application_setting_id")
val id: Long? = null,
#Column(name = "application_setting_name")
var name: String = "",
#Column(name = "application_setting_value")
var value: T? = null,
)
My Repository:
#Repository
interface ApplicationSettingsRepository : JpaRepository<ApplicationSetting<*>, Long> {
fun findApplicationSettingByName(name: String): ApplicationSetting<*>
}
My Service:
#Service
#Transactional
class ApplicationSettingsServiceImpl(
private val applicationSettingsRepository: ApplicationSettingsRepository,
) : ApplicationSettingsService {
override fun saveBooleanApplicationSetting(applicationSetting: ApplicationSetting<Boolean>): ApplicationSetting<Boolean> {
return applicationSettingsRepository.save(applicationSetting)
}
override fun saveIntegerApplicationSetting(applicationSetting: ApplicationSetting<Int>): ApplicationSetting<Int> {
return applicationSettingsRepository.save(applicationSetting)
}
override fun getAllApplicationSettings(): MutableList<ApplicationSetting<*>> {
return applicationSettingsRepository.findAll()
}
}
However, I get the following error when I then try to run the application:
Caused by: org.hibernate.AnnotationException: Property com.jre.hireout.database.entities.application.ApplicationSetting.value has an unbound type and no explicit target entity. Resolve this Generic usage issue or set an explicit target attribute (eg #OneToMany(target=) or use an explicit #Type
I get that I'm having an annotation issue, however I'm unsure how to fix it along with getting this to work as intended.
There is no support for entities with generics in JPA.
The closest you can get is probably to have an entity that keeps the value as String which I guess is what you use in the database as well.
And then have a couple of methods to returning the value using different types, e.g. valueAsInt.
You will need to do the conversion yourself.
An alternative would be to have an inheritance hierarchy.
But by default that would map the value to different columns.
I'm not sure if you could map it to the same column without anything blowing up.
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.
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.
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
}
}