Spring boot "Operation is not supported for read-only collection" when updating a record in database - spring-boot

I am fairly new to Spring Boot and unable to find out why my CRUD repository seems to keep throwing an exception saying "Operation is not supported for read-only collection" when sending a request to a Put endpoint. All other repositories seem to work just fine. Here's my code:
User.kt
package com.karbal.tutortek.entities
import com.karbal.tutortek.dto.userDTO.UserPostDTO
import java.sql.Date
import javax.persistence.*
#Entity
#Table(name = "users")
data class User(
#Id
#Column(name = "id", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_generator")
#SequenceGenerator(name = "user_generator", sequenceName = "user_seq", allocationSize = 1)
var id: Long? = null,
#Column(name = "firstName", nullable = false)
var firstName: String = "",
#Column(name = "lastName", nullable = false)
var lastName: String = "",
#Column(name = "birthDate", nullable = false)
var birthDate: Date = Date(System.currentTimeMillis()),
#Column(name = "rating", nullable = false)
var rating: Float = 0.0F,
#OneToMany(mappedBy = "user")
var payments: List<Payment> = listOf(),
#OneToMany(mappedBy = "user")
var topics: List<Topic> = listOf()
){
constructor(userPostDTO: UserPostDTO) : this(
null,
userPostDTO.firstName,
userPostDTO.lastName,
userPostDTO.birthDate,
userPostDTO.rating
)
fun copy(user: User){
firstName = user.firstName
lastName = user.lastName
birthDate = user.birthDate
rating = user.rating
payments = user.payments
topics = user.topics
}
}
UserController.kt
package com.karbal.tutortek.controllers
import com.karbal.tutortek.dto.userDTO.UserGetDTO
import com.karbal.tutortek.dto.userDTO.UserPostDTO
import com.karbal.tutortek.entities.User
import com.karbal.tutortek.services.UserService
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException
import java.util.*
#RestController
class UserController(val userService: UserService) {
#PostMapping("/users/add")
fun addUser(#RequestBody userDTO: UserPostDTO): UserGetDTO {
val user = User(userDTO)
return UserGetDTO(userService.saveUser(user))
}
#DeleteMapping("/users/{id}")
fun deleteUser(#PathVariable id: Long){
val user = userService.getUser(id)
if(user.isEmpty) throw ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")
userService.deleteUser(id)
}
#GetMapping("/users/all")
fun getAllUsers() = userService.getAllUsers().map { u -> UserGetDTO(u) }
#GetMapping("/users/{id}")
fun getUser(#PathVariable id: Long): UserGetDTO {
val user = userService.getUser(id)
if(user.isEmpty) throw ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")
return UserGetDTO(user.get())
}
#PutMapping("/users/{id}")
fun updateUser(#PathVariable id: Long, #RequestBody userDTO: UserPostDTO){
val user = User(userDTO)
val userInDatabase = userService.getUser(id)
if(userInDatabase.isEmpty) throw ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")
val extractedUser = userInDatabase.get()
extractedUser.copy(user)
userService.saveUser(extractedUser)
}
}
UserService.kt
package com.karbal.tutortek.services
import com.karbal.tutortek.entities.User
import org.springframework.stereotype.Service
import com.karbal.tutortek.repositories.UserRepository
#Service
class UserService(val database: UserRepository) {
fun getAllUsers(): List<User> = database.getAllUsers()
fun saveUser(user: User) = database.save(user)
fun deleteUser(id: Long) = database.deleteById(id)
fun getUser(id: Long) = database.findById(id)
}
UserRepository.kt
package com.karbal.tutortek.repositories
import com.karbal.tutortek.entities.User
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository
#Repository
interface UserRepository : CrudRepository<User, Long> {
#Query("SELECT * FROM users", nativeQuery = true)
fun getAllUsers(): List<User>
}
UserPostDTO.kt
package com.karbal.tutortek.dto.userDTO
import java.sql.Date
data class UserPostDTO(
var firstName: String,
var lastName: String,
var rating: Float,
var birthDate: Date
)
The JSON that I send:
{
"firstName": "Thomas",
"lastName": "Thompson",
"rating": 4.7,
"birthDate": "2000-02-03"
}
Post works fine. Put works fine on other entities in my code. But here it always responds with 500 and a message "Operation is not supported for read-only collection". Any ideas why this could be happening?

Just solved this after a couple of hours. Changed lists in User class to mutable lists and now it works fine.

Related

Repository not executing methods in test

I am trying to simply test a repository. Here is my test code:
#ContextConfiguration(classes = [
DatabaseTestConfiguration::class
])
#TestPropertySource(
properties = [
"spring.jpa.hibernate.ddl-auto=create-drop",
"spring.flyway.enabled=false"
]
)
#AutoConfigureTestDatabase
#AutoConfigureDataJpa
#AutoConfigureTestEntityManager
internal open class UserTest {
#Autowired
private lateinit var entityManager: TestEntityManager
#Autowired
private lateinit var userRepository: UserRepository
#Test
#Transactional
open fun whenSuccessfullyFoundAnyDuplicatedUserThenReturnTrue() {
val user = User()
user.name = NAME
user.surname = SURNAME
user.gender = GENDER
entityManager.persistAndFlush(user)
assertEquals(true, userRepository.anyDuplicate(
name = user.name,
surname = user.surname,
gender = user.gender,
))
}
companion object {
const val NAME = "John"
const val SURNAME = "Snow"
const val GENDER = "male"
}
Here is my DatabaseTestConfiguration:
#TestConfiguration
#EntityScan(value = [
"com.username.db.entities"
])
#EnableJpaRepositories(
"com.username.db.repositories"
)
class DatabaseTestConfiguration {
}
This is my repository:
#Repository
interface UserRepository: JpaRepository<User,Long> {
#Query(
"""
SELECT CASE
WHEN COUNT(user.id) > 0 THEN TRUE
ELSE FALSE END
FROM User user
WHERE user.name = :name AND user.surname = :surname AND user.gender = :gender
)
"""
)
fun anyDuplicate(
#Param("name") name: String?,
#Param("surname") surname: String?,
#Param("gender") gender: String?
): Boolean
}
This is my entity:
#Entity
#Table(name = "user")
class User {
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "cdr_generator"
)
#SequenceGenerator(
name = "cdr_generator",
sequenceName = "cdr_seq",
allocationSize = 1
)
#Column(name = "id", nullable = false)
var id: Long? = null
#Column(name = "name", nullable = false)
var name: String? = null
#Column(name = "surname", nullable = false)
var surname: String? = null
#Column(name = "gender", nullable = false)
var gender: String? = null
}
Now when i am testing. this will always fail my test. When i debug it, i get that in my test it never enters userRepository.anyDuplicate() function. I assume that the problem is with configuring test. Can anybody help?

Spring JPA Expecting Member Declaration for Derived Query

I'm trying to add a derived query to my Spring application to return users from my SQL database with a matching email, but I've been running into the following error (sorry for the external link, this is my 1st Stack Overflow question):
Error Image Here
Here's my code for the files that I think could conceivably matter for this:
repository.kt
package com.companyname.productname.repository
import com.companyname.productname.entity.User
import org.springframework.stereotype.Repository
import org.springframework.data.jpa.repository.JpaRepository
import java.util.List
#Repository
interface UserRepository: JpaRepository<User, Long> {
List<User> findByEmail(String email);
}
service.kt
package com.companyname.productname.service
import com.companyname.productname.entity.User
import com.companyname.productname.repository.UserRepository
import org.springframework.stereotype.Service
import java.util.Optional;
#Service
class UserService(
val userRepository: UserRepository
) {
fun findAllUsers(): List<User> {
return userRepository.findAll()
}
fun findOneUser(id: Long): Optional<User> {
return userRepository.findById(id)
}
fun findByEmail(email: String): Optional<User> {
return userRepository.findByEmail(email)
}
fun createUser(user: User) {
userRepository.save(user)
}
fun updateUser(existingID: Long, info: User) {
val userCurrent = findOneUser(existingID).get()
val userUpdate = User()
userUpdate.id = existingID
userUpdate.firstName = if(info.firstName != "") info.firstName else userCurrent.firstName
userUpdate.lastName = if(info.lastName != "") info.lastName else userCurrent.lastName
userUpdate.email = if(info.email != "") info.email else userCurrent.email
userUpdate.admin = info.admin
userUpdate.enabled = info.enabled
userRepository.save(userUpdate)
}
fun deleteUser(id: Long) {
userRepository.deleteById(id)
}
}
entity.kt
package com.companyname.productname.entity
import javax.persistence.*
#Entity
#Table(name = "users")
class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0L
#Column(name = "first_name")
var firstName: String = ""
#Column(name = "last_name")
var lastName: String = ""
#Column(name = "email")
var email: String = ""
#Column(name = "security_role")
var admin: Boolean = false
#Column(name = "enabled")
var enabled: Boolean = true
}
controller.kt
package com.companyname.productname.controller
import com.companyname.productname.entity.User
import com.companyname.productname.service.UserService
import org.springframework.web.bind.annotation.*
import java.util.Optional;
#RestController
class DemoController {
#GetMapping("/hello-world")
fun helloWorld(): String {
return "Hello World!"
}
}
#RestController
class UserController(
val userService: UserService
) {
#GetMapping("/users")
fun findAllUsers(): List<User> {
return userService.findAllUsers()
}
#GetMapping("/users/{id}")
fun findOneUser(#PathVariable id: Long): Optional<User> {
return userService.findOneUser(id)
}
#GetMapping("/users/getEmail/{email}")
fun findByEmail(#PathVariable email: String): Optional<User> {
return userService.findByEmail(email)
}
#PostMapping("/users")
fun createUser(#RequestBody newUser: User) {
userService.createUser(newUser)
}
#PutMapping("/users/{id}")
fun updateUser(#PathVariable id: Long, #RequestBody newInfo: User) {
userService.updateUser(id, newInfo)
}
#DeleteMapping("/users/{id}")
fun deleteUser(#PathVariable id: Long) {
userService.deleteUser(id)
}
}
My apologies if this is something obvious, I'm new to Spring and all the documentation I've seen has led me to believe this should be working. Other requests I've implemented so far other than findByEmail have worked so far. Thanks in advance for any help!
The syntax of the findByEmail method in the UserRepository is not valid Kotlin, it's Java. The signature should look like this:
fun findByEmail(email: String): List<User>
By the way, there is yet another problem: you are trying to return List<User> from UserService.findByEmail while the method return type is Optional<User>.

One-To-Many relation with JPA & Hibernate

I am new to Stack Overflow, so sorry in advance if I make a mistake regarding asking a question.
I am trying to create a one-to-many relation between my User entity and my Team entity. Which is pretty straight forward as you can auto-generate a join table's using annotions. But I also want an extra column in this join table. I want to keep track of whether someone is a coach yes or no. I hope this ERD gives a clearer idea of what I mean:
As you can see, I also added the join table user_roles. I think this should be taken into consideration when considering how to solve this problem. Because the User Entity has a set of these roles. These (should) be retrieved when you retrieve a Team from the database.
This is what I have now in terms of code:
User class:
package com.goldwasser.app.resourceserver.entity
import java.time.LocalDateTime
import java.util.*
import javax.persistence.*
import javax.validation.constraints.Email
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Size
#Entity
#Table(name = "users")
data class User(
#NotBlank
#Size(max = 50)
#Email
private val username: String,
#NotBlank
#Size(max = 50)
#Email
private val email: String,
#NotBlank
#Size(max = 120)
private val password: String,
#NotBlank
#Size(max = 50)
private val firstname: String,
#NotBlank
#Size(max = 50)
private val lastname: String,
#NotBlank
private val createTime: LocalDateTime,
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",
joinColumns = [JoinColumn(name = "user_id")],
inverseJoinColumns = [JoinColumn(name = "role_id")])
private val roles: MutableSet<Role>,
#OneToOne(mappedBy = "player", cascade = [CascadeType.ALL], orphanRemoval = true)
var playerTeam: PlayerTeam? = null
) : AbstractJpaPersistable<UUID>() {
companion object {
operator fun invoke(
username: String? = null,
email: String? = null,
password: String? = null,
firstName: String? = null,
lastName: String? = null,
createTime: LocalDateTime? = null,
roles: MutableSet<Role>? = null
) = User(
username ?: "",
email ?: "",
password ?: "",
firstName ?: "",
lastName ?: "",
createTime ?: LocalDateTime.now(),
roles ?: mutableSetOf()
)
}
fun getRoles(): Set<Role> = roles
fun getPassword(): String = password
fun getUsername(): String = username
fun getEmail(): String = email
fun getFirstname(): String = firstname
fun getLastname(): String = lastname
fun getCreateTime(): LocalDateTime = createTime
fun setRoles(roles: Set<Role>) {
this.roles.addAll(roles)
}
}
PlayerTeam class:
package com.goldwasser.app.resourceserver.entity
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import java.util.UUID
import javax.persistence.*
#Entity
#Table(name = "player_teams")
data class PlayerTeam(
#EmbeddedId
var playerTeamId: PlayerTeamId? = null,
#OneToOne(fetch = FetchType.LAZY)
#MapsId("userId")
val player: User? = null,
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("teamId")
val team: Team? = null,
val isCoach: Boolean = false
) {
override fun toString(): String {
val mapper = ObjectMapper()
mapper.enable(SerializationFeature.INDENT_OUTPUT)
return mapper.writeValueAsString(this)
}
fun getIsCoach(): Boolean = isCoach
}
PlayerTeamId (Composite-key) class:
package com.goldwasser.app.resourceserver.entity
import org.hibernate.annotations.GenericGenerator
import org.hibernate.annotations.Type
import java.io.Serializable
import java.util.UUID
import javax.persistence.Column
import javax.persistence.Embeddable
import javax.persistence.GeneratedValue
#Embeddable
data class PlayerTeamId (
#GeneratedValue(generator = "uuid2")
#GenericGenerator(
name = "uuid2",
strategy = "uuid2"
)
#Column(name = "user_id", updatable = false, nullable = false, columnDefinition = "VARCHAR(36)")
#Type(type = "uuid-char")
var userId: UUID = UUID.randomUUID(),
#GeneratedValue(generator = "uuid2")
#GenericGenerator(
name = "uuid2",
strategy = "uuid2"
)
#Column(name = "team_id", updatable = false, nullable = false, columnDefinition = "VARCHAR(36)")
#Type(type = "uuid-char")
var teamId: UUID = UUID.randomUUID()
) : Serializable
My Team class:
package com.goldwasser.app.resourceserver.entity
import java.util.UUID
import javax.persistence.*
#Entity
#Table(name="teams")
data class Team(
val name: String,
#OneToMany(mappedBy = "team", cascade = [CascadeType.ALL], orphanRemoval = true)
var playerTeams: MutableSet<PlayerTeam>? = null
): AbstractJpaPersistable<UUID>() {
}
And at last, my main Entity class (all entities should inherit this one because it provides them with an Id as UUID):
package com.goldwasser.app.resourceserver.entity
import org.hibernate.annotations.GenericGenerator
import org.hibernate.annotations.Type
import org.springframework.data.util.ProxyUtils
import java.io.Serializable
import javax.persistence.*
#MappedSuperclass
abstract class AbstractJpaPersistable<T : Serializable> {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(
name = "uuid2",
strategy = "uuid2"
)
#Column(name = "id", updatable = false, nullable = false, columnDefinition = "VARCHAR(36)")
#Type(type = "uuid-char")
private var id: T? = null
fun getId(): T? {
return this.id
}
fun setId(type: T?) {
this.id = type
}
override fun equals(other: Any?): Boolean {
other ?: return false
if (this === other) return true
if (javaClass != ProxyUtils.getUserClass(other)) return false
other as AbstractJpaPersistable<*>
return if (null == this.id) false else this.id == other.id
}
override fun hashCode(): Int {
return 31
}
override fun toString() = "Entity of type ${this.javaClass.name} with id: $id"
}
Now that you've seen all my entity classes, I will show/tell you where I have been getting stuck at.
I have a repository called "TeamRepository". It's responsible for handling all database action related stuff for the Team entity:
package com.goldwasser.app.resourceserver.repository
import com.goldwasser.app.resourceserver.entity.Team
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.util.UUID
#Repository
interface TeamRepository : JpaRepository<Team, UUID> {
}
And I have a service called "TeamServiceImpl":
package com.goldwasser.app.resourceserver.service
import com.goldwasser.app.resourceserver.entity.PlayerTeam
import com.goldwasser.app.resourceserver.entity.PlayerTeamId
import com.goldwasser.app.resourceserver.entity.Team
import com.goldwasser.app.resourceserver.entity.User
import com.goldwasser.app.resourceserver.repository.PlayerTeamRepository
import com.goldwasser.app.resourceserver.repository.TeamRepository
import com.goldwasser.app.resourceserver.repository.UserRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.annotation.Persistent
import org.springframework.stereotype.Service
import javax.transaction.Transactional
#Service
class TeamServiceImpl : TeamService {
#Autowired
private lateinit var playerTeamRepository: PlayerTeamRepository
#Autowired
private lateinit var userRepository: UserRepository
#Autowired
private lateinit var teamRepository: TeamRepository
#Transactional
override fun addPlayerToTeam(player: User, team: Team) {
val playerTeamId = PlayerTeamId(
player.getId()!!,
team.getId()!!
)
val playerTeam = PlayerTeam(
playerTeamId,
user = player,
team = team,
isCoach = true
)
playerTeamRepository.save(playerTeam)
}
override fun getPlayerTeam(team: Team) : Team {
val teamInDb = teamRepository.findById(team.getId()!!).get()
return teamInDb;
}
}
The problem is that when I call getPlayerTeam(team), my teamInDb does contain a Team-object, but the playerTeams of the Team-object results in a LazyInitializationException. The same applies when I try to fetch a user object. I know this has to do something with JPA not being able to access the requested entity because of the session being closed. But I do not know how to fix this.
EDIT:
I have added the all-open plugin:
plugins {
id("org.springframework.boot") version "2.6.6"
id("org.jetbrains.kotlin.plugin.allopen") version "1.6.21"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.6.10"
kotlin("plugin.spring") version "1.6.10"
kotlin("plugin.jpa") version "1.6.10"
}
Now I do get a PlayerTeam object containing a Player and Team. But those Player and Team have also have a PlayerTeam set which cannot be initialised, see picture below:

how to include an Embedded attribute to json return object spring boot

I have a Kotlin Spring Boot application with these following entities:
import com.fasterxml.jackson.annotation.JsonIgnore
import javax.persistence.*
#Entity
#Table(name = "users")
class Users(
#Id
var username: String = "",
#JsonIgnore
var password: String = "",
#JsonIgnore
var enabled: Boolean = false,
#ElementCollection
#CollectionTable(name = "authorities", joinColumns = [JoinColumn(name = "username")])
#MapKeyColumn(name = "users")
private var authorities : MutableSet<Authorities> = mutableSetOf()
)
and
#Embeddable
class Authorities(
var authority: String
)
When I call this endpoint:
#GetMapping("/users")
fun getUserById(#RequestParam(value = "username") username : String) : ResponseEntity<Users>{
val optionalUser = usersService.getByUsername(username)
return if(optionalUser.isPresent) ResponseEntity.ok(optionalUser.get())
else ResponseEntity(HttpStatus.BAD_REQUEST)
}
it returns correctly, as you can see in this json:
{
"username": "test#gmail.com"
}
But it doesn't return the list of authorities that belongs to this user, and I wanted to have it returned. My question is: How could I do it?
Thank you so much for helping

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.

Resources