Kotlin Type Mismatch: Taking String from URL path variable and using it as an ID - spring-boot

My Spring Boot Application in Kotlin has a POST endpoint defined like this:
fun postTermin( #PathVariable("pathID") pathID: String, #Validated #RequestBody termin: RequestBody): ResponseEntity<Appointment> {
return ResponseEntity(HttpStatus.NOT_IMPLEMENTED)
}
I'd like to take that "pathID" and use it to find an entity from a repository like so:
myRepository.findById(pathID)
The CRUDRepository I'm calling there is for an Entity "Dealer" where the ID is defined as:
#Id
#Column(name = "id", length = 10, nullable = false)
open var id: String = ""
The Problem: I get this compile error:
Kotlin: Type mismatch: inferred type is Optional<Dealer!> but Dealer?
was expected
What's the problem here? Why the "Optional"?

The comment by Slaw really helped.
As described in Spring Data JPA How to use Kotlin nulls instead of Optional there's also a "findByIdOrNull" function for CRUDRepository, i had missed that.
For my use case, that works.

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?

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

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.

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?

Kotlin data class No String-argument constructor with spring data rest

I'm using Spring data rest with Kotlin and if I use data classes the associations via uri stops working with the error no String-argument constructor/factory method to deserialize from String value
Data class
#Entity
data class Comment(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var author: String? = null,
var content: String? = null,
#ManyToOne
var post: Post? = null) {
}
If I use a simple class instead the association works fine.
#Entity
class Comment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = 0
var author: String? = null
var content: String? = null
#ManyToOne
var post: Post? = null
}
The association is done via a POST request {"author":"John Doe","content":"Dummy Content", "post":"http://localhost:8080/post/33"}
Any ideia why I have this error when I use a data class and what can I do to use the association creation via uri and keep using data classes?
I did some investigation, and turns out Spring Data Rest uses a custom Jackson module to deserialize JSON into JPA entities: it uses PersistentEntityJackson2Module class and using the inner class UriStringDeserializer to resolve the concrete entities from entity URI references, http://localhost:8080/post/33 in your example.
Problem is, this custom deserialization only kicks in when the "standard deserialization" of Jackson is triggered: The one that uses empty constructor, then using setters to resolve & set the fields. At that moment, UriStringDeserializer converts the string into the concrete entity - Post instance of your example.
When you use a data class, the class neither has an empty constructor nor setters, therefore in BeanDeserializer#deserializeFromObject method of Jackson, it branches into if (_nonStandardCreation) being true, from there the call goes into BeanDeserializerBase#deserializeFromObjectUsingNonDefault , but not handed over to PersistentEntityJackson2Module anymore, and directly failing due to type mismatch between the constructor argument and the json value.
It seems you need to create a feature request for it to be implemented. If you decide to implement yourself, providing a _delegateDeserializer to the BeanDeserializer might be a start (not sure).
However, I don't know how JPA itself plays with data classes in the first place - after all it tracks the entity state changes, but a data class cannot have state changes. So, it might not be possible to use data classes after all - better to keep in mind.
Note: You probably cannot simply extend/override PersistentEntityJackson2Module because it is registered to multiple beans in RepositoryRestMvcConfiguration

Spring Data JPA - How to find by param with underscore

Here is my model:
public class Log {
#Id
private String id;
private String project;
private String test_no;
// Constructor, getters and setters.
}
How can I add a findBy query which allows me to finding an element by its test_no value? I have tried those method headers but they don't work:
List<Log> findByTest_No(String test_no);
The error that my STS reports is: Invalid delivery query! No property test found in type Log!
List<Log> findByTest_no(String test_no);
The error that my STS reports is: Invalid delivery query! No property test found in type Log!
List<Log> findByTestno(String test_no);
The error that my STS reports is: Invalid derived query! No property testno found for type Log! Did you mean 'test_no'?
It seems that _ is a special character which separates properties names
... it is possible for the algorithm to select the wrong property ... To resolve this ambiguity you can use \_ inside your method name to manually define traversal points ...
Docs
So it can't find test field in Log class.
Try to use:
#Query("select log from Log log where log.test_no = ?1")
List<Log> findByTestNo(String testNo);
There is no need to use private String test_no; You may use private String testNo; Spring automatically understand that you are binding test_no column in the database. Also you can use
#Column(name = "test_no")
private String testNo;
After this changes you can execute queries, mentioned in your question without any custom sql.
List<Log> findByTestNo(String testNo);

Resources