Spring MVC + DataTables 1.10 Parameters binding - spring

I'm trying to do a controller that do the Server Side for DataTables.
#RequestMapping(value="/grid", method={RequestMethod.GET}, produces= MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public DataTablesResponse<MyObject> grid(DataTablesRequest dt) {
return service.getListOfMyObjects();
}
DataTablesRequest.class:
public class DataTablesRequest {
private int draw;
private int start;
private int length;
private Search search;
private List<Order> order;
private List<Column> columns;
//... Getters and setters
}
Order.class:
public class Order {
private String column;
private String dir;
//...getters and setters
}
Column.class
public class Column {
private String data;
private String name;
private boolean searchable;
private boolean orderable;
private Search search;
//...Getters and setters
}
Search.class:
public class Search {
private String value;
private boolean regex;
//...getters and setters
}
The problem is that DataTables sends parameters like: column[0][name] and SpringMVC is expecting something like column[0].name.
Is there any way to fix that? How can i bind the parameters of the DataTables to an object?

Kind of a late answer, but maybe this would help someone in the future:
In datatables settings, we need to tell jQuery to not process data and to send JSON:
ajax: {
url: "/some/url.json"),
type: "POST",
data: function (data) {
return JSON.stringify(data);
},
dataType: "json",
processData: false,
contentType: 'application/json;charset=UTF-8'
},
on the Spring side:
#RequestMapping(value = "/some/url")
#ResponseBody
public DataTablesResponse<Element> list(#RequestBody final DataTablesRequest dataTablesRequest) {
// query the DB to get the number of elements (without filtering)
// and the list of elements (filtered and/or paginated)
return new DataTablesResponse(numberOfElements, notifications.getTotalElements(), dataTablesRequest.getDraw(), "", listOfElements);
}
With the following for DataTablesRequest etc (#Data is from lombok, javadoc comments are from the official datatables doc.):
#Data
public class DataTablesRequest {
/**
* Draw counter. This is used by DataTables to ensure that the Ajax returns from server-side processing requests are drawn in sequence by DataTables
* (Ajax requests are asynchronous and thus can return out of sequence). This is used as part of the draw return parameter (see below).
*/
private int draw;
/**
* Paging first record indicator. This is the start point in the current data set (0 index based - i.e. 0 is the first record).
*/
private int start;
/**
* Number of records that the table can display in the current draw. It is expected that the number of records returned will be equal to this number, unless
* the server has fewer records to return. Note that this can be -1 to indicate that all records should be returned (although that negates any benefits of
* server-side processing!)
*/
private int length;
/**
* #see Search
*/
private Search search;
/**
* #see Order
*/
#JsonProperty("order")
private List<Order> orders;
/**
* #see Column
*/
private List<Column> columns;
}
#Data
private static class Search {
/**
* Global search value. To be applied to all columns which have searchable as true.
*/
private String value;
/**
* <code>true</code> if the global filter should be treated as a regular expression for advanced searching, false otherwise. Note that normally server-side
* processing scripts will not perform regular expression searching for performance reasons on large data sets, but it is technically possible and at the
* discretion of your script.
*/
private boolean regex;
}
#Data
private static class Order {
/**
* Column to which ordering should be applied. This is an index reference to the columns array of information that is also submitted to the server.
*/
private int column;
/**
* Ordering direction for this column. It will be <code>asc</code> or <code>desc</code> to indicate ascending ordering or descending ordering,
* respectively.
*/
private String dir;
}
#Data
private static class Column {
/**
* Column's data source, as defined by columns.data.
*/
private String data;
/**
* Column's name, as defined by columns.name.
*/
private String name;
/**
* Flag to indicate if this column is searchable (true) or not (false). This is controlled by columns.searchable.
*/
private boolean searchable;
/**
* Flag to indicate if this column is orderable (true) or not (false). This is controlled by columns.orderable.
*/
private boolean orderable;
/**
* Search value to apply to this specific column.
*/
private Search search;
/**
* Flag to indicate if the search term for this column should be treated as regular expression (true) or not (false). As with global search, normally
* server-side processing scripts will not perform regular expression searching for performance reasons on large data sets, but it is technically possible
* and at the discretion of your script.
*/
private boolean regex;
}
#Data
public class DataTablesResponse<T> {
/**
* The draw counter that this object is a response to - from the draw parameter sent as part of the data request. Note that it is strongly recommended for
* security reasons that you cast this parameter to an integer, rather than simply echoing back to the client what it sent in the draw parameter, in order
* to prevent Cross Site Scripting (XSS) attacks.
*/
private int draw;
/**
* Total records, before filtering (i.e. the total number of records in the database)
* <p/>
* (NB: I changed this to long)
*/
private long recordsTotal;
/**
* Total records, after filtering (i.e. the total number of records after filtering has been applied - not just the number of records being returned for this
* page of data).
* <p/>
* (NB: I changed this to long)
*/
private long recordsFiltered;
/**
* Optional: If an error occurs during the running of the server-side processing script, you can inform the user of this error by passing back the error message
* to be displayed using this parameter. Do not include if there is no error.
*/
private String error;
/**
* The data to be displayed in the table. This is an array of data source objects, one for each row, which will be used by DataTables. Note that this parameter's
* name can be changed using the ajax option's dataSrc property.
*/
private List<T> data = Lists.newArrayList();
}

How about to implement HandlerMethodArgumentResolver, please refer to https://github.com/rakurakupg/springmvc-jquerydatatables-example

I had similar issue and below worked for me.
Try adding #JsonProperty to each property in every parameter binding class e.g. #JsonProperty(value = "draw"). This way Jackson maps it fine with the JSON sent from DataTable. In my case, I was getting below JSON
{
"draw":1,
"columns":[
{"data":0,"name":"","searchable":true,"orderable":false,"search":{"value":"","regex":false}},
{"data":1,"name":"","searchable":true,"orderable":false,"search":{"value":"","regex":false}}],
"order":[],
"start":0,
"length":-1,
"search":{"value":"","regex":false}
}
Also there is a need to have the empty constructor defined for DataTablesRequest, Column,Order and Search.
If you do not want to have the default constructor, you can add it like below,
public Search(#JsonProperty("value") String value,#JsonProperty("regex") String regex) {
// TODO Auto-generated constructor stub
}
Hope this helps!

The solution to this for me was to send the request as JSON by following the instructions here: ASP.net jQuery DataTables
Essentially just change the JS on your page to something like this:
'ajax': {
'url': 'serverSideTableProviderPage',
'type': 'POST',
'data':function(data)
{
return data = JSON.stringify(data);
}
},

Related

How to give ttl in Cassandra when inserting data in batches?

Hello I am using Cassandra to save user data . I want to store data of a user for only 24 hours so I am giving a ttl for 24 hours. For each user there are multiple entries. So I want to batch insert data for each user instead of multiple calls to data base . I am using Cassandra operations to give ttl . I am able to give ttl for single record . How to provide ttl when inserting data in batches
public class CustomizedUserFeedRepositoryImpl<T> implements CustomizedUserFeedRepository<T> {
private CassandraOperations cassandraOperations;
#Autowired
CustomizedUserFeedRepositoryImpl(CassandraOperations cassandraOperations){
this.cassandraOperations = cassandraOperations;
}
#Override
public <S extends T> S save(S entity, int ttl){
InsertOptions insertOptions;
if(ttl == 0) {
insertOptions = InsertOptions.builder().ttl(Duration.ofHours(24)).build();
} else {
insertOptions = InsertOptions.builder().ttl(ttl).build();
}
cassandraOperations.insert(entity,insertOptions);
return entity;
}
#Override
public void saveAllWithTtl(java.lang.Iterable<T> entities, int ttl){
entities.forEach(entity->{
save(entity,ttl);
});
}
}
As you can see I have to iterate over the list make and make database calls for each record . The batch operation cassandraOperations.batchOps().insert() only takes list of objects . How to set ttl for each record when using batchops() fucntion ?
/**
* Add a collection of inserts with given {#link WriteOptions} to the batch.
*
* #param entities the entities to insert; must not be {#literal null}.
* #param options the WriteOptions to apply; must not be {#literal null}.
* #return {#code this} {#link CassandraBatchOperations}.
* #throws IllegalStateException if the batch was already executed.
* #since 2.0
*/
CassandraBatchOperations insert(Iterable<?> entities, WriteOptions options);
You can use insert(Iterable<?> entities, WriteOptions options) method
#EqualsAndHashCode(callSuper = true)
public class WriteOptions extends QueryOptions {
private static final WriteOptions EMPTY = new WriteOptionsBuilder().build();
private final Duration ttl;
private final #Nullable Long timestamp;
batchOperations.insert(entity, WriteOptions.builder().ttl(20).build());

How can I save a SparseArray in a Room database?

I'm trying to persist a SparseArray in a Room database and can not get it to compile. I keep getting the "Not sure how to convert a Cursor to this method's return type" error message along with "The query returns some columns [plannerLineData] which are not use by android.util.SparseArray."
I have tried using a single field in the PlannerLine Entity alone with a separate PlannerLineData class.
I have data converters to convert SparseArray to String and to convert String back to SparseArray.
I have checked several questions on stackoverflow and have successfully used the Date to Long and the Long to Date converters in other projects, but I seem to be missing something somewhere.
Data Files:
#Entity
public class PlannerLine implements Serializable {
private static final long serialVersionUID = 1L;
#TypeConverters(Converters.class)
#PrimaryKey
#SerializedName("planner_line")
#NonNull
public SparseArray plannerLineData;
public SparseArray getPlannerLineData() {
return plannerLineData;
}
public void setPlannerLineData(SparseArray plannerLineData) {
this.plannerLineData = plannerLineData;
}
public class PlannerLineData implements Serializable {
#SerializedName("lineId")
public int lineId;
#SerializedName("plan_text")
public String planText;
public int getLineId() {
return lineId;
}
public void setLineId(int lineId) {
this.lineId = lineId;
}
public String getPlanText() {
return planText;
}
public void setPlanText(String planText) {
this.planText = planText;
}
}
DAO problem area:
#Dao
public interface PlannerDao {
#Query("SELECT * from PlannerLine")
public SparseArray getPlannerLine(); <---Doesn't like this line
I have also tried returning SparseArray<PlannerLine> and SparseArray<PlannerLineData>, but no joy.
Converters class:
public class Converters {
#TypeConverter
public static String sparseArrayToString(SparseArray sparseArray) {
if (sparseArray == null) {
return null;
}
int size = sparseArray.size();
if (size <= 0) {
return "{}";
}
StringBuilder buffer = new StringBuilder(size * 28);
buffer.append('{');
for (int i = 0; i < size; i++) {
if (i > 0) {
buffer.append("-,- ");
}
int key = sparseArray.keyAt(i);
buffer.append(key);
buffer.append("-=-");
Object value = sparseArray.valueAt(i);
buffer.append(value);
}
buffer.append('}');
return buffer.toString();
}
#TypeConverter
public static SparseArray stringToSparseArray(String string) {
if (string == null) {
return null;
}
String entrySeparator = "-=-";
String elementSeparator = "-,-";
SparseArray sparseArray = new SparseArray();
String[] entries = StringUtils.splitByWholeSeparator(string, elementSeparator);
for (int i = 0; i < entries.length; i++) {
String[] parts = StringUtils.splitByWholeSeparator(entries[i], entrySeparator);
int key = Integer.parseInt(parts[0]);
String text = parts[1];
sparseArray.append(key, text);
}
return sparseArray;
}
Suggestions would be appreciated. Thanks
Edit:
My original vision for this app was to store all the plan lines in a single SparseArray, along with two additional SparseIntArrays (which I did not mention before because the solution would be similar to the SparseArray) to hold info on how the plan lines interact with each other.
After reading through #dglozano's helpful responses, I have decided to re-design the app to just store regular DB files in Room and load the data into the SparseArray (and the two SparseIntArrays) at startup, use only the in memory SparseArray and SparseIntArrays while the app is active, then write changes in the Sparse Arrays to the DB during onStop(). I am also considering updating the DB in the background as I work through app.
Because the answers and suggestions provided by #dglozano led me to the re-design decision, I am accepting his answer as the solution.
Thanks for the help.
It seems that you are doing the Conversion properly. However, the problem is in your DAO Query:
#Query("SELECT * from PlannerLine") // This returns a List of PlannerLine, not a SparseArray
public SparseArray getPlannerLine(); // The return type is SparseArray, not a List of PlannerLine
Therefore, you can try two different things:
1 - Change the Query to #Query("SELECT plannerLineData FROM PlannerLine WHERE lineId == :lineId") , so that the query returns the SparseArray inside the PlannerLine with id lineId. You should change the method signature so it accepts the parameter lineId
#Query("SELECT plannerLineData FROM PlannerLine WHERE lineId == :lineId")
public SparseArray getPlannerLine(int lineId);
2 - If you want to return the full PlannerLine object and then access to its SparseArray field, then you should change the return type. You should also add the lineId parameter to return just one record and not a list of all the PlannerLine stored in the database table.
#Query("SELECT * FROM PlannerLine WHERE lineId == :lineId")
public PlannerLine getPlannerLine(int lineId);
UPDATE
If you want to get a List<PlannerLine> with all the PlannerLine stored in the database, use the following query in your Dao.
#Query("SELECT * FROM PlannerLine")
public List<PlannerLine> getAllPlannerLines();
Then you can access to the SparseArray of each PlannerLine in the list as usual.

Java, Hibernate -- Can't retrieve id after persisting entity with separate embedded key class

I am working on a sample Springboot server application and using hibernate for JPA. I am using a generic repository pattern that performs all the CRUD operations on my entity. I am following this example :
http://www.concretepage.com/spring-boot/spring-boot-rest-jpa-hibernate-mysql-example that I came across. (My idea to have a Generic repository was to have a similar implementation for all CRUD operations, than explicitly stating one in each Service/DAO or repository implementation for each Entity) In the above example the #ID attribute is in the same class as the Entity. As a result of that I was able to persist an entity and the id would be reflected in the object after entityManager.persist(object)
In my code I have the Key class separate and it is referenced in the Entity class. On calling persist on EntityManager, a row is created in the database (since the column for the primary key is set to auto-increment in the database), but that same ID isn't reflected in the object after calling persist(). At all times my ID attribute within the key class is set to 0 that is the default int value. I would like to know if there is a way that I could fetch the ID of the inserted object either through Session or EntityManager. Also is there any alternate strategy to going about this problem without having the include the primary key in the Entity class itself. (As of now, I have looked at multiple posts on SO but haven't been able to get to a solution to my problem.)
Entity class
#Entity
#Table(name = "articles")
public class SampleArticle extends AbstractDomainObject {
/** The serialVersionUID. */
private static final long serialVersionUID = 7072648542528280535L;
/** Uniquely identifies the article. */
#EmbeddedId
#AttributeOverride(name = "articleId", column = #Column(name = "article_id"))
#GeneratedValue(strategy = GenerationType.IDENTITY)
//#GeneratedValue(strategy = GenerationType.AUTO)
private SampleArticleKey key;
/** Indicates the title. */
#Column(name = "title")
private String title;
Key class
#Embeddable
public class SampleArticleKey extends AbstractDomainKey {
/**
* Serial version id.
*/
private static final long serialVersionUID = 1325990987094850016L;
/** The article id. */
private int articleId;
Repository class
#Repository
#Transactional
public class SampleArticleRepository extends
AbstractRepository<SampleArticle, SampleArticleKey> implements
ISampleArticleRepository<SampleArticle, SampleArticleKey> {
/*
* (non-Javadoc)
* #see
* com.wpi.server.entity.repository.ISampleArticleRepository#addArticle
* (java.lang.Object)
*/
#Override
public SampleArticle create(SampleArticle article) throws Exception {
return super.create(article);
}
Abstract Repository
#Transactional
public abstract class AbstractRepository<T extends AbstractDomainObject, K
extends AbstractDomainKey> {
/** The entity manager. */
#PersistenceContext
private EntityManager entityManager;
/** The Constant LOGGER. */
private static final Logger LOGGER = Logger.getLogger(AbstractRepository.class.getName());
/**
* Persist the given object at persistence storage.
*
* #param object
* The object extending {#link AbstractDomainObject} which needs
* to be inserted.
* #return object of type {#link AbstractDomainObject} with the newly
* generated id.
* #throws Exception
* If unable to insert data.
*/
public T create(T object) throws Exception {
final Session session = entityManager.unwrap(Session.class);
session.getTransaction().begin();
session.save(object);
session.flush();
session.getTransaction().commit();
session.close();
LOGGER.fine("Entity CREATED successfully.");
return object;
};
Let me give you a working embeddable key example. It might help.
First overwrite equals() and hashCode() methods so that Hibernate proper identifies objects in the cash.
Now you can persist objects
Let me know if this helps or you have other issues with this.

Eagerly fetch child when loading parent spring data jpa

Link.java
#Entity
#Table(name = "LINK")
#AttributeOverride(name="id", column=#Column(name="LINK_ID"))
public class Link extends AbstractAuditableEntity<Integer> {
/**
*
*/
private static final long serialVersionUID = 3825555385014396995L;
#Column(name="NAME")
private String name;
#Column(name="UI_SREF")
private String uiSref;
#ManyToOne
#JoinColumn(name="PARENT_LINK_ID")
private Link parentLink;
#OneToMany(mappedBy="parentLink", fetch = FetchType.EAGER)
private List<Link> childLinks;
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* #return the uiSref
*/
public String getUiSref() {
return uiSref;
}
/**
* #param uiSref the uiSref to set
*/
public void setUiSref(String uiSref) {
this.uiSref = uiSref;
}
/**
* #return the parentLink
*/
public Link getParentLink() {
return parentLink;
}
/**
* #param parentLink the parentLink to set
*/
public void setParentLink(Link parentLink) {
this.parentLink = parentLink;
}
/**
* #return the childLinks
*/
public List<Link> getChildLinks() {
return childLinks;
}
/**
* #param childLinks the childLinks to set
*/
public void setChildLinks(List<Link> childLinks) {
this.childLinks = childLinks;
}
}
LinkRepository .java
public interface LinkRepository extends BaseRepository<Integer, Link> {
#Query("select distinct p from Link l JOIN fetch l.parentLink p where l.id in (select lar.link.id from LinkAccessRole lar where lar.accessRoleLu in ?1) and p.id in (select lar.link.id from LinkAccessRole lar where lar.accessRoleLu in ?1)")
public List<Link> getNavigationByaccessRoleLuList(List<AccessRoleLu> accessRoleLu);
}
Link_Table
Link_Access_Role Table
generated Queries:
SELECT DISTINCT t0.LINK_ID, t0.CREATED_BY_ID, t0.CREATED_DATE, t0.LAST_MODIFIED_BY_ID, t0.LAST_MODIFIED_DATE, t0.NAME, t0.UI_SREF, t0.PARENT_LINK_ID FROM LINK t0, LINK t1 WHERE ((t1.LINK_ID IN (SELECT t2.LINK_ID FROM LINK_ACCESS_ROLE t3, LINK t2 WHERE ((t3.ACCESS_ROLE_ID IN (?,?)) AND (t2.LINK_ID = t3.LINK_ID))) AND t0.LINK_ID IN (SELECT t4.LINK_ID FROM LINK_ACCESS_ROLE t5, LINK t4 WHERE ((t5.ACCESS_ROLE_ID IN (?,?)) AND (t4.LINK_ID = t5.LINK_ID)))) AND (t0.LINK_ID = t1.PARENT_LINK_ID))
bind => [4 parameters bound]
SELECT LINK_ID, CREATED_BY_ID, CREATED_DATE, LAST_MODIFIED_BY_ID, LAST_MODIFIED_DATE, NAME, UI_SREF, PARENT_LINK_ID FROM LINK WHERE (PARENT_LINK_ID = ?)
bind => [1 parameter bound]
SELECT LINK_ID, CREATED_BY_ID, CREATED_DATE, LAST_MODIFIED_BY_ID, LAST_MODIFIED_DATE, NAME, UI_SREF, PARENT_LINK_ID FROM LINK WHERE (PARENT_LINK_ID = ?)
bind => [1 parameter bound]
I get one query for each child related to the fetched parent Regardless it has the access role or not.
i want to fetch the parents and its childs that have access role not all childs that related to that parent.
The only way that you can fetch a parent entity and have one of its collections populated with a subset of entries based on some criteria is by using Hibernate's proprietary filters.
I'm not certain whether the other JPA providers provide some proprietary solution either, but JPA itself doesn't offer this directly.
You first need to register a filter definition using #FilterDef and then you need to reference the filter's definition using the #Filter on your collection property.
The hard part here is that you can't rely on Spring data's #Query or their repository implementation generation process to help. You will need to use a real implementation so that you can manually enable this hibernate filter before you query the parent entity.
Filter filter = session.enableFilter( "link-with-restrictions-by-roles" );
filter.setParameter( "roles", yourRolesList );
return session.createQuery( ... ).getResultList();
The documentation describes the use of #Filter and #FilterDef in detail. You can also find another post of mine where I give slightly more implementation details here.

Spring Data MongoDB: Accessing and updating sub documents

First experiments with Spring Data and MongoDB were great. Now I've got the following structure (simplified):
public class Letter {
#Id
private String id;
private List<Section> sections;
}
public class Section {
private String id;
private String content;
}
Loading and saving entire Letter objects/documents works like a charm. (I use ObjectId to generate unique IDs for the Section.id field.)
Letter letter1 = mongoTemplate.findById(id, Letter.class)
mongoTemplate.insert(letter2);
mongoTemplate.save(letter3);
As documents are big (200K) and sometimes only sub-parts are needed by the application: Is there a possibility to query for a sub-document (section), modify and save it?
I'd like to implement a method like
Section s = findLetterSection(letterId, sectionId);
s.setText("blubb");
replaceLetterSection(letterId, sectionId, s);
And of course methods like:
addLetterSection(letterId, s); // add after last section
insertLetterSection(letterId, sectionId, s); // insert before given section
deleteLetterSection(letterId, sectionId); // delete given section
I see that the last three methods are somewhat "strange", i.e. loading the entire document, modifying the collection and saving it again may be the better approach from an object-oriented point of view; but the first use case ("navigating" to a sub-document/sub-object and working in the scope of this object) seems natural.
I think MongoDB can update sub-documents, but can SpringData be used for object mapping? Thanks for any pointers.
I figured out the following approach for slicing and loading only one subobject. Does it seem ok? I am aware of problems with concurrent modifications.
Query query1 = Query.query(Criteria.where("_id").is(instance));
query1.fields().include("sections._id");
LetterInstance letter1 = mongoTemplate.findOne(query1, LetterInstance.class);
LetterSection emptySection = letter1.findSectionById(sectionId);
int index = letter1.getSections().indexOf(emptySection);
Query query2 = Query.query(Criteria.where("_id").is(instance));
query2.fields().include("sections").slice("sections", index, 1);
LetterInstance letter2 = mongoTemplate.findOne(query2, LetterInstance.class);
LetterSection section = letter2.getSections().get(0);
This is an alternative solution loading all sections, but omitting the other (large) fields.
Query query = Query.query(Criteria.where("_id").is(instance));
query.fields().include("sections");
LetterInstance letter = mongoTemplate.findOne(query, LetterInstance.class);
LetterSection section = letter.findSectionById(sectionId);
This is the code I use for storing only a single collection element:
MongoConverter converter = mongoTemplate.getConverter();
DBObject newSectionRec = (DBObject)converter.convertToMongoType(newSection);
Query query = Query.query(Criteria.where("_id").is(instance).and("sections._id").is(new ObjectId(newSection.getSectionId())));
Update update = new Update().set("sections.$", newSectionRec);
mongoTemplate.updateFirst(query, update, LetterInstance.class);
It is nice to see how Spring Data can be used with "partial results" from MongoDB.
Any comments highly appreciated!
I think Matthias Wuttke's answer is great, for anyone looking for a generic version of his answer see code below:
#Service
public class MongoUtils {
#Autowired
private MongoTemplate mongo;
public <D, N extends Domain> N findNestedDocument(Class<D> docClass, String collectionName, UUID outerId, UUID innerId,
Function<D, List<N>> collectionGetter) {
// get index of subdocument in array
Query query = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query.fields().include(collectionName + "._id");
D obj = mongo.findOne(query, docClass);
if (obj == null) {
return null;
}
List<UUID> itemIds = collectionGetter.apply(obj).stream().map(N::getId).collect(Collectors.toList());
int index = itemIds.indexOf(innerId);
if (index == -1) {
return null;
}
// retrieve subdocument at index using slice operator
Query query2 = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query2.fields().include(collectionName).slice(collectionName, index, 1);
D obj2 = mongo.findOne(query2, docClass);
if (obj2 == null) {
return null;
}
return collectionGetter.apply(obj2).get(0);
}
public void removeNestedDocument(UUID outerId, UUID innerId, String collectionName, Class<?> outerClass) {
Update update = new Update();
update.pull(collectionName, new Query(Criteria.where("_id").is(innerId)));
mongo.updateFirst(new Query(Criteria.where("_id").is(outerId)), update, outerClass);
}
}
This could for example be called using
mongoUtils.findNestedDocument(Shop.class, "items", shopId, itemId, Shop::getItems);
mongoUtils.removeNestedDocument(shopId, itemId, "items", Shop.class);
The Domain interface looks like this:
public interface Domain {
UUID getId();
}
Notice: If the nested document's constructor contains elements with primitive datatype, it is important for the nested document to have a default (empty) constructor, which may be protected, in order for the class to be instantiatable with null arguments.
Solution
Thats my solution for this problem:
The object should be updated
#Getter
#Setter
#Document(collection = "projectchild")
public class ProjectChild {
#Id
private String _id;
private String name;
private String code;
#Field("desc")
private String description;
private String startDate;
private String endDate;
#Field("cost")
private long estimatedCost;
private List<String> countryList;
private List<Task> tasks;
#Version
private Long version;
}
Coding the Solution
public Mono<ProjectChild> UpdateCritTemplChild(
String id, String idch, String ownername) {
Query query = new Query();
query.addCriteria(Criteria.where("_id")
.is(id)); // find the parent
query.addCriteria(Criteria.where("tasks._id")
.is(idch)); // find the child which will be changed
Update update = new Update();
update.set("tasks.$.ownername", ownername); // change the field inside the child that must be updated
return template
// findAndModify:
// Find/modify/get the "new object" from a single operation.
.findAndModify(
query, update,
new FindAndModifyOptions().returnNew(true), ProjectChild.class
)
;
}

Resources