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

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.

Related

FindById doesn't work with UUID in Mongodb SpringData

I try to use Mongodb with Spring Data.
I wanted to use UUID instead of ObjectId. I have followed this tutorial: https://www.baeldung.com/java-mongodb-uuid (some differences might exist because I use Kotlin). I took path 2 where I added Entity callback. When I create a new entity it is saved to the database with UUID as I wanted. If I use mongo console I can type:
db.home.find({_id: UUID("18aafcf9-0c5a-46f3-84ff-1c25b00dd1ab")})
And I will find my entity by id.
However, when I try to do it by code it doesn't work as it should. It will always throw here DataOperationException(NOT_FOUND) because findById returns null.
fun findHomeById(id: String): Home {
val home = homeRepository.findById(id)
return home.unwrap() ?: throw DataOperationException(NOT_FOUND)
}
Here is repository
#Repository
interface HomeRepository: MongoRepository<Home, String>
Abstraction with id.
abstract class UuidIdentifiedEntity {
#Id
var id: UUID? = null // I tried use UUID type and String with the same result
}
And my home class
class Home(
var address: String,
var rooms: Int,
): UuidIdentifiedEntity()
Not sure if this will help, but see if changing your #Id annotation to #MongoId fixes this. Under certain circumstances, Mongo needs to know more about a field being used for an id. #MongoId should give you more control on how the field is stored too.
What is use of #MongoId in Spring Data MongoDB over #Id?

How to POST entity with relationships in REST endpoint - Kotlin Spring Data

I followed this tutorial to create a basic web app in Kotlin using Spring Boot. However, I fail to POST new entities with a many-to-one relationship to an existing resource.
My code:
#Entity
class Song(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var title: String,
#ManyToOne(fetch = FetchType.EAGER)
var addedBy: User)
#Entity
class User(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var email: String,
var displayName: String)
#RestController
#RequestMapping("/api/songs")
class SongController(private val repository: SongRepository) {
#PostMapping("/")
fun add(#RequestBody song: Song) =
repository.save(song)
This answer and others point out that you can reference another resource using its URI, but sending the following request:
{
"title": "Some title",
"addedBy": "http://localhost:8080/api/users/1"
}
gets me an errors with stack trace org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of 'com.example.springboot.User' (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/users/1'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of 'com.example.springboot.User' (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/users/1')\n at [Source: (PushbackInputStream); line: 6, column: 13] (through reference chain: com.example.springboot.Song[\"addedBy\"])
I got out of this that somewhere between Jackson/Hibernate/Spring Data it fails to convert the User resource URI into a User entity, but I'm in the dark where this magic should happen.
It seems to be an issue that occurs with Kotlin specifically. All the suggestions here on SO do not solve this specific error and the tutorial itself stops just short of dealing with relationships. If it's not the right approach at all to handle relationships this way I'd be eager to know what the preferred practice would be.
The tutorial is using HATEOAS. See the request body where they are referencing the corresponding child entity by using
"books" : { "href" : "http://localhost:8080/authors/1/books" }
Meaning you should also apply this pattern to your request. Otherwise this will not work. HATEOAS allows you to directly reference the related child entities by their corresponding resource path but you need to keep the necessary structure which your posted request body is missing. Further you must support HATEOAS in your WebService / WebApi / Spring Boot Project.
What you could do:
{
"title": "Some title",
"addedByUserId": "1"
}
Then
#PostMapping("/")
fun add(#RequestBody song: Song) =
val userEntity = userRepository.findById(song.getAddedByUserId())
Song newSong = new SongEntity();
// map props
newSong.setUser(userEntity)
repository.save(song)
That code does not work but I hope you get the idea.
Further
In your code you are treating the Request Body as an Entity. Please consider to separate your incoming Class and your Entity class. This would make several things easier.
I think you're missing jackson's kotlin module, it's exactly what it was created for.
https://github.com/FasterXML/jackson-module-kotlin
Just adding this dependency in your project will cause spring to autoconfigure your object mapper with this new module. If you have a Bean with your own created objectMapper then you need to configure it manually, there's a section about this in module's README.md

How to prevent saving over an existing entity with Spring Data REST

(Samples in Kotlin)
I have an entity with manually assigned IDs:
#Entity
#Table(name = "Item")
class Item {
#Id
#Column(name = "ItemId", nullable = false, updatable = false)
var id: Int? = null
#Column(name = "Name", nullable = false)
var name: String? = null
}
and the Spring Data REST repository for it:
interface ItemRepository : PagingAndSortingRepository<Item, Int>
If I do a POST to /items using an existing ID, the existing object is overwritten. I would expect it to throw back an error. Is there a way to configure that behavior without rolling my own save method for each resource type?
Thanks.
I ended up using a Spring Validator for this with the help of this article.
I created the validator like this:
class BeforeCreateItemValidator(private val itemRepository: ItemRepository) : Validator {
override fun supports(clazz: Class<*>) = Item::class.java == clazz
override fun validate(target: Any, errors: Errors) {
if (target is Item) {
itemRepository
.findById(target.id!!)
.ifPresent {
errors.rejectValue("id",
"item.exists",
arrayOf(target.id.toString()),
"no default message")
}
}
}
}
And then set it up with this configuration:
#Configuration
class RestRepositoryConfiguration(
private val beforeCreateItemValidator: BeforeCreateItemValidator
) : RepositoryRestConfigurer {
override fun configureValidatingRepositoryEventListener(
validatingListener: ValidatingRepositoryEventListener) {
validatingListener.addValidator("beforeCreate", beforeCreateItemValidator)
}
}
Doing this causes the server to return a 400 Bad Request (I'd prefer the ability to change to a 409 Conflict, but a 400 will do) along with a JSON body with an errors property containing my custom message. This is fine for my purposes of checking one entity, but if my whole application had manually assigned IDs it might get a little messy to have to do it this way. I'd like to see a Spring Data REST configuration option to just disable overwrites.
You can add a version attribute to the entity annotated with #Version this will enable optimistic locking. If you provide always the version 0 with new entities you'll should get an exception when that entity does already exist (with a different version).
Of course you then need to provide that version for updates as well.

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

Kotlin & Spring (Data): custom setters

I currently am working on a project that uses Spring Boot, written in Kotlin. It has got to be mentioned that I am still relatively new to Kotlin, coming from Java. I have a small database consisting of a single table that is used to look up files. In this database, I store the path of the file (that for this testing purpose is simply stored in the the resources of the project).
The object in question looks as follows:
#Entity
#Table(name = "NOTE_FILE")
class NoteFile {
#Id
#GeneratedValue
#Column(name = "id")
var id: Int
#Column(name = "note")
#Enumerated(EnumType.STRING)
var note: Note
#Column(name = "instrument")
var instrument: String
#Column(name = "file_name")
var fileName: String
set(fileName) {
field = fileName
try {
file = ClassPathResource(fileName).file
} catch (ignored: Exception) {
}
}
#Transient
var file: File? = null
private set
constructor(id: Int, instrument: String, note: Note, fileName: String) {
this.id = id
this.instrument = instrument
this.note = note
this.fileName = fileName
}
}
I have created the following repository for retrieving this object from the database:
#Repository
interface NoteFileRepository : CrudRepository<NoteFile, Int>
And the following service:
#Service
class NoteFileService #Autowired constructor(private val noteFileRepository: NoteFileRepository) {
fun getNoteFile(id: Int): NoteFile? {
return noteFileRepository.findById(id).orElse(null)
}
}
The problem that I have is when I call the getNoteFile function, neither the constructor nor the setter of the constructed NoteFile object are called. As a result of this, the file field stays null, whereas I expect it to contain a value. A workaround this problem is to set the fileName field with the value of that field, but this looks weird and is bound to cause problems:
val noteFile: NoteFile? = noteFileService.getNoteFile(id)
noteFile.fileName = noteFile.fileName
This way, the setter is called and the file field gets the correct value. But this is not the way to go, as mentioned above. The cause here could be that with the Spring Data framework, a default constructor is necessary. I am using the necessary Maven plugins described here to get Kotlin and JPA to work together to begin with.
Is there some way that the constructor and/or the setter does get called when the object is constructed by the Spring (Data) / JPA framework? Or maybe should I explicitly call the setter of fileName in the service that retrieves the object? Or is the best course of action here to remove the file field as a whole and simply turn it into a function that fetches the file and returns it like that?

Resources