Spring Boot and Mongo - findById only works with ObjectID and not Id of String - spring

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.

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 make a custom converter for a String field using Spring Data Elasticsearch?

I am using Spring Data Elasticsearch in my project. I have a class like this,
#Document("example")
class Example {
#Id
private String id;
private String name;
private String game;
private String restField;
}
What I need is when ever I am saving the Example object to elasticsearch I am removing a value. But when I am getting the data from elasticsearch I need that removed value to be appended.
save(Example example) {
example.setRestField(example.getRestField().replace("***", ""));
exampleRepository.save(example);
}
get(String id) {
Example example = exampleRepository.findById(id);
example.getRestField().concat("***");
return example;
}
Right now I am doing like the above way. But can we use a custom converter for this? I checked the converter examples for Spring Data Elasticsearch but those are for different different objects. How I can create a custom converter only for this particular String field restField? I don't want to apply this converter for other String fields.
Currently there is no better solution. The converters registered for Spring Data Elasticsearch convert from a class to a String and back. Registering a converter for your case would convert any String property of every entity.
I had thought about custom converters for properties before, I have created a ticket for this.
Edit 05.11.2021:
Implemented with https://github.com/spring-projects/spring-data-elasticsearch/pull/1953 and will be available from 4.3.RC1 on.

Is there a way to persist a field in Elasticsearch but not in mongodb?

I am using both elasticsearch and mongodb in my project. I am looking for functionality similar to using #Transient, in the sense that the field should not persisted in mongodb, but can be in elasticsearch.
Public class pojo{
#PersistInBothESAndMongo
String field1;
#PersistOnlyInES
String field2;
}

SpringBoot GraphQL field name mismatch

It seems that the names defined in the .graphqls file MUST match the field names in the POJO. Is there a way to maybe annotate the field so that they don't have to?
For example, I have something like this in the graphqls file
type Person {
personId: ID!
name: String!
}
Then in my Entity POJO I have like
#Id
#Column(name="PERSON_ID")
#JsonProperty("person_id")
private int personId;
#Column(name="NAME")
#JsonProperty("name")
private String name;
So, the intention is for the field name to be personId and the database to store it as a column called PERSON_ID and for it to get serialized as JSON and GraphQL as person_id
But graphql talks in the language of the schema. So it serializes it as personId which matches the schema field but is not the intention. I could change the schema to be person_id but then I need to change the field too... This isn't the end of the world but it's quite "un-javalike" to have fields named that way.
I am using this library:
compile group: 'com.graphql-java', name: 'graphql-spring-boot-starter', version: '5.0.2'
I have also seen the #GraphQLName annotation in the annotations library but I must be missing something because it doesn't do what I am expecting or maybe I am using it wrong.
Is there some way to get around this or should I just change the names?
GraphQL Java uses PropertyDataFetcher by default to resolve field values (see data fetching section in the docs). This data fetcher works out-of-the box when the data object returned by a top level field data fetcher contains child fields that match the data object property names.
However, you can define your own data fetcher for any field and use whatever rule you need.
So, if you want a schema that looks like this
type Person {
person_id: ID!
name: String!
}
and your entity like this:
class Person {
private int personId;
private String name;
// getters and setters
}
You can write a simple custom data fetcher for the field personId
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
// query root data fetchers wiring
.type(newTypeWiring("Person")
.dataFetcher("person_id", environment -> {
Person person = environment.getSource();
return person.getPersonId();
})
)
// maybe other wirings
.build();
}

get one field from elasticsearch by spring data

I have a ES Document like this
class User {
String name;
String describe;
List<String> items;
}
I'm using spring data to talk to ES by the Repository interface
interface UserRepository extends Repository<User, String> {
}
Now I need to build a rest interface which responses a JSON-format data like this
{"name": String, "firstItem": String}
Because the describe and items in User is very big, it's very expensive to retrieve all field from the ES.
I know the ES have a feature named "Response Filtering" which can fit my requirement, but I don't find a way to using it in Spring Data.
How to do this in spring data?
What you need is a mix of source filtering (for not retrieving heavy fields) and response filtering (for not returning heavy fields). However, the latter is not supported in Spring Data ES (yet)
For the former, you can leverage NativeSearchQueryBuilder and specify a FetchSourceFilter that will only retrieve the fields you need. The latter is not supported yet in Spring Data ES. What you can do is to create another field named firstItem in which you'd store the first element of items so that you can return it for this query.
private ElasticsearchTemplate elasticsearchTemplate;
String[] includes = new String[]{"name", "firstItem"};
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withSourceFilter(new FetchSourceBuilder(includes, null))
.build();
Page<User> userPage =
elasticsearchTemplate.queryForPage(searchQuery, User.class);

Resources