spring boot 3 jpa inheritance not working properly - spring-boot

After upgrading to spring boot 3 from 2.x the JPA inheritance strategy InheritanceType.JOINED is behaving incorrectly.
I have below Entities Book and ComputerBook
Book
#Entity(name = "book")
#Inheritance(strategy = InheritanceType.JOINED)
abstract class Book #JvmOverloads constructor(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
open var id: Long,
open var author: String,
open var publisher: String
)
Computer Book
#Entity(name = "computer_book")
#DiscriminatorValue("computer")
data class ComputerBook #JvmOverloads constructor(
override var id: Long,
override var author: String,
override var publisher: String,
var language: String
): Book(
id,
author,
publisher
)
When I get the ComputerBook entity using BookRepository I get it as follows:
id = 1
author= null
publisher = null
language = "Php"
Book.id = 0
Book.author = "Jhon Doe"
Book.publisher = "Bright publishers"
but when I was using spring boot 2.x the entity I used to get was
id = 1
author= "Jhon Doe"
publisher = "Bright publishers"
language = "Php"
Book.id = 0
Book.author = null
Book.publisher = null
Is this Bug in spring boot 3 or I am missing something?

Related

Spring Data Mongo with Koltin coroutines and #DocumentReference

I am using Spring Data MongoDB with Kotlin Coroutines to create a non-blocking server.
I have a document Deck:
#Document
data class Deck(
#Id val id: ObjectId = ObjectId.get(),
#DocumentReference(lazy = true) val author: User,
val title: String,
val desc: String,
#DocumentReference(lazy = true) val cards : Set<Card> = emptySet(),
#CreatedDate val createdAt: Instant = Instant.now(),
#LastModifiedDate val lastModifiedAt: Instant = Instant.now()
)
Deck has a reference to a User type in the author field:
#Document
data class User(
#Id val id: ObjectId = ObjectId.get(),
val name: String,
#Indexed(unique = true, name = "username_index") val username: String,
val password: String,
#CreatedDate val createdAt: Instant = Instant.now(),
#LastModifiedDate val lastModifiedAt: Instant = Instant.now(),
#DocumentReference(lazy = true) val decks: Set<Deck> = emptySet()
)
Here's my DeckRepository interface:
interface DeckRepository: CoroutineSortingRepository<Deck, String> {
}
The Deck documents are getting saved properly in the database with the author's ObjectId.
But when I try to fetch the deck with id, the repository method gives an error. It's not able to populate the author's reference.
override suspend fun getDeck(deckId: String): DeckResponseDTO {
log.info("Getting deck with id $deckId")
return deckRepository.findById(deckId)?.toDto() ?: throw Exception("Could not find deck with id $deckId")
}
Stacktrace snippets:
Failed to instantiate io.gitub.startswithzed.dex.model.Deck using constructor fun <init>(org.bson.types.ObjectId, io.gitub.startswithzed.dex.model.User, kotlin.String, kotlin.String, kotlin.collections.Set<io.gitub.startswithzed.dex.model.Card>, java.time.Instant, java.time.Instant): io.gitub.startswithzed.dex.model.Deck with arguments 63173407712ec0263535829a,null,Test title,Test Desc,null,2022-09-06T11:50:31.370Z,2022-09-06T11:50:31.370Z,16,null
at org.springframework.data.mapping.model.KotlinClassGeneratingEntityInstantiator$DefaultingKotlinClassInstantiatorAdapter.createInstance(KotlinClassGeneratingEntityInstantiator.java:215) ~[spring-data-commons-2.7.2.jar:2.7.2]
Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method io.gitub.startswithzed.dex.model.Deck.<init>, parameter author
at io.gitub.startswithzed.dex.model.Deck.<init>(Deck.kt) ~[main/:na]
at io.gitub.startswithzed.dex.model.Deck.<init>(Deck.kt:14) ~[main/:na]
I even verified the data by querying using lookup in the console. Somehow Spring Data is not able to query properly.
Thanks for the help!

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

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")

Entity manager changes detached entities

Help me.
Why does the collection in the entity change after the transaction?
My entity:
#Entity
class Entity(
#Id
val uuid: UUID,
#OneToMany(cascade = [CascadeType.PERSIST, CascadeType.REMOVE])
#JoinTable(
name = "entities_items",
joinColumns = [JoinColumn(name = "entity_uuid")],
inverseJoinColumns = [JoinColumn(name = "item_uuid")]
)
val items: MutableList<Item>
)
My test:
#SpringBootTest
internal class EntityTest {
#Autowired
lateinit var entityRepository: EntityRepository
#Autowired
lateinit var transactionManager: PlatformTransactionManager
#Test
fun will_added_item() {
val entityBefore = entityRepository.findById(entityId).get()
// entityBefore.items.size == 0
TransactionTemplate(transactionManager).execute { _ ->
val entity = entityRepository.findById(entityId).get()
entity.items.add(item)
}
// entityBefore.items.size == 1 <-- ???
val entityAfter = entityRepository.findById(entityId).get()
}
}
Interestingly, if I add any call to the collection before the transaction, everything will be fine.
Spring Boot + Hibernate + JUnit
please ignore this wrong answer - can't seem to delete it on my phone!
This is because when you retrieve an item by id within a transaction multiple times, Hibernate will give you same object as it is managing the objects involved in the transaction as a unit of work.
Hence entityBefore and entity are pointing at the same object.
Ah sorry, ignore that - I see first retrieval was outside the tx!

Type mismatch: inferred type is () -> JoinColumn but JoinColumn was expected

We are using Corda 4, Springboot web server and Postgresql 11.
Following are the versions of Corda platform, Springboot server, and other essential dependencies used-
cordaReleaseGroup=net.corda
cordaVersion=4.0
gradlePluginsVersion=4.0.45
kotlinVersion=1.2.71
junitVersion=4.12
quasarVersion=0.7.10
spring_version = '4.3.11.RELEASE'
spring_boot_version = '2.0.2.RELEASE'
spring_boot_gradle_plugin_version = '2.1.1.RELEASE'
jvmTarget = "1.8"
log4jVersion =2.11.2
platformVersion=4
slf4jVersion=1.7.25
nettyVersion=4.1.22.Final
We were able to achieve the sending of single transaction record to a target vault table, from a node to another.
We have come across a requirement in which the transaction is of One-to-many type, for which parent-child tables need to be created in the vault.
Following is the code to create the schema for the parent-child tables but it throws error on compilation -
"Type mismatch: inferred type is () -> JoinColumn but JoinColumn was expected".
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
import java.util.UUID
object Schema1
object SchemaV1 : MappedSchema(
schemaFamily = Schema1.javaClass,
version = 1,
mappedTypes = listOf(PersistentEmployees::class.java,PersistentEmployeeVehicles::class.java))
{
#Entity
#Table(name = "TBL_EMPLOYEES")
class PersistentEmployees(
#Column(name = "EmployeeId")
var Pid: Long,
#Column(name = "EmployeeName")
var EmployeeName: String,
#Column(name = "EmployeeAddress")
var EmployeeAddress: String,
#OneToMany(cascade = [(CascadeType.PERSIST)])
#JoinColumns({
JoinColumn(name = "output_index", referencedColumnName = "output_index");
JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id") })
private val EmpVehicles:List<PersistentEmployeeVehicles>
) : PersistentState(), Serializable
#Entity
#Table(name = "TBL_EMPLOYEE_VEHICLES")
class PersistentEmployeeVehicles(
#Column(name = "ID")
var ID: UUID,
#Column(name = "VEHICLETYPE")
var VEHICLETYPE: String,
#Column(name = "VEHICLEMODEL")
var VEHICLEMODEL: String,
#Column(name = "VEHICLENUMBER")
var VEHICLENUMBER: String
)
}
Question 1: What would be the cause of the error and also the solution (if possible)?
We used "Car insurance" "One-to-many" mapping sample from Corda Git Hub. Following are the links-
"https://github.com/corda/samples/blob/release-V4/carinsurance-QueryableState/contracts/src/main/java/net/corda/examples/carinsurance/schema/PersistentInsurance.java"
"https://github.com/corda/samples/blob/release-V4/carinsurance-QueryableState/contracts/src/main/java/net/corda/examples/carinsurance/schema/PersistentClaim.java"
The syntax for declaring arrays within annotations is different between Java and Kotlin, for Kotlin, you should use [] like the following:
#JoinColumns(value = [
JoinColumn(name = "output_index", referencedColumnName = "output_index"),
JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id") ])
private val EmpVehicles:List<PersistentEmployeeVehicles>

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.

Resources