Spring Data JDBC Kotlin NoSuchMethod error: Dialect.getIdGeneration() - spring

I'm writing a very simple Spring Data JDBC repository in Kotlin (using Postgres as the database):
data class Label(
#Id
#GeneratedValue
#Column( columnDefinition = "uuid", updatable = false )
val id: UUID,
val name: String
)
#Repository
interface LabelRepository: CrudRepository<Label, UUID> {}
When I do repository save:
val l = Label(id = UUID.randomUUID(), name = "name")
labelRepo.save(l)
It works fine. But since id is not null Spring Data JDBC will always treat it as an "update" to an existing label entity instead of creating a new one with generated ID.
So I changed id: UUID to id: UUID? And having val l = Label(id = null, name = "name")
But call the same save() method gives me:
java.lang.NoSuchMethodError: 'org.springframework.data.relational.core.dialect.IdGeneration org.springframework.data.relational.core.dialect.Dialect.getIdGeneration()'
I have tried a solution here: https://jivimberg.io/blog/2018/11/05/using-uuid-on-spring-data-jpa-entities/
But it didn't work, still gives me the same error
Wondering what's the cause of this and why this error pops up only when I change UUID to UUID??

nvm, turns out I have to use the implementation("org.springframework.boot:spring-boot-starter-data-jdbc") dependency instead of implementation("org.springframework.data:spring-boot-starter-data-jdbc:2.1.3")

Related

Postgresql Spring Data #GeneratedValue without #Id return null value

im working in spring boot project i want to map a property with serial (using my sequence) column in my table and this column is not the ID.
i found this solution :
#Column(name = "DEMANDE_NUMBER", insertable = false, updatable = false, columnDefinition = "serial")
private Integer demandeNumber;
(because #GeneratedValue persist null and doesn't use the sequence)
this solution works fine and the field is persisted in my DB and the value uses the sequence but when i get my object after saving using my repository the demandeNumber is null
Demande savedDemande= demandeRepository.save(demandeToSave);
//demandeObj .getDemandeNumber() return null
any suggestion to resolve this issue please ?
Thanks.
according to this answer How to use a sequence generator for a non ID field?
i added the following annotation on my property
#Generated(GenerationTime.INSERT)
#Column(name = "column_name", columnDefinition = "serial", updatable = false)
you should import the package from hibernate and not javax.persistence.
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
i hope this can help other people in the future. note this solution is for spring data with postgresql.

Hibernate detached entity passed to persist error in Spring Boot in Kotlin using OneToOne with MapsId

I have the following entities in a fairly simple and straightforward Spring Boot application in Kotlin:
#Entity
class Target(
#Id #GeneratedValue var id: Long? = null,
// ... other stuff
)
#Entity
class Ruleset(
#OneToOne(fetch = FetchType.LAZY) #MapsId
var target: Target,
#Id #GeneratedValue var id: Long? = null,
// ... other stuff
)
And I have the following code to create them upon startup of a #Component:
#PostConstruct
#Transactional
fun init() {
val target = Target()
targetRepository.save(target)
val rule = Ruleset(target)
rulesetRepository.save(rule)
}
And when this runs I get the "detached entity passed to persist: com.mystuff.Target" error. I've used this approach in the past (see here: https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/) without issue, although never in trying to create them at the same time in the same method. I've also tried using the entity passed back by the .save() call on the Target repository in the persist of the Ruleset object with no success.
I am able to fix this if I go back to the "normal" way of doing a OneToOne relationship:
#Entity
class Target(
#OneToOne(mappedBy = "target", cascade = [CascadeType.ALL],
fetch = FetchType.LAZY, optional = false)
var ruleset: Ruleset?
#Id #GeneratedValue var id: Long? = null
)
#Entity
class Ruleset(
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "target_id")
var target: Target,
#Id #GeneratedValue var id: Long? = null,
)
But this is annoying as it forces me to pass a null into the Target constructor and then update it immediately after creating the Ruleset. I can't figure out why the other, simpler approach doesn't work.

Spring Data JDBC - Kotlin support - Required property not found for class

I'm trying to use Spring Data JDBC with Kotlin data-classes, and after adding #Transient property to primary constructor I received error on simple findById call:
java.lang.IllegalStateException: Required property transient not found for class mitasov.test_spring_data_with_kotlin.Entity!
my entity class looks like below:
data class Entity(
#Id
var id: String,
var entityName: String,
#Transient
var transient: List<TransientEntity>? = mutableListOf(),
)
After reading that issue I've tried to make #PersistenseConstructor without #Transient field:
data class Entity(
#Id
var id: String,
var entityName: String,
#Transient
var transient: List<TransientEntity>? = mutableListOf(),
) {
#PersistenceConstructor
constructor(
id: String,
entityName: String,
) : this(id, entityName, mutableListOf())
}
But this didn't help me and I'm still getting that error.
How can I solve this problem?
It's turned out that my second attempt IS the solution.
The trick was in my Run/Debug configuration for tests.
In IDEA Preferences I have checked Preferences | Build, Execution, Deployment | Build Tools | Maven | Runner — Delegate IDE build/run actions to Maven checkbox, and this means that I need to manually recompile my project before running tests.
Solution
So, this is it, the solution for error
java.lang.IllegalStateException: Required property transient not found for class mitasov.test_spring_data_with_kotlin.Entity!
is making #PersistenseConstructor without #Transient fields:
data class Entity(
#Id
var id: String,
var entityName: String,
#Transient
var transient: List<TransientEntity>? = mutableListOf(),
) {
#PersistenceConstructor
constructor(
id: String,
entityName: String,
) : this(id, entityName, mutableListOf())
}

Neo4jRepository saves empty node from Kotlin data class

I have a Kotlin data class node for Neo4j nodes:
#NodeEntity
data class MyNode (
#Id #GeneratedValue var dbId: Long? = null,
#Index(unique = true) val name: String,
val description: String
)
and a Spring repository:
interface MyNodesRepository : Neo4jRepository<MyNode, Long>
Then, when I save a node into the DB via this repository it is empty, without any properties:
val node = MyNode(null, "name 1", "lorem ipsum")
myNodesRepository.save(node)
after the save(node) call, the node.dbId is set to the Neo4j's internal id i.e. it is null before save() and has a value afterwards. I can also see the node in the Neo4j browser, but it does not have name and description properties.
After that, when I try to load all nodes the call crashes with InvalidDataAccessApiUsageException because it cannot deserialize/map the nodes with null/missing name and description:
val allNodes = myNodesRepository.findAll()
If I add a custom save method to my repository, where I manually create the node with CQL query, then everything works.
interface MyNodesRepository : Neo4jRepository<MyNode, Long> {
#Query(
"MERGE (mn:MyNode {name:{name}})\n" +
"ON CREATE SET m += {description:{description}}"
)
fun customSave(#Param("name") name: String, #Param("description") description: String)
}
Now the findAll() loads my newly created and/or updated nodes.
I am using org.springframework.boot:spring-boot-starter-data-neo4j:2.1.6.RELEASE and this is inside a Spring Boot CLI application so no web server and RestControllers.
What am I doing wrong?
EDIT: This is now solved in Neo4j OGM 3.1.13
EDIT: This is now solved in Neo4j OGM 3.1.13 and the workaround below is not needed anymore.
ORIGINAL ANSWER
After few days of debugging it looks like there is a bug in Neo4j OGM for Kotlin data classes where it does not save val properties -- both in data classes and normal classes. So change from val properties:
#NodeEntity
data class MyNode (
#Id #GeneratedValue var dbId: Long? = null,
#Index(unique = true) val name: String,
val description: String
)
to all var properties:
#NodeEntity
data class MyNode (
#Id #GeneratedValue var dbId: Long? = null,
#Index(unique = true) var name: String,
var description: String
)
works. Now both saving and loading works, but it is not idiomatic Kotlin.
So at least we have a workaround.

Spring Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

I can't understand, what's wrong with my Service. I receive org.hibernate.StaleObjectStateException trying to run this method:
fun updateNameForPhone(phone: String, name: String): Client {
val res = clientRepository.findByPhone(phone) ?: throw ClientNotFoundException(phone)
res.name = name
return clientRepository.save(res)
}
ClientRepository:
#Repository
interface ClientRepository : JpaRepository<Client, UUID> {
fun findByPhone(phone: String): Client?
}
Client entity:
#Entity
data class Client(
var name: String = "",
var phone: String = "",
#Id #GeneratedValue(strategy = GenerationType.AUTO)
val uuid: UUID = defaultUuid()
)
Exception:
Object of class [com.app.modules.client.domain.Client] with identifier
[12647903-7773-4f07-87a8-e9f86e99aab3]: optimistic locking failed;
nested exception is org.hibernate.StaleObjectStateException: Row was
updated or deleted by another transaction (or unsaved-value mapping
was incorrect) :
[com.app.modules.client.domain.Client#12647903-7773-4f07-87a8-e9f86e99aab3]"
What is the reason?
I'm using Kotlin 1.3.11, Spring Boot 2.1.1, MySql. I don't run it in different threads, just trying with single request.
Well, finally I've found a solution. Better say workaround.
The problem is in the way spring uses UUID as entity identifier.
So there are two workarounds, solving this issue:
first, you can change your id field type to other one, such as Long, for example, if it's possible to you;
or you can add this annotation to your uuid field: #Column(columnDefinition = "BINARY(16)").
The last solution I've found from this question.

Resources