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