I'm unable to get documents by Id (using Spring Boot and Mongo) - I've tried the following lines with no luck:
sRepository.findById(songId)
and
query.addCriteria(Criteria.where("_id").is(songId))
I've added the #Id annotation in my model to specify the id field:
#Document(collection = "songs")
data class Song(
#Id
val id: String,
and in my mongo the document I've added looks like:
{
"_id": "532132assad4a0",
"code": "956743458",
...
The Mongo repository class:
#Repository
interface SongRepository : MongoRepository<Song, String>
Any help would be appreciated - rather confused by it.
FOUND THE ANSWER:
I had to add the annotation #MongoId instead of #Id as I was saving it as a string, didn't want any other conversion happening on it.
Found at the documentation here: https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mapping.conventions.id-field
Related
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.
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
I have a Spring Boot GraphQL service which reads from Mongo.
I've noticed that if my MongoDB document ID has ObjectID e.g. "_id": ObjectID("5e5605150") I'm able to get results from doing myRepository.findById(myId).
However if that id is just a string like "_id": "5e5605150" then findById returns nothing.
The repository looks like this:
#Repository
interface MyRepository : MongoRepository<Song, String>
And that Song looks like this:
#Document(collection = Song.COLLECTION)
data class Song(
#Id
val id: String,
val title: String
) {
companion object {
const val COLLECTION: String = "song"
}
}
Any ideas?
Thanks.
See the Spring Data MongoDB reference on mapping - the _id field is treated specially. If it's a String or BigInteger type, it will automatically be converted to/from an ObjectId. MongoDB supports plain strings for the special _id field though, so if you're inserting documents that don't go through Spring Data (or inserting documents as a raw JSON string for the MongoDB driver to insert) then MongoDB won't automatically convert strings to ObjectId - it will store them as strings.
So, if you insert the document {"_id": "5e5605150"} into your DB, the Spring Data MongoDB mapper won't be able to find it, because it will be searching by ObjectId only.
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.
I am using Spring Data Mongo in my project, as the following:
Spring Data MongoDB 1.2/ Spring Data Commons 1.5
Spring 3.2.2 RELEASE
MongoDB / QueryDSL 2.9/ Mongo Java Driver 2.10.1
Case 1: #Id from the spring data commons, #Document is from Spring data Mongo.
#Document
class User{
#Id String id;
}
#Document
class Picture{
#Id String id;
}
#Document
class Avatar extends Picture{
#DBref User user;
}
Neither I used Spring Data Repostory api or QueryDSL one, I can not get the avatar data by user.
//decalred in repository
List<Avatar> findByUser(User user);
// or from the QueryDSL executor
List<Avatar> avatars=rep.findAll(QAvatar.avatar.user.eq(user));
All return empty collections(of course there are some data in it).
Case 2: If I remove #DBRef, in the first time, after I have insert the avatar and user, it worked, but when I updated the data in User, then get the avatar by user, return empty list.
#Document
class Avatar extends Picture{
User user;
}
Case 3: change the User to String(userid), it works.
#Document
class Avatar extends Picture{
String userId;
}
Any suggestion for java modeling for MongoDB here? Thanks.