Removing relationship between two nodes in Spring Boot 2.4.3 Neo4j - spring

I'm in the middle of upgrading from Spring Boot 2.3 to 2.4 and are having some issues with Neo4j after upgrade.
I'm creating two nodes, and adding a relationship between them
#Node
class Document(
#Id
val id: String,
) {
#Relationship(type = "CONCERNS")
var concerns: Concerns? = null
}
#RelationshipProperties
class Concerns(
#TargetNode val user: User
) {
#Id
#GeneratedValue
private var id: Long? = null
}
#Node
class User(
#Id
val id: String
) {
var givenName: String? = null
var familyName: String? = null
}
Creating the Nodes and adding the relationship works fine
But when I try to remove the relationship from the user, nothing happens:
val document = documentRepository.findById(id)
document.concerns = null
documentRepository.save(document)

Related

Unable to insert entity using room

I am new to android Room and trying to insert some one-to-many relationships into my database. But I am facing some issues that I have not managed to fix sofar.
Here is my data constellation:
Entity:
#Entity(tableName = "artist")
data class Artist(
#PrimaryKey(autoGenerate = true) val id: Long = 0,
val name: String,
) {
}
#Entity(
tableName = "song",
foreignKeys = [ForeignKey(
entity = Artist::class,
parentColumns = ["id"],
childColumns = ["artistId"],
onDelete = ForeignKey.CASCADE
)]
)
data class Song(
#PrimaryKey(autoGenerate = true) val id: Long = 0,
val artistId: Long,
val title: String?
) {
}
data class ArtistWithSongs(
#Embedded val artist: Artist,
#Relation(parentColumn = "id", entityColumn = "artistId", ) val songs: List<Meal>
) {
}
Repository:
#Singleton
class AppRepository #Inject constructor(
private val artistDao: ArtistDao
) {
suspend fun insert(artist: Artist) {
artistDao.insert(artist)
}
}
Dao:
#Dao
interface ArtistDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(artist: Artist)
}
ViewModel:
#HiltViewModel
class ArtistViewModel #Inject constructor(
private val repository: AppRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
fun insert(artist: Artist) = viewModelScope.launch {
repository.insert(artist)
}
}
Then in my activity I call:
private val viewModel: ArtistViewModel by viewModels()
...
val artist = Artist("Bob")
viewModel.insert(artist)
But insert artist does not work. The database is still empty.
Thanks
I was using viewModel.artist.value to check if any artist was present. As the method returning artist has LiveData as return type, I was getting null.
Observing the artist instead proved that my insert method is working just right.

Spring boot UUID primary key entity doesn't show right id after created

So I have entity with UUID as primary key. After I create the entity in my service, the response return wrong id.
My Role entity
package denny.study.stock.entity
import denny.study.stock.util.converter.JsonToMapConverter
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.GenericGenerator
import org.hibernate.annotations.Type
import org.hibernate.annotations.UpdateTimestamp
import java.util.*
import javax.persistence.*
#Entity
#Table(name = "roles")
class Role {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "id", columnDefinition = "CHAR(36)")
#Type(type = "uuid-char")
var id: UUID = UUID.randomUUID()
#Column
var name: String = ""
#Column
var slug: String = ""
#Column
#Convert(converter = JsonToMapConverter::class)
var permissions: MutableMap<String, Boolean>? = null
#Column(name = "created_at")
#CreationTimestamp
var createdAt: Date = Date()
#Column(name = "updated_at")
#UpdateTimestamp
var updatedAt: Date? = null
}
My store method in role service
override fun store(roleRequest: RoleRequest): RoleResponse {
val role = Role().apply {
name = roleRequest.name
slug = roleRequest.slug
permissions = roleRequest.permissions
}
roleRepository.save(role)
return RoleResponse(role)
}
My Role response
package denny.study.stock.model.response.role
import denny.study.stock.entity.Role
class RoleResponse(role: Role) {
var id = role.id
var name = role.name
var slug = role.slug
var permissions = role.permissions
}
the json response return id "f95bddf6-eb22-49bb-b8e6-5eb819603fa9"
{
"code": 200,
"status": "OK",
"data": {
"id": "f95bddf6-eb22-49bb-b8e6-5eb819603fa9",
"name": "Role 1",
"slug": "role-1",
"permissions": {
"asd": true,
"dsa": false
}
}
}
while in DB it stored as "87596692-7ee9-4ecb-a425-3b1372d901f4". Do you know why It return the wrong id? Thank you!
You are using #GeneratedValue annotation and also assigning UUID.randomUUID() to the id attribute. Either use one or the other, not both.
If you want the ID to be generated by the persistence provider then keep #GeneratedValue and remove #GenericGenerator (which is a Hibernate annotation) and UUID.randomUUID(). If you want to do that on your own then remove #GeneratedValue and #GenericGenerator.

Constructor takes only ID of referenced entity, but getter returns the entity itself - possible?

Let's assume, I've a simple relationship of Student and Teacher Entities
I'm using Spring Boot with kotlin-jpa plugin, so data classes should work fine:
data class Student(
#Id
#GeneratedValue(strategy = IDENTITY)
val id: Long = null,
#OneToOne(fetch = FetchType.LAZY)
var responsibleTeacher: Teacher,
// ... other props
)
data class Teacher(
#Id
#GeneratedValue(strategy = IDENTITY)
val id: Long = null,
val name: String,
// ... other props
)
My problem: To construct an instance of Student, I always need an instance of the (already persisted) Teacher as well. As I only have the ID of the teacher at hand, I first need to obtain the full Teacher entity from the database and then pass it to the constructor of Student:
val responsibleTeacher = getTeacherFromDB(teacherId)
val student = Student(responsibleTeacher)
What I would like to to, is to pass only the Teacher ID in the constructor, but still being able to query the full Teacher entity from Student when calling the getter/property.
data class Student(
#Id
#GeneratedValue(strategy = IDENTITY)
val id: Long = null,
#Column(name = "responsible_teacher_id")
private var responsibleTeacherId: Long,
// ... other props
// pseudo-code, doesn't work!
// Should query the [Teacher] Entity by [responsibleTeacherId], specified in constructor
#OneToOne(fetch = LAZY)
var responsibleTeacher:Teacher
)
I was messing around with this for almost a day but couldn't find any working solution. Is there any?
For this purpose you can use a proxy that you can retrieve by calling entityManager.getReference(Teacher.class, teacherId)
Use #JoinColumn annotation
#Column(name = "responsible_teacher_id")
private var responsibleTeacherId: Long,
#JoinColumn(name = "responsible_teacher_id")
#OneToOne(fetch = LAZY, insertable = false, updatable = false)
var responsibleTeacher: Teacher

Spring Hibernate - #Transactional between different transactions

I'm creating a test and basically doing different transactions inside a #Transactional method.
I add a Project, then add a Task to it, and last will fetch the project again from DB to test it has the task saved.
Please note the case I'm showing is a unit test but I'm more interesting in fixing the transactional methods and not the test itself as I already had this in the past in "production code".
Model Classes:
#Entity
#Table(name = "Task")
data class Task(
#Id
#SequenceGenerator(name = "TaskSeq", sequenceName = "TaskSeq", initialValue = 100)
#GeneratedValue(generator = "TaskSeq")
val id: Long = 0,
#Column(nullable = false)
val name: String,
val description: String,
val inZ: LocalDateTime = LocalDateTime.now(),
var outZ: LocalDateTime = JpaConstants.MAX_DATETIME,
var completed: Boolean = false,
#ManyToOne(cascade = [CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH])
#JoinColumn(name = "projectId")
var project: Project? = null
) {
}
#Entity
#Table(name = "Project")
data class Project(
#Id
#SequenceGenerator(name = "ProjectSeq", sequenceName = "ProjectSeq", initialValue = 100)
#GeneratedValue(generator = "ProjectSeq")
val id: Long = 0,
#Column(nullable = false)
var name: String,
#OneToMany(mappedBy = "project", cascade = [CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH])
val tasks: MutableList<Task> = Lists.mutable.empty()
) {
}
Service Classes:
#Service
class ProjectServiceImpl(private val projectRepository: ProjectRepository) : ProjectService {
override fun save(project: Project): Project {
return projectRepository.save(project)
}
}
#Service
class TaskServiceImpl(private val taskRepository: TaskRepository, private val projectRepository: ProjectRepository) : TaskService {
override fun save(task: Task): Task {
return taskRepository.save(task)
}
override fun addTaskToProject(projectId: Long, task: Task): Task {
val project = projectRepository.findById(projectId).orElseThrow { RecordNotFoundException("Couldn't find project with id {$projectId}") }
task.project = project
return save(task)
}
}
The class I'm trying to use the transactional method:
class TaskServiceImplTest : TaskApplicationTests() {
#Autowired
private lateinit var taskService: TaskService
#Autowired
private lateinit var taskRepository: TaskRepository
#Autowired
private lateinit var projectService: ProjectService
#Test
#Transactional
fun canInsertTaskToProject() {
val project = projectService.save(Project(name = "Conquer Paris"))
var task = Task(name = "Check how many people we need to hire", description = "")
task = taskService.addTaskToProject(project.id, task)
assertTrue(task.id > 0)
val projects = projectService.findAll()
assertEquals(1, projects.size())
assertEquals(1, projects[0].tasks.size)
assertEquals(task.id, projects[0].tasks[0].id)
}
If I add a #Transactional(REQUIRES_NEW) to the methods in the service it will work, but I don't want it as if this method is called inside a real transaction I want it to be rolled back accordingly. Also I'd like to avoid using too many REQUIRES_NEW to avoid future problems
If I remove the #Transactional from the test method, it won't work when I test the size of the task list on last two lines as they are lazy.
What is the best way to make it work ? I thought that inside a #Transactional when I used another command from db it would get the latest updates that were not committed yet..
If needed, code in Java is fine too :)
Thanks in advance!
Based on your scenarios, you can use #TestEntityManagerso that each test can be managed in transaction context.
This example can help you,
https://grokonez.com/testing/datajpatest-with-spring-boot

#CreationTimestamp and #UpdateTimestamp don't work in Kotlin

This is my Tag and Post Entity classes:
#Entity
class Tag(
#get:NotBlank
#Column(unique = true)
val name: String = "",
val description: String = ""
) {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
val id: Int? = null
#ManyToMany(mappedBy = "tags")
val posts: MutableSet<Post> = mutableSetOf()
#CreationTimestamp
lateinit var createDate: Date
#UpdateTimestamp
lateinit var updateDate: Date
fun addPost(post: Post) {
this.posts.add(post)
post.tags.add(this)
}
}
#Entity
class Post(
#get:NotBlank
var name: String = "",
val content: String = ""
) {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
val id: Int? = null
#ManyToMany
#Cascade(CascadeType.ALL)
val tags: MutableSet<Tag> = mutableSetOf()
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
lateinit var createDate: Date
#UpdateTimestamp
#Temporal(TemporalType.TIMESTAMP)
lateinit var updateDate: Date
fun addTag(tag: Tag) {
this.tags.add(tag)
tag.posts.add(this)
}
}
The query:
val post1 = Post( "Post 1", "Post 1 content");
val post2 = Post( "Post 2", "Post 2 content");
var tag = Tag( "news", "Tag description")
post1.addTag(tag)
post2.addTag(tag)
em.persist(post1)
em.persist(post2)
em.remove(post1)
em.flush()
But then, the createDate and updateDate return null (both tag and post):
I converted this code to Java and it works fine
Kotlin version: 1.2-M2
springBootVersion: '2.0.0.M7'
The problem likely exists in the fact that those annotations are not limited to what they should annotate. This means kotlin does not know exactly where to put them in the final bytecode.
To get the same bytecode as would be generated by java, you need to specify the annotation target of the annotation in question:
#field:CreationTimestamp
lateinit var createDate: Date
The context is not enough to provide any meaningful answer.
You have to give more context, in particular the application server, with its version, or even code for this specific use-case. To start with, I'd suggest first checking if the same Java code works.
My best guess so far, you're using Spring Data JPA while #CreationTimestamp and #UpdateTimestamp are Hibernate specific.

Resources