Analogue of #Formula annotation from JPA for Spring data neo4j? - cypher

I'd like to calculate some properties of my domain objects at DB level using neo4j and return the read-only results. In JPA one can achieve this via #Formula annotation over field of domain object entity:
#Formula("(select avg(f.rating) from Feedback f where f.offer_id = offer_id)")
private Double rating;
What should one do to achieve the same behavior in Spring data neo4j? I wrote a Cypher query, but don't know where to use it.

A similar outcome can be achieved using #QueryResult
Create a class with fields to hold return data.
Annotate it with #QueryResult
Example: (in Kotlin, which is what I had on hand)
#QueryResult
open class Principal constructor(applicationToken: String,
profileId: String,
stageName: String,
showMeLaterDays: Float,
roles: Array<Role>)
{
var applicationToken: String
var profileId: String
var stageName: String
var showMeLaterDays: Float
#Convert(RoleArrayAttributeConverter::class)
var roles: Array<Role>
init
{
this.applicationToken = applicationToken
this.profileId = profileId
this.stageName = stageName
this.showMeLaterDays = showMeLaterDays
this.roles = roles
}
//Provide a default constructor for OGM
constructor() : this(applicationToken = "", profileId = "", stageName = "", showMeLaterDays = 0f,
roles = emptyArray())
}
Then use it with a repository as follows:
#Query("MATCH (n:CandidateProfile {applicationToken: {0} })
RETURN n.id as profileId, n.applicationToken as applicationToken, n.stageName as stageName, n.showMeLaterDays as showMeLaterDays, n.roles as roles;")
fun findByApplicationToken(token: String): Principal?
Note the way that node properties are returned to correspond with the class field names.
The same can be done with function results.

Related

Values loss for copy() for Kotlin data class

I have such data class:
data class BookObject(
val description: String?,
val owningCompanyData: OwningCompanyData?,
) {
var id: String? = null
var createdAt: Instant? = null
var createdBy: String? = null
var modifiedAt: Instant? = null
fun update(command: CreateOrUpdateBookObjectCommand): BookObject =
this.copy(
description = command.description,
owningCompanyData = command.owningCompanyData
)
}
When I use the update function for an object with completely filled fields, I get an object with empty id, createdAt, createdBy, modifiedAt fields (they become equal to null). But why is this happening? Why do these fields lose their values?
The kotlin documentation says:
Use the copy() function to copy an object, allowing you to alter some
of its properties while keeping the rest unchanged.
The answer actually is present in your link, located in the paragraph just before "Copying".
The compiler only uses the properties defined inside the primary constructor for the automatically generated functions.

Returning one of multiple class types from a data class

I have scenario where in I have a "Lookup Table" class that holds getters and setters for multiple class types. As I'm interfacing with a database, I need to provide a way to provide a result boolean and then the class instance which was requested.
For example. Say I have an AssetStatus, StockItemStatus and NominalCode class. I would have to write the following data class for each:
data class LookupResult(
val wasSuccessful: Boolean = false,
val resultClass: AssetStatus? = null,
)
// or
data class LookupResult(
val wasSuccessful: Boolean = false,
val resultClass: StockItemStatus? = null,
)
// or
data class LookupResult(
val wasSuccessful: Boolean = false,
val resultClass: NominalCode? = null,
)
Ideally I don't want to have to repeat myself so I was wondering if I could write one data class (or normal class?) which is able to return one of my multiple Lookup classes?
Initially I thought it would have needed to be Any but after looking into this, that might not be the best case for what I want.
So is there a way of sticking to writing once but not repeating? Or is it something I have to do?
Edit:-
All of my class types would have the following structure (note: I'm using Spring Boot as back end) :
#Entity
#Table(name = "lookup_asset_status")
data class AssetStatus(
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "asset_status_id")
val id: Long? = null,
#Column(name = "asset_status_name")
val name: String = "",
)
...
// Repeat for each entity type
...
If I understand correctly, you want something like this:
data class LookupResult<T>(
val wasSuccessful: Boolean = false,
val resultClass: T? = null,
)
Each of the classes would be written as LookupResult<AssetStatus>, LookupResult<StockItemStatus> and LookupResult<NominalCode>.
If your method needs to be able to return any of those three classes, then it should be declared to return LookupResult<*>. Note that you can only access the members of Any when you access the resultClass of a LookupResult<*>, because you don't know which exact look up result it is.

Jackson SNAKE_CASE How to generate underscore in field names before number

I have the next peace of code
#Test
fun `simple test`() {
val objectMapper = ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
.registerModule(KotlinModule())
val value = objectMapper.writeValueAsString(MyClass(myField1 = "something", myField2 = "something2"))
assertNotNull(value)
}
data class MyClass (
val myField1: String? = null,
#JsonProperty("my_field_2")
val myField2: String? = null,
)
the result of deserialization is next
{"my_field1":"something","my_field_2":"something2"}
Is it possible to configure objectMapper to automatically populate _ value, before digits in object property names, without specifying it in #JsonProperty?
Yes, this is possible using a PropertyNamingStrategy:
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
Note that you named your snake-case fields inconsistently, because there is my_field1 without a _ before the digit, and my_field_2 with a _ before the digit. The configuration above using PropertyNamingStrategies.SNAKE_CASE works fine for the first naming (like in my_field1).
If you want to use the second naming (like in my_field_2), then you would have to write your own naming strategy like this:
class MySnakeCaseStrategy : NamingBase() {
override fun translate(input: String?): String? =
if (input == null) null
else "([A-Z]+|[0-9]+)".toRegex().replace(input) { "_${it.groupValues[1]}".lowercase() }
}
That naming strategy can then be used to configure your object-mapper:
objectMapper.setPropertyNamingStrategy(MySnakeCaseStrategy())
I do not know if and how it would be possible to support both naming strategies at the same time.

InvalidDataAccessApiUsageException - Parameter value did not match expected type

I need to get all users with a specific role from a database. For this I need to use JPA.
All roles are stored in a special set:
UserAccount.kt
#ManyToMany(cascade = [(CascadeType.MERGE)])
#JoinTable(
name = "user_authorities",
joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")],
inverseJoinColumns = [JoinColumn(name = "authority_id", referencedColumnName = "id")]
)
var authoritySet: MutableSet<Authority> = hashSetOf()
I want to do it like this:
UserAccountService.kt
override fun getAllUsersByAuthorityName(name: String): List<UserAccountDto> {
return userAccountRepository.findUsersByAuthoritySet(mutableSetOf(authorityService.findAuthorityByAuthorityName(name))).map { it.toDto() }
}
UserAccountRepository.kt
#Query("select u from UserAccount u where u.authoritySet = ?1")
fun findUserAccountByAuthoritySet(authoritySet: MutableSet<Authority>): List<UserAccount>
But when calling these methods in tests, it gives an error:
Parameter value [Authority(id=1, authority='ROLE_ADMIN')] did not match expected type [java.util.Set (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [Authority(id=1, authority='ROLE_ADMIN')] did not match expected type [java.util.Set (n/a)]
Tell me how you can properly organize the search for users?
Can you show sites with examples of such requests?
First, I started using the JPA functionality and added the "In" particle to the method name in the repository.
In the second, I removed the #Query annotation
Third, I revisited my tests, and it turned out that I added roles after the saved users, and therefore my changes were not committed to the database. I fixed it.
Problem solved

Kotlin short-cut to assign value to variable using stream function or other

for (i in 0 until result.size){ result[i].config= addConfig(taskNames!![i],processKeys!![i]) }
Here result is a list of class which has datamember config and tasNames and processKeys are list of string.
Is there a way in kotlin to map result.config with respective taskNames and processKeys without using traditional loop and mentioning length of result.I am new to kotlin.
class Process {
var processKey: String? = null
var task: List<Task>? = null}
class Task {
var taskName: String? = null
var processVariables: List<ProcessVariable>? = null}
class ProcessVariable {
var name: String? = null
var label: String? = null
var applicableValue: List<String>? = null}
Result is already present with datamember config pf type ProcessVariable
If I understand your problem correctly, you need to combine 3 lists.
So iterating over the lists may be easier to understand than some clever way of list transformations.
You can get rid of the traditional for loop, so you don't need to calculate the size of the loop:
result.forEachIndexed {
i, resultData -> resultData.config = addConfig(taskNames[i], processKeys[i])
}
If you want to combine two lists, you can use the zip method:
val configList = taskNames.zip(processKeys) {tsk, prc -> addConfig(tsk, prc)}
In your example, the result-object was already existing. Maybe it is easier to create new result-objects:
val results = configList.map {
Result(config = it)
}