Create index in correct collection - spring-data

I annotate a document with #Index(unique = true) like so:
public class ADocumentWithUniqueIndex {
private static final long serialVersionUID = 1L;
#Indexed(unique = true)
private String iAmUnique;
public String getiAmUnique() {
return iAmUnique;
}
public void setiAmUnique(String iAmUnique) {
this.iAmUnique = iAmUnique;
}
}
When saving the object, I specify a custom collection:
MongoOperations mongoDb = ...
mongoDb.save(document, "MyCollection");
As a result I get:
A new document in "MyCollection"
An index in the collection "ADocumentWithUniqueIndex"
How can I create the index in "MyCollection" instead without having to explicitly specify it in the annotation?
BACKGROUND:
The default collection name is too ambiguous in our use case. We cannot guarantee, that there wouldn't be two documents with the same name but in different packages. So we added the package name to the collection.
Mapping a document to a collection is dealt with in an infrastructure component.
The implementation details like collection name etc. shouldn't leak into the individual documents.
I understand this is a bit of an "abstraction on top of an abstraction" smell but required since we had to support MongoDb and Windows Azure blob storage. Not anymore though...
This seemed like a fairly standard approach to hide the persistence details in a infrastructure component. Any comments on the approach appreciated as well.

It's kind of unusual to define the collection for an object to be stored and then expect the index annotations to work. There's a few options you have here:
Use #Document on ADocumentWithUniqueIndex and configure the collection name manually. This will cause all objects of that class to be persisted into that collection of course.
Manually create indexes via MongoOperations.indexOps() into the collections you'd like to use. This would be more consistent to your approach of manually determining the collection name during persistence operations.

Related

Simple Tagging Implementation with Spring Data JPA/Rest

I am trying to come up with a way of implementing tags for my entity that works well for me and need some help in the process. Let me write down some requirements I have in mind:
Firstly, I would like tags to show in entities as a list of strings like this:
{
"tags": ["foo", "bar"]
}
Secondly, I need to be able to retrieve a set of available tags across all entities so that users can easily choose from existing tags.
The 2nd requirement could be achieved by creating a Tag entity with the value of the Tag as the #Id. But that would make the tags property in my entity a relation that requires an extra GET operation to fetch. I could work with a getter method that resolves all the Tags and returns only a list of strings, but I see two disadvantages in that: 1. The representation as a list of strings suggests you could store tags by POSTing them in that way which is not the case. 2. The process of creating an entity requires to create all the Tags via a /tags endpoint first. That seem rather complicated for such a simple thing.
Also, I think I read somewhere that you shouldn't create a repository for an entity that isn't standalone. Would I create a Tag and only a Tag at any point in time? Nope.
I could store the tags as an #ElementCollection in my entity. In this case I don't know how to fulfill the 2nd requirement, though.
#ElementCollection
private Set<String> tags;
I made a simple test via EntityManager but it looks like I cannot query things that are not an #Entity in a result set.
#RestController
#RequestMapping("/tagList")
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class TagListController implements RepresentationModelProcessor<RepositoryLinksResource> {
#PersistenceContext
private final #NonNull EntityManager entityManager;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<EntityModel<TagList>> get() {
System.out.println(entityManager.createQuery("SELECT t.tags FROM Training t").getFirstResult());
EntityModel<TagList> model = EntityModel.of(new TagList(Set.of("foo", "bar")));
model.add(linkTo(methodOn(TagListController.class).get()).withSelfRel());
return ResponseEntity.ok(model);
}
}
org.hibernate.QueryException: not an entity
Does anyone know a smart way?
The representation as a list of strings suggests you could store tags by POSTing them in that way which is not the case
This is precisely the issue with using entities as REST resource representations. They work fine until it turns out the internal representation (entity) does not match the external representation (the missing DTO).
However, it would probably make most sense performance-wise to simply use an #ElementCollection like you mentioned, because you then don't have the double join with a join table for the many-to-many association (you could also use a one-to-many association where the parent entity and the tag value are both part of the #Id to avoid a join table, but I'm not sure it's convenient to work with. Probably better to just put a UNIQUE(parent_id, TAG) constraint on the collection table, if you need it). Regarding the not an entity error, you would need to use a native query. Assuming that you have #ElementCollection #CollectionTable(name = "TAGS") #Column(name = "TAG") on tags, then SELECT DISTINCT(TAG) FROM TAGS should do the job.
(as a side note, the DISTINCT part of the query will surely introduce some performance penalty, but I would assume the result of that query is a good candidate for caching)

Avoid multiple transactions within single execution when updating entities in MongoDB

I am working with 2 MongoDB collections, Recipe, and Menu. A single Menu is a combination of Recipe. Refer the below code segment for more information
#Document
public class Recipe {
private String id;
private String name;
private String description;
// getter and setter
}
#Document
public class Menu {
private String id;
private String name;
private List<RecipeItem> recipeItem;
// getter and setter
}
public class RecipeItem {
private String id;
private String name;
private String description;
// getter and setter
}
RecipeItem is just a copy of the Recipe object which is referred within the Menu collection
When the Menu collection is saved, you can add recipes to the menu and therefore a list of Recipe objects will also be saved within the Menu collection in the name of RecipeItem. When any of the Recipe is updated, the corresponding RecipeItem which is in the Menu is also required to be updated. Otherwise, the recipe within the Menu becomes outdated compared to the current Recipe after updating. So I have to iterate Menu collection which contains the updated Recipe by Id and needs to update the recipe information within the Menu collection.
So the update Menu function will initiate multiple transactions within the single execution and therefore we are in a need of a rollback mechanism as well. So I am not very fond of this approach.
I am new to MongoDB and I want to verify whether the current database design of Menu and Recipe is correct or incorrect? If yes what will be the optimal way of doing it? I know that use a DB ref between collections can be used, but there is a performance impact on it.
The Menu document should store a list of Recipe s IDs rather than the recipes themselves. Then you can dispense with RecipeItem and use Recipe directly.
It would seem more sensible that a Recipe consists of RecipeItems (Apple tart consists of flour, sugar, eggs, apples etc.).
In any case a reference would remove the need to keep two lists in sync.

Morphia : Using Transient

I have an object which has 3 fileds:
public class tags{
#Property("n")
private String name;
#Property("t")
private int type;
#Property("r")
private int rank;
.....
}
I am using morphia to communicate to my MongoDB.
I want to save al lthe fileds to the DB, but while retreiving I want to query only based on the 'name' and 'type' fields within my object. I have tried using the #Transient Annotation, but it completely ignores the field during load/save.
This is a very common use case.
The morphia wiki describes using filters or fluent interface: https://github.com/mongodb/morphia/wiki/Query#wiki-filter
Here's an example:
ds.createQuery(tags.class).field('name').equal('idbentley').field('type').equal(1);
If you are looking for limited query results https://github.com/mongodb/morphia/wiki/Query#wiki-ignoring-fields will cover that:
ds.createQuery(tags.class).retrievedFields(true, "name", "type").get();
Beware that you should only read these limited result sets or write back specific values. If you save them back, you will lose all the values you didn't retrieve.

Can I rename GORM's "version" field? (Grails 2.2 + GORM MongoDB)

I've got a domain object that already has a property called versions, so I'd like to give a different name to the built-in version property (used in GORM for optimistic locking). For instance, I'd like to call it updateCount instead.
Note that I do want the semantics of optimistic locking; I just want to give the field another name. Here's what I've naively tried (and it didn't work):
class Item {
ObjectId id
static hasMany = [versions: ItemVersion]
static mapping = {
table 'item'
version column: 'updateCount' // <-- This was my attempt
}
}
I would definitely appreciate any help in...
Determining whether this is possible, and
If so, making it work :-)
Thanks!
First thing first. MongoDB (NoSQL) deals with Documents and Collections instead of Table and rows.
Being said that, the domain class should look like:
class Item {
ObjectId id
String itemName
static hasMany = [versions: ItemVersion]
static mapping = {
//Collection in Mongodb is to Table in relational world
collection 'item'
//attr in Mongodb is to column in relational world
itemName attr: 'item_name'
//After spending some time investigating it was found that
//attr for version does not make any difference
//The below would not work for implicit GORM variable "version"
//default attribute name is the variable name.
//version attr: 'updateCount'
}
}
In case you want to configure default property across the domains to switch on/off the versioning then have a look at Global Mapping Configuration.

SpringData Mongodb MongoTemplate.findAndModify does not set "_class" values for embedded objects

I am using Spring Data Commons - 1.4.0.RC1 and Spring Data MongoDB - 1.1.0.RC1, having a problem with updating a list of embedded documents(of the same class hierarchy) using mongoTemplate.findAndModify(), which does not set the "_class" value as mongoTemplate.save() does, causing problems when reading data from mongodb as the application does not know which concrete class to instantiate for each embedded doc in that list.
For instance, I have a Student class, and a student has enrolled on subjects such as Maths, Physics, Computer Science, etc. This is not the domain I am working with, but should be enough to demonstrate the problem. So I will have a Student class mapped to a "student" collection and a list of subjects as embedded documents and all subjects extends the Subject super class, as below :
#Document
public class Student {
#Id
private String identifier;
private List<Subject> subjcts;
}
public abstract class Subject {
// common properties and methods;
}
public class Maths extends Subject {}
public class Physics extends Subject{}
To create a Student and save to database, I do
Student student = new Student();
student.setIdentifier(UUID.random().toString);
student.addSubject(new Maths());
student.addSubject(new Physics());
mongoTemplate.insert(student); or mongoTemplate.save(student);
The above will create a student with subjects maths and physics as embedded documents with an extra field "_class" specifying the concrete class so that it can be mapped to the correct class while fetching from the db.
However, if now i want to update the list of subjects, it is intuitive to do something like mongoTemplate.findAndModify(the query, new Update().set("subject", newSubjects), Student.class);
but findAndModify does not seem to go through the type mapper which sets the "_class" for the subjects, hence causes problems in reading back from db.
Is it a bug in Spring Data ? How do I work around it?
Any pointer will be appreciated.
Thank you.
This is a known issue of SpringData MongoDB (https://jira.springsource.org/browse/DATAMONGO-392).
For now the only workaround I found is to create a specific converter for the inner-document class.