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.
Related
I have a data https://gist.githubusercontent.com/iva-nova-e-katerina/fc1067e971c71a73a0b525a21b336694/raw/954477261bb5ac2f52cee07a8bc45a2a27de1a8c/data2.json a List with seven CheckResultItem elements.
I trying to parse them this way:
import com.fasterxml.jackson.module.kotlin.readValue
...
val res = restHelper.objectMapper.readValue<List<CheckResultItem>>(text)
which gives me the following error:
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.fmetric.validation.api.Brick] value failed for JSON property upperLevelBricks due to missing (therefore NULL) value for creator parameter upperLevelBricks which is a non-nullable type
at [Source: (StringReader); line: 1, column: 714] (through reference chain: java.util.ArrayList[0]->com.fmetric.validation.api.checking.CheckResultItem["brick"]->com.fmetric.validation.api.Brick["upperLevelBricks"])
at com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator.createFromObjectWith(KotlinValueInstantiator.kt:116)
There is #JsonIgnore annotation in data class :
data class Brick(
val id: UUID?,
val name: String,
val type: BrickType,
val propertyValues: List<ProjectBrickPropertyValue<*>>,
#JsonIgnore
val upperLevelBricks: ArrayList<Brick>,
val downLevelBricks: ArrayList<Brick>,
var drawingDetails: List<BrickDrawingDetails>?
) {
But it seems it doesn't work. Could you explain me what is wrong?
UPD: Also I have tried #JsonIgnoreProperties({"upperLevelBricks"}) class annotation but it doesn't work. My solution was to set a default value
val upperLevelBricks: ArrayList<Brick> = arrayListOf(),
But I think that annotations should work!
Actually, it works, but not the way you think. During deserialization #JsonIgnore ignores the respectful field in JSON, like it wasn't there (but it's doesn't make sense in this case, because it's initially absent in JSON).
In Java, Jackson would've just instantiated class with null value for the absent field (because all object types in Java are nullable, which means they allow the value to be set to null). But in Kotlin, a property should be explicitly marked as nullable (val upperLevelBricks: List<Brick>?) or have a default value (val upperLevelBricks: List<Brick> = emptyList()) so that Jackson could create a class instance in this case.
Note that approach with default value for property won't work (unless you additionally mark it with #JsonIgnore) if this field is present in JSON but explicitly set to null:
{
...
"upperLevelBricks": null,
...
}
Anyway, if you don't want to change the API of your Brick class you may provide a default value for this field only when it's created during Jackson deserialization (and only if it's absent/null in JSON) via custom deserializer:
object EmptyListAsDefault : JsonDeserializer<List<Brick>>() {
override fun deserialize(jsonParser: JsonParser, context: DeserializationContext): List<Brick> =
jsonParser.codec.readValue(
jsonParser,
context.typeFactory.constructCollectionType(List::class.java, Brick::class.java)
)
override fun getNullValue(context: DeserializationContext): List<Brick> = emptyList()
}
data class Brick(
//...
#JsonDeserialize(using = EmptyListAsDefault::class)
val upperLevelBricks: List<Brick>,
//...
)
I have seen a lot of examples of interfaces for enums methods here but I am looking for another thing.
I want to assure some string enums have at least three keys there:
enum InterstitialEnum(val webName: string) {
Showed("interstitialShowed"),
Dismissed("interstitialDismissed"),
Failed("interstitialFailed"),
SomeInterstititalValue("intersititalSomeValue")
}
enum VideoEnum(val webName: string) {
Showed("videoShowed"),
Dismissed("videoDismissed"),
Failed("videoFailed"),
VideoSomethingHere("videoSomethingHere")
}
My end goal is to use that interface as function parameter, so I can access functionParameter.Showed.webName, etc.
I tried to create an interface but I can not find a way to define Showed, Dismissed or Failed, just functions.
This does not work
interface BaseEnum {
val FailedToShow: String;
}
Edit:
Important, this is not a duplicate of How to extend enums in Kotlin? because I do not want the same key/value pair, I want the same key with different value.
You can’t do this with different enums because there’s no mechanism for relating the names of enum instances of different enums.
Here’s an idea for something that’s similar to the structure you’re looking for.
interface WebNames {
val showed: String
val dismissed: String
val failed: String
}
object InterstitialWebNames: WebNames {
override val showed: String = "interstitialShowed"
override val dismissed: String = "interstitialDismissed"
override val failed: String = "interstitialFailed"
}
object VideoWebNames: WebNames {
override val showed: String = "videoShowed"
override val dismissed: String = "videoDismissed"
override val failed: String = "videoFailed"
}
If you have other properties, you could use a wrapper class instead of Strings for these properties.
I think #TenFour04's example is the closest you're going to get. You can think of an enum as a type, and Showed, Dismissed and Failed as subtypes - but there's no way of enforcing that a particular supertype must have a certain set of subtypes, with specific names.
If you don't just want to deal with String properties (e.g. so you can do something like if (state is Showed) then you could make a type for that:
open class State(val webName: String)
class Showed(webName: String) : State(webName)
class Dismissed(webName: String) : State(webName)
class Failed(webName: String) : State(webName)
interface WebNames {
val showed: Showed
val dismissed: Dismissed
val failed: Failed
}
object VideoWebNames : WebNames {
override val showed = Showed("videoShowed")
override val dismissed = Dismissed("videoDismissed")
override val failed = Failed("videoDismissed")
// a State that's not a standard one included in the interface
val videoSomethingHere = State("videoSomethingHere")
}
if you wanted you could stick all the required states in a sealed class, to group them together and maybe do some checking later
open class State(val webName: String)
sealed class RequiredState(webName: String) : State(webName)
class Showed(webName: String) : RequiredState(webName)
class Dismissed(webName: String) : RequiredState(webName)
class Failed(webName: String) : RequiredState(webName)
So now videoWebNames.showed is a State that also is Showed and is RequiredState
I run into an error which I do not understand after deserializing with gson.
My usecase is way more complex, but I created this test below which shows the behaviour I see.
The issue is the calculated transient value (allObjs) that is null after deserialisation. The unit test fails on the last assert.
Apparently the #Transient prevents correct initialisation of allObjs after deserialisation.
Is this expected behaviour? Can I do anything to make the field work in this way?
If not then I have to convert all the transient fields to functions like getThemAll(), which does work.
I hope for any insights.
Regards, Rob
class CoupleDeserializeTest {
data class Couple( val objA: String, val objB: String){
#Transient
val allObjs: List<String> = listOf(objA, objB)
fun getThemAll() = listOf(objA, objB)
}
#Test
fun testDe_SerializeCouple() {
val couple = Couple("my", "text")
// This succeeds
Assert.assertNotNull(couple.allObjs)
val gson = Gson()
val json = gson.toJson(couple)
// This succeeds
Assert.assertEquals("{\"objA\":\"my\",\"objB\":\"text\"}", json)
val coupleDeserialized = gson.fromJson<Couple>(json, Couple::class.java)
// This succeeds
Assert.assertNotNull(coupleDeserialized.getThemAll())
// This fails
Assert.assertNotNull(coupleDeserialized.allObjs)
}
}
If you are marking a field as Transient, it will ignore the field from serialisation or deserialisation, snippet from JvmFlagAnnotations
/**
* Marks the JVM backing field of the annotated property as `transient`, meaning that it is not
* part of the default serialized form of the object.
*/
#Target(FIELD)
#Retention(AnnotationRetention.SOURCE)
#MustBeDocumented
public actual annotation class Transient
EDIT
It will ignore it in initialisation because, the value of objA and objB will be initialised using serialisation meaning objA and objB have not assigned by the time you allObjs gets initialised, to get the values later instead of directly assigning, you can use get()
data class Couple( val objA: String, val objB: String){
#Transient
val allObjs: List<String>
get() = listOf(objA, objB)
fun getThemAll() = listOf(objA, objB)
}
I have a Java class that holds generic information on databse entities (i.e. their id).
#Data
public class DbEntity {
protected final String id;
public DbEntity(String id) {
this.id = id;
}
}
We use Lombok #Data to generate getters, toString, equals...
In Java I would simply extend this class and add #Data once again.
#Data
class JavaSubClass extends DbEntity {
public JavaSubClass(String id) {
super(id);
}
}
In a newer service we use Kotlin but would like to reuse standard classes such as DbEntity.
My first approach was to simply declare a data class such as
data class SubClass1(val id: String, val name: String) : DbEntity(id)
Accidental override: The following declarations have the same JVM signature (getId()Ljava/lang/String;):
fun <get-id>(): String defined in com.demo.SubClass1
fun getId(): String! defined in com.demo.SubClass1
After some reading I found several solutions, all of which I'm not super happy with.
Don't use data classes. This works but leaves me with the task of implementing equals etc.
class SubClass4(id: String, val name: String) : DbEntity(id)
Duplicate the field. This works but we end up with two fields that could go out of sync.
data class SubClass3(val subId: String, val name: String) : DbEntity(subId)
Assign a different name to the getter. This fundamentally also duplicates the field, but hides the getter.
data class SubClass2(#get:JvmName("getId_") val id: String, val name: String) : DbEntity(id)
As I said, I'm not happy with any of the solution presented above. Having an abstract super class or an interface instead would certainly be more appropriate. However the Entity class resides in a library that primarily Java projects depend on. I'm hesitant to change it just because of a new Kotlin dependnecy.
Did anyone encounter similar issues and has advice on how to solve them?
As a workaround, until KT-6653 - Kotlin properties do not override Java-style getters and setters is fixed, I would go for a variant of your point 3, i.e.:
data class SubClass(#get:JvmName("bogusId") private val id: String, val name: String) : DbEntity(id)
The benefit of this variant is, that you always access the "original" getId-function. You will not use the bogusId()-function as it is not visible/accessible (accessing it via reflection makes no sense... you are only interested in the actual id-field). This works and looks similar for both sides: from Java as also from Kotlin. Still, under the hood this variant uses 2 fields, but in the best case you can just replace it in future with something like:
data class SubClass(override val id: String, val name : String) : DbEntity(id)
I (often) have a resource with two states, pre-created and post-created, where both states have the same fields except for an id field. id is null in the pre-created state and non-null in the post-created state.
I would like to define and use this resource in a clean and type-safe way.
It's common to represent this ID field as a nullable, which handles both scenarios with minimal boilerplate in the class definition. The problem is that it creates a lot of boilerplate in the business logic because you can't assert whether a resource is pre-created or post-created by looking at its type.
Here is an example of the nullable approach:
data class Resource(val id: String?, val property: String)
This is simple to define, but not as simple to handle with due to lack of compile-time guarantees.
Here's an example of a more type-safe approach:
sealed class Resource(val property: String) {
class WithoutID(property: String): Resource(property)
class WithID(val id: String, property: String): Resource(property)
}
This allows me to pass around Resource.WithID and Resource.WithoutID, which have all the same fields and methods, except for id.
One inconvenience with this type-safe approach is that the resource definition code gets quite bloated when you have many property fields. This bloating makes the code harder to read.
I'm wondering if there's an alternative approach with less boilerplate, or if Kotlin has any features that make this kind of thing simpler.
What about defining
sealed class MayHaveId<T> { abstract val record: T }
class WithId<T>(val id: String, override val record: T): MayHaveId<T>()
class WithoutId<T>(override val record: T): MayHaveId<T>()
class Resource(val property: String)
// and other similar types
and using WithId<Resource> and WithoutId<Resource>? In Scala you could add an implicit conversion from MayHaveId<T> to T, but not in Kotlin, alas, nor can you write : T by record. Still should be clean enough to use.
One of the options is to get into composition relying on properties inside interfaces.
interface Resource {
val property: String
}
interface WithId : Resource {
val id: Int
}
interface WithOtherField : Resource {
val otherField: Any
}
class WithoutIdImpl(override val property: String) : Resource
class WithIdImpl(override val id: Int, override val property: String) : WithId
class WithIdAndOtherField(
override val id: Int,
override val otherField: Any,
override val property: String) : WithId, WithOtherField
I didn't get from your example, how you're going to switch between two states of Resource. So probably there is a gap to overcome.
Probably, Smart casts will allow to switch states.