Non null relationships in primary constructor of entity - kotlin

I'm creating application with Spring Data Neo4j and Kotlin. I use standard kotlin way to declare entities (class with primary constructor). Everything worked fine until I wanted to create simple, one-to-many and mandatory relationship between my entities. When I'm calling .findAll() on my repository I get Parameter specified as non-null is null: method ...model.Campaign.<init>, parameter client.
I tried to call .findAll(depth = 1) to load related entities to my entity but that didn't help.
#NodeEntity
class User(var name: String)
{
#Id #GeneratedValue
var id: Long? = null
}
#NodeEntity
class Campaign(
var name: String,
#Relationship(type = "CLIENT", direction = Relationship.OUTGOING)
var client: User)
{
#Id #GeneratedValue
var id: Long? = null
}
interface CampaignRepository : Neo4jRepository<Campaign, Long>
//...
campaignRepository.save(Campaign("C1", user))
campaignRespository.findAll()
Of course, I can just declare var client: User? as nullable and everything is fine. But, since in my model I will have both mandatory and optional relationships I want to know if there's a way to overcome this.

I found a solution, but not very elegant:
#NodeEntity
class Campaign(
var name: String,
client: User?)
{
#Id #GeneratedValue
var id: Long? = null
#Relationship(type = "CLIENT", direction = Relationship.OUTGOING)
lateinit var client: User
init
{
client?.let { this.client = it }
}
}

Related

Re-use mapping code for immutable data class in Kotlin

Updated: added some clarifications from the comments
I would like to use the same 'mapping' code for the primary constructor and copy() method of an immutable data class. How can I do this without creating an empty object first, and then using copy() on it?
The issue with how it is now is that if I add a new attribute with default value to Employee and EmployeeForm it would be easy to only add it in one of the two mapping functions and forget about the other (toEmployeeNotReusable / copyEmployee).
These are the data classes I'd like to map between:
#Entity
data class Employee(
val firstName: String,
val lastName: String,
val jobType: Int,
#OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
private val _absences: MutableSet<Absence> = mutableSetOf(),
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0 // prevents #Joffrey's answer from working
) {
init {
_absences.forEach { it.employee = this }
}
val absences get() = _absences.toSet()
fun addAbsence(newAbsence: Absence) {
newAbsence.employee = this
_absences += newAbsence
}
#Entity
#Table(name = "absence")
data class Absence(
// ... omitted fields
) {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "employee_id")
lateinit var employee: Employee
}
}
data class EmployeeForm(
var firstName: String = "",
var lastName: String = "",
var jobType: Int = 0
) {
// not reusable
fun toEmployeeNotReusable(): Employee {
return Employee(firstName, lastName, jobType)
}
// works but hacky
fun toEmployee(): Employee {
return copyEmployee(Employee("", "", 0))
}
fun copyEmployee(employee: Employee): Employee {
return employee.copy(
firstName = firstName,
lastName = lastName,
jobType = jobType
)
}
}
While mutability would be fine, in my case, I'd be interested to know how this would be possible.
One way to avoid listing the attributes 4 times would be to declare Employee as an interface instead, and use the "mutable" version, the form, as the only data class implementing it. You would have the "read-only" view using the interface, but you would technically only use the mutable instance behind the scenes.
This would follow what Kotlin designers have done for List vs MutableList.
interface Employee {
val firstName: String
val lastName: String
val jobType: Int
}
data class EmployeeForm(
override var firstName: String = "",
override var lastName: String = "",
override var jobType: Int = 0
): Employee {
fun toEmployee(): Employee = this.copy()
fun copyEmployee(employee: Employee): Employee = this.copy(
firstName = firstName,
lastName = lastName,
jobType = jobType
)
}
However, this implies that the form has all fields of an employee, which you probably don't want.
Also, I would personally prefer what you had done in the beginning, listing twice the field would not be a problem, just write tests for your functions, and when you want to add functionality, you'll add tests for that functionality anyway.
You should be able to do this using reflection: check list of properties in Employee and EmployeeForm, call the constructor by the matching names (using callBy to handle default parameters). The drawback, of course, is that you won't get compile-time errors if any properties are missing (but for this case, any test would probably fail and tell you about the problem).
Approximate and untested (don't forget to add the kotlin-reflect dependency):
inline fun <reified T> copy(x: Any): T {
val construct = T::class.primaryConstructor
val props = x::class.memberProperties.associate {
// assumes all properties on x are valid params for the constructor
Pair(construct.findParameterByName(it.name)!!,
it.call(x))
}
return construct.callBy(props)
}
// in EmployeeForm
fun toEmployee() = copy<Employee>(this)
You can make an equivalent which is compile-time checked with Scala macros, but I don't think it's possible in Kotlin.

Deserialize a nested json field with Jackon in Kotlin

I've already deserialized some nested field in the past in Java, following instructions from https://www.baeldung.com/jackson-nested-values (section 5) :
#JsonProperty("brand")
private void unpackNested(Map<String,Object> brand) {
this.brandName = (String)brand.get("name");
Map<String,String> owner = (Map<String,String>)brand.get("owner");
this.ownerName = owner.get("name");
}
ownerName being a field in the bean.
Now, I need to do something similar in Kotlin, but I am not happy with what I have so far. Assuming I have a MyPojo class that has a createdAt field, but in the JSON that represents it, the field is nested under a metadata attribute:
data class MyPojo(var createdAt: LocalDateTime = LocalDateTime.MIN) {
#JsonProperty("metadata")
private fun unpackNested(metadata: Map<String, Any>) {
var createdAtAsString = metadata["createdAt"] as String
this.createdAt = LocalDateTime.parse(createdAtAsString,DateTimeFormatter.ISO_DATE_TIME)
}
}
One of the thing I don't like here is that I am forced to make createdAt a var, not a val.
Is there a Kotlin trick to make things overall better here?
For the sake of simplicity, I used Int as type for createdAt.
You could do it like this:
class JsonData(createdAt: Int = 0) {
private var _createdAt: Int = createdAt
val createdAt: Int
get() = _createdAt
#JsonProperty("metadata")
private fun unpackNested(metadata: Map<String, Any>) {
_createdAt = metadata["createdAt"] as Int
}
}
createdAt will be a parameter with a default value. Since a data classe's constructor can only have properties (var/val) you will loose the advantages of a data class (toString() out of the box etc.).
You will assign this parameter to a private var _createdAt when the class is instantiated.
The only thing that will be exposed to the outside is a property without a backing field createAt (just a getter in Java terms). So, _createdAt cannot be changed after instantiation.
There are two cases now:
If you instantiate the class, _createdAt will be set to the value you specify.
If Jackson instantiates the class the value of _createdAt will be overwritten by the unpackNested call.
Here is an example:
val jsonStr = """{
"metadata": {
"createdAt": 1
}
}
""".trimIndent()
fun main() {
val objectMapper = ObjectMapper()
// Jackson does instantiation
val jsonData = objectMapper.readValue(jsonStr, JsonData::class.java)
// you do it directly
JsonData(5)
}

What is the benefit of using primarykey and references method in class jooq

I'm start the learn jooq. I have mssql server. I create some class the represent table on my server. But I don't understand what is the benefit when I was using getPrimaryKey and getReferences methods in my table class?
class User : TableImpl<Record>("users") {
companion object {
val USER = User()
}
val id: TableField<Record, Int> = createField("id", SQLDataType.INTEGER)
val name: TableField<Record, String> = createField("name", SQLDataType.NVARCHAR(50))
val countryId: TableField<Record, Short> = createField("country_id", SQLDataType.SMALLINT)
override fun getPrimaryKey(): UniqueKey<Record> = Internal.createUniqueKey(this, id)
override fun getReferences(): MutableList<ForeignKey<Record, *>> =
mutableListOf(Internal.createForeignKey(primaryKey, COUNTRY, COUNTRY.id))
}
class Country : TableImpl<Record>("country") {
companion object {
val COUNTRY = Country()
}
val id: TableField<Record, Short> = createField("id", SQLDataType.SMALLINT)
val name: TableField<Record, String> = createField("name", SQLDataType.NVARCHAR(100))
override fun getPrimaryKey(): UniqueKey<Record> =
Internal.createUniqueKey(this, id)
}
The generated meta data is a mix of stuff that's useful...
to you, the API user
to jOOQ, which can reflect on that meta data for a few internal features
For instance, in the case of getPrimaryKey(), that method helps with all sorts of CRUD related operations as you can see in the manual:
https://www.jooq.org/doc/latest/manual/sql-execution/crud-with-updatablerecords/simple-crud
If you're not using the code generator (which would generate all of these methods for you), then there is no need to add them to your classes. You could shorten them to this:
class User : TableImpl<Record>("users") {
companion object {
val USER = User()
}
val id: Field<Int> = createField("id", SQLDataType.INTEGER)
val name: Field<String> = createField("name", SQLDataType.NVARCHAR(50))
val countryId: Field<Short> = createField("country_id", SQLDataType.SMALLINT)
}
However, using the code generator is strongly recommended for a variety of advanced jOOQ features which you might not get, otherwise.

Parcelize and ObjectBox clash

I'm using kotlin and ObjectBox in my application. My object box entity looks something like
#Entity
class Order {
#Id var id: Long = 0
lateinit var customer: ToOne<Customer>
}
#Entity
class Customer {
#Id var id: Long = 0
#Backlink
lateinit var orders: List<Order>
}
But when I use #Parcelize, the properties are being ignored in the parcel. How do I use #Parcelize but still include these properties? I tried overriding writeToParcel but I am not allowed to override it due to #Parcelize.
According to docs, you have to declare all properties in primary constructor, which should be serialized via #Parcelize. All other ones are ignored.
ObjectBox doesn't support ToOne so you have to write custom Parceler. In the end your solution should look like this:
#Entity
#Parcelize
#TypeParceler<ToOne<Customer>, ToOneCustomerParceler>
class Order(
#Id var id: Long = 0,
var customer: ToOne<Customer>
) : Parcelable
#Entity
#Parcelize
class Customer(
#Id var id: Long = 0,
#Backlink var orders: List<Order>
) : Parcelable
object ToOneCustomerParceler : Parceler<ToOne<Customer>> {
override fun create(parcel: Parcel): ToOne<Customer> {
//Somehow recreate ToOne instance
...
}
override fun ToOne<Customer>.write(parcel: Parcel, flags: Int) {
val customer = target
...
}
}
Also don't forget to include correct dependencies:
dependencies {
compile "io.objectbox:objectbox-android:$objectboxVersion"
compile "io.objectbox:objectbox-kotlin:$objectboxVersion"
}
P.S. Use different models for each purpose (#Entity and #Parcelize) even if both are the same. It is much easier to manage them since you separate your intentions into 2 models, rather than trying to push everything into single one.

Call super class constructor in Kotlin, Super is not an expression

I have two classes Entity and Account as
abstract class Entity(
var id: String? = null,
var created: Date? = Date()) {
constructor(entity: Entity?) : this() {
fromEntity(entity)
}
fun fromEntity(entity: Entity?): Entity {
id = entity?.id
created = entity?.created
return this;
}
}
and
data class Account(
var name: String? = null,
var accountFlags: Int? = null
) : Entity() {
constructor(entity: Entity) : this() {
super(entity)
}
}
Which gives me the error
Super is not an expression, it can be only used in the left-hand side of
a dot '.'
Why cannot I do that?
The following will pass the compilation error, but I am not sure if it is correct.
constructor(entity: Entity) : this() {
super.fromEntity(entity)
}
You have a couple of problems in your code.
First, this is the correct syntax, to call a super constructor from a secondary constructor:
constructor(entity: Entity) : super(entity)
Second, you can't call a super constructor from a secondary constructor if your class has a primary constructor (which your class does).
Solution 1
abstract class Entity(
var id: String,
var created: Date
)
class Account(
var name: String,
var accountFlags: Int,
id: String,
created: Date
) : Entity(id, created) {
constructor(account: Account) : this(account.name, account.accountFlags, account.id, account.created)
}
Here, the copy constructor is in the child class which just delegates to the primary constructor.
Solution 2
abstract class Entity(
var id: String,
var created: Date
) {
constructor(entity: Entity) : this(entity.id, entity.created)
}
class Account : Entity {
var name: String
var accountFlags: Int
constructor(name: String, accountFlags: Int, id: String, created: Date) : super(id, created) {
this.name = name
this.accountFlags = accountFlags
}
constructor(account: Account) : super(account) {
this.name = account.name
this.accountFlags = account.accountFlags
}
}
Here I'm only using secondary constructors in the child class which lets me delegate them to individual super constructors. Notice how the code is pretty long.
Solution 3 (most idiomatic)
abstract class Entity {
abstract var id: String
abstract var created: Date
}
data class Account(
var name: String,
var accountFlags: Int,
override var id: String,
override var created: Date
) : Entity()
Here I omitted the copy constructors and made the properties abstract so the child class has all the properties. I also made the child class a data class. If you need to clone the class, you can simply call account.copy().
You can also move your primary constructor down into the class like this:
data class Account: Entity {
constructor(): super()
constructor(var name: String? = null, var accountFlags: Int? = null): super()
constructor(entity: Entity) : super(entity)
}
Advantage of this is, compiler will not require your secondary constructor to call primary constructor.
Another option is to create companion object and provide factory method e.g.
class Account constructor(
var name: String? = null,
var accountFlags: Int? = null,
id: String?,
created: Date?
) : Entity(id, created) {
companion object {
fun fromEntity(entity: Entity): Account {
return Account(null, null, entity.id, entity.created)
}
}
}
Use this super<Entity>.fromEntity(entity) to call super class methods.
As Documentation says:
In Kotlin, implementation inheritance is regulated by the following rule: if a class inherits many implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation (perhaps, using one of the inherited ones). To denote the supertype from which the inherited implementation is taken, we use super qualified by the supertype name in angle brackets, e.g. super.
constructor(entity: Entity) : this() {
super<Entity>.fromEntity(entity)
}
To know more read Overriding Rules