Spring Data Mongo Linked Document Storage - spring

Is there a pattern for Spring Data Mongo to support persisting a link to a separate document in separate collection and having it re-hydrate automagically when its pulled back out of the database?
#Document
class Person <-- Saves to the Person Collection
#id
UUID id
String name
Address address
#Document
class Address --
#id
UUID id
String address1
...
Calling save(person) I'd want the address property in the database to reflect the Address Id, and have the address object persisted to the address collection. When I pull the Person back out, Address would be a fully hydrated (or maybe lazily?) and accessible.
Spring Data Mongo 3.1
Spring Boot 2.4
Groovy 2.5
JDK11

At the time of writing Spring Data MongoDB 3.2 only supports linking documents via DBRefs. Those however follow a fixed structure so that the value representing the link within the target document looks like this
{ "$ref" : <value>, "$id" : <value>, "$db" : <value> }
#DBRef allows lazy loading via its lazy attribute. Please have a look at the reference documentation
The upcoming Spring Data MongoDB 3.3 release will extend the support for linking documents to cover the above mentioned use case. Still the linked document needs to be persisted on its own. #DocumentReference allows to link Address via the id property as outlined below.
#Document
class Person {
#Id
String id;
#DocumentReference
Address address;
}
{
"_id" : "p1457",
"name" : "...",
"address" : "a4711"
}
#DocumentReference will also support lazy loading and can be used to link a collection of documents.
Please find the full documentation here.

Related

How do I map "id" property into "id" field in Mongo without #Field annotation?

I have multiple classes with the id field. I would like to store their instances in MongoDB using spring-data-mongodb. I would like to map id property in these classes to id field in Mongo.
So here is what my classes look like:
public class Entity {
private final String id; // = 42
...
}
And here is what I am expecting to be in Mongo collection:
{
"_id": ObjectId("5fba805dfdaaa760974d45de"),
"id": "42"
}
By default, spring-mongodb maps id property to _id field in Mongo. I know that the simplest way to avoid this is to put #org.springframework.data.mongodb.core.mapping.Field("id") annotation on id property in a Java class. But I prefer not to use this annotation since I would like to keep my model independent of Mongo, or Spring, or whatever. Which options are possible here?
Here is what I have tried or checked:
Registering custom AbstractMongoEventListener in order to modify Mongo documents just before they are written to Mongo, or just after they have been read from Mongo. It does not work for me since custom listener is called only during get and insert operations, but not during the update or upsert operations (see discussion here for details).
Providing custom FieldNamingStrategy — it does not work since in the spring-data-mongodb code they use strategy only if field name is not id or _id.
Providing custom converter for each of my classes. I believe it should work. But this approach seems to be too complicated since I have many classes with many properties in each of them and I'm not sure I would like to have many converters with boilerplate code inside.
Any help would be appreciated.
Don't you think that id with _id is already confusing?
If you have a strong valid reason to have an id (different than DB one); give it a name .e.g: version_id, old_id, other_services_id ... some meaningful name.
And yes using #Field in your case is the simplest way

Mongo Spring #Document annotation

I'm trying to work with MongoTemplate and Spring and while looking on some other peoples code I was that everyone uses #Document annotation.
I did not used it at all up until not and everything worked fine for me.
I'm afraid I'm missing something and could not find any specific detailed information about the benefits of #Document annotation.
#Document is an annotation provided by Spring data project.
It is used to identify a domain object, which is persisted to MongoDB.
So you can use it to map a Java class into a collection inside MongoDB.
If you don't use Spring Data, you don't need this annotation.
I wrote a German blog post about how to use Spring Data for MongoDB - #Document is used there also:
https://agile-coding.blogspot.com/2020/10/keine-ahnung-von-mongodb-dann-nimm.html
This annotation serves only for specifying collection properties.
You can basically create simple class without any annotation needed.
Document annotation serves you for example when you are not happy with autogenerated collection name. If you do not specify #Document annotation, you still have the same result, for example
[
{
"_id": {"$oid": "62b43525de57ec7dec41a286"},
"_class": "com.example.demo.Person",
"name": "Adam"
}
]
This annotation may however be important for some kind of annotation based process to target which document are in use (maybe for indexing and so on)
Same goes for Id annotation:
if you do not provide any id specification, Id field is automatically added by mongo
if you have field named as "id" of type BigInteger, ObjectId or String, then this field is automatically populated after insert
From the docs https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo-template.id-handling
11.5.1. How the _id Field is Handled in the Mapping Layer
MongoDB requires that you have an _id field for all documents. If you
do not provide one, the driver assigns an ObjectId with a generated
value. When you use the MappingMongoConverter, certain rules govern
how properties from the Java class are mapped to this _id field:
A property or field annotated with #Id (org.springframework.data.annotation.Id) maps to the _id field.
A property or field without an annotation but named id maps to the _id field.
The following outlines what type conversion, if any, is done on the
property mapped to the _id document field when using the
MappingMongoConverter (the default for MongoTemplate).
If possible, an id property or field declared as a String in the Java class is converted to and stored as an ObjectId by using a Spring
Converter<String, ObjectId>. Valid conversion rules are delegated to
the MongoDB Java driver. If it cannot be converted to an ObjectId,
then the value is stored as a string in the database.
An id property or field declared as BigInteger in the Java class is converted to and stored as an ObjectId by using a Spring
Converter<BigInteger, ObjectId>.
If no field or property specified in the previous sets of rules is
present in the Java class, an implicit _id file is generated by the
driver but not mapped to a property or field of the Java class.
When querying and updating, MongoTemplate uses the converter that
corresponds to the preceding rules for saving documents so that field
names and types used in your queries can match what is in your domain
classes.
Some environments require a customized approach to map Id values such
as data stored in MongoDB that did not run through the Spring Data
mapping layer. Documents can contain _id values that can be
represented either as ObjectId or as String. Reading documents from
the store back to the domain type works just fine. Querying for
documents via their id can be cumbersome due to the implicit ObjectId
conversion. Therefore documents cannot be retrieved that way. For
those cases #MongoId provides more control over the actual id mapping
attempts. Example 62. #MongoId mapping
public class PlainStringId { #MongoId String id; }
public class PlainObjectId { #MongoId ObjectId id; }
public class StringToObjectId { #MongoId(FieldType.OBJECT_ID) String id; }
The id is treated as String without further conversion. The id is
treated as ObjectId. The id is treated as ObjectId if the given
String is a valid ObjectId hex, otherwise as String. Corresponds to
#Id usage.

DatastoreException: The given key doesn't have a String name value but a conversion to String was attempted

Changed #Id type from Long to String in GCP datastore using spring java Repository.
DatastoreDataException
org.springframework.cloud.gcp.data.datastore.core.mapping.DatastoreDataException: The given key doesn't have a String name value but a conversion to String was attempted
So Keys in datastore can either have the property id which is a number or the property name which is a string.
I included 2 screenshots of an example of each
Numeric id:
String name:
So when you say this:
Changed #Id type from Long to String in GCP datastore using spring java Repository.
What did you actually do?
It sounds like you just changed a model definition in your ORM. This doesn't actually change anything already stored in the datastore, it only impacts new entities going forward. So it sounds like, you're fetching entities with ids but your model definition is expecting them to have names.
You would have to have some kind of data migration job convert them all over. Convert isnt even the right word since changing the key to use name instead would just create a new entity. You would have to delete the old entities that use id in this process.
You would also have to update all other entities that have key properties to this kind too.
So we changed the Id from Long to String. And datastore table was already created with Long Id. so when we changed it we saw the above exception. By creating new table with String Id we resolved the issue.

Spring Data- how to tell spring what entities to retrieve

If i have several entites, lets say :
#Entity
class Book{
String name;
Author author;
}
#Entity
class Author{
String name;
City hometown;
}
#Entity
class City{
String cityName;
}
If i want to retrieve all the books, if i use classic JPA Repository and Spring Data and just do a findAll(), it will get me all the books with all the Authors with all their home towns. I know i can use #JsonIgnore, but i think that only prevents whats being returned, not whats being looked up in the database. And also i have methods that DO want to return both books and authors, so #JsonIgnore -ing does not work for me. Is there anything like this ? To tell Spring Data what to look up and what to return ? Any links or guides or methods i don't know of, would be appreciated.
Spring Data has the concept of 'projections' which allow you to return different representations of the same Entity.
Official Documentation:
Spring Data query methods usually return one or multiple instances of
the aggregate root managed by the repository. However, it might
sometimes be desirable to create projections based on certain
attributes of those types. Spring Data allows modeling dedicated
return types, to more selectively retrieve partial views of the
managed aggregates.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
Where a Projection is a 'closed' projection (a projection interface whose accessor methods all match properties of the target aggregate) then the documentation notes that additionally:
Spring Data can optimize the query execution [to select only the relevant fields], because we know about
all the attributes that are needed to back the projection proxy
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.interfaces.closed
Spring Data also allows for Projections to be specified dynamically at runtime. See further:
https://github.com/spring-projects/spring-data-commons/blob/master/src/main/asciidoc/repository-projections.adoc#dynamic-projections
First mark your relations as LAZY
Then specify what data needs to be fetched on a per-query basis.
See for example:
https://vladmihalcea.com/eager-fetching-is-a-code-smell/

how should i get data from mongo db using spring mongo repository?

i am using mongodb in my spring boot application and i am using mongo repository interface to get data from the database this is the way i am getting the data .
School sch=repository.findOne("id");
this will give me the school object then i can get all the data from there but my question is will it affect my application's performance if i get the whole object everytime i need some data from that object even if i need some fields . if so what will be the method to do that i searched and i see that using Query annotiation i can limit the fields but even then it give whole object it put all the other fields null and data is only at fields which i specify . any guidence will be helpfull .
You can use a projection interface to retrieve a subset of attributes.
Spring Data Mongo DB
interface NamesOnly {
String getName();
}
interface SchoolRepository extends Repository<School, UUID> {
NamesOnly findOneById(String id);
}
So after reading the documentation and reading other options this is the only solution i find where i can search the result using id and get only field i want in the result since i am only searching using the id so i have to overload the findbyId and since projection interface only change the return type i can not use that so here is what i did .
#Query(value="{_id:?0}",fields="{?1:1,?2:1}")
List<School> findById(String schoolId, String fieldOne,String fieldTwo);
here ?0 is a place holder for schoolId and ?1 and ?2 are the placeholder for the fields name so now i can create these overloaded method which i can use to any no of fields the output is list of school since i am using id which is primary key so its just one school object list so i can just do get(0) to get my school object and it will have all the other fields as null this is the best i could find please share your thought to improve it i would love to hear other solutions .

Resources