DAO class does not recognize the table name and id - android-room

I'm trying to save my images using room database.
Saving is not working. I guess my DAO class does not recognize the table name and id. Can you review the following pictures and help me to solve this issue?
#Entity(tableName = "my_images")
public class MyImagesEntity {
#PrimaryKey(autoGenerate = true)
public int image_id;
public String image_title;
public String image_description;
//images/sounds are stored in database as binary large object
public byte[] image;
//constructor
public MyImagesEntity(String image_title, String image_description, byte[] image) {
this.image_title = image_title;
this.image_description = image_description;
this.image = image;
}
}
The #Dao annotated interface
#Dao
public interface MyImagesDao {
#Insert
void insert(MyImagesEntity myImagesEntity);
#Delete
void delete(MyImagesEntity myImagesEntity);
#Update
void update(MyImagesEntity myImagesEntity);
#Query("SELECT * FROM my_images ORDER BY image_id ASC")
LiveData<List<MyImagesEntity>> getAllImages();
}
The dependencies
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
def lifecycle_version = "2.5.0-alpha03"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
// Annotation processor
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"

Saving is not working.
Using the code you have supplied, I don't believe that your issue is that data is not being saved as using the following additional code :-
#Database(entities = {Data.class/*<<<<< due to test using a hijacked project*/,MyImagesEntity.class}, exportSchema = false, version = 1)
abstract class AppDatabase extends RoomDatabase {
abstract DataDao getDataDao(); //<<<<< due to test using a hijacked project
abstract MyImagesDao getMyImagesDao();
private static volatile AppDatabase instance = null;
static AppDatabase getAppDatabase(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context, AppDatabase.class,"the_database.db")
.allowMainThreadQueries()
.build();
}
return instance;
}
}
and the following in an activity:-
db = AppDatabase.getAppDatabase(this);
myImagesDao = db.getMyImagesDao();
myImagesDao.insert(new MyImagesEntity("Image001","Image 1 description",new byte[]{10,11,12,13,14,15,16,17}));
myImagesDao.insert(new MyImagesEntity("Image002","Image 2 description",new byte[]{0,1,2,3,4,5,6,7,8,9,}));
for(MyImagesEntity mie: myImagesDao.getAllImagesOther()) {
Log.d("DBINFO","Image is " + mie.image_description + " Description is " + mie.image_description);
}
with getAllImagesOther being (without LiveData) :-
#Query("SELECT * FROM my_images ")
List<MyImagesEntity> getAllImagesOther();
Then the result is :-
D/DBINFO: Image is Image 1 description Description is Image 1 description
D/DBINFO: Image is Image 2 description Description is Image 2 description
and using App Inspection you can see that the data has been added :-
Therefore I believe that you issues is with how your are observing (or not) the LiveData. I'd suggest using App Inspection to see if the data is being saved.

Related

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.

Spring Boot Neo4J has no property with propertyKey="__type__"

Am using spring boot 1.2.3.RELEAE and spring-data-neo4j 3.2.2.RELEASE which uses Neo4J 2.1.5.
I am trying to build a graph of Stations which are connected to other stations via "CONNECTED_TO" relationship. The relationship has "distance" as a property. Later on we are planning to do dijkstra's algorithm on the graph. But anyway... this is what we have:
#NodeEntity
#TypeAlias("Station")
public class Station {
#GraphId
private Long id;
#Indexed
public String name;
#Indexed(unique = true)
public String tlc;
public double gps_x;
public double gps_y;
#RelatedTo(type = "CONNECTED_TO", direction = OUTGOING)
public Set<Station> connectedTo = new HashSet<>();
#Fetch
#RelatedToVia(type = "CONNECTED_TO", direction = OUTGOING)
public Set<ConnectedTo> connections = new HashSet<>();
// getter + setters
}
#RelationshipEntity(type = "CONNECTED_TO")
public class ConnectedTo {
#GraphId
private Long id;
#Fetch
#StartNode
private Station fromStation;
#Fetch
#EndNode
private Station toStation;
private double distance;
// getter + setters
}
And have a stations.csv with 2K plus stations... here is a sample:
name,tlc,gps_y,gps_x
Alexandra Palace,AAP,-0.120235219,51.59792231
Achanalt,AAT,-4.913851155,57.60959445
Aberdare,ABA,-3.443083402,51.71505622
Altnabreac,ABC,-3.706280166,58.38814697
And then for the relations ships (5K plus) we have station_connections.csv.. here is a sample:
name,from_tlc,to_tlc,distance
Alexandra Palace,AAP,BOP,0.7
Alexandra Palace,AAP,HRN,0.9
Alexandra Palace,AAP,NSG,1.5
Achanalt,AAT,ACN,6.5
Achanalt,AAT,LCC,4.2
Aberdare,ABA,CMH,0.8
Altnabreac,ABC,FRS,8.1
Altnabreac,ABC,SCT,9.1
Then I have a import service to import the CSVs
Firstly, I import the stations from stations.csv. This works fine. This is the code to import it:
#Transactional
public void importStations(CsvReader stationsFile) throws IOException {
// id,terminalName,name,installed,locked,temporary,lat,lng
while (stationsFile.readRecord()) {
Station station = new Station()
.setName(stationsFile.get("name").toUpperCase())
.setTlc(stationsFile.get("tlc").toUpperCase())
.setGps_y(asDouble(stationsFile.get("gps_y")))
.setGps_x(asDouble(stationsFile.get("gps_x")));
stationRepository.save(station);
}
}
Secondly, I want to import station connections from station_connections.csv. using the following code:
#Transactional
public void importConnections(CsvReader stationsFile) throws IOException {
// name,from_tlc,to_tlc,distance
while (stationsFile.readRecord()) {
String from_tlc = stationsFile.get("from_tlc").toUpperCase();
String to_tlc = stationsFile.get("to_tlc").toUpperCase();
String distance = stationsFile.get("distance");
Station fromStation = stationRepository.findByTlc(from_tlc);
Station toStation = stationRepository.findByTlc(to_tlc);
if (fromStation != null && toStation != null) {
// need to do this get the connected stations...!!!
template.fetch(fromStation.getConnectedTo());
template.fetch(toStation.getConnectedTo());
fromStation.addStation(toStation);
template.save(fromStation);
System.out.println(from_tlc + " connected to: " + to_tlc);
}
}
}
So when it tries to import the connections I get the following error: RELATIONSHIP[4434] has no property with propertyKey="__type__".
Exception in thread "main" org.neo4j.graphdb.NotFoundException: RELATIONSHIP[4434] has no property with propertyKey="__type__".
at org.neo4j.kernel.impl.core.RelationshipProxy.getProperty(RelationshipProxy.java:195)
at org.springframework.data.neo4j.support.typerepresentation.AbstractIndexBasedTypeRepresentationStrategy.readAliasFrom(AbstractIndexBasedTypeRepresentationStrategy.java:126)
at org.springframework.data.neo4j.support.mapping.TRSTypeAliasAccessor.readAliasFrom(TRSTypeAliasAccessor.java:36)
at
So I basically I am baffled at this error and would appreciate some help.
If there is a better way of doing this please do let me know.
GM
Can you check Relationships with id 4434 ? If it really doesn't have that property, and what kind of relationship it is.
It means that SDN couldn't load a relationship mapped to a Java type because somehow the type-information was not stored on it.
You can do that after the fact with template.postEntityCreation(rel, ConnectedTo.class);

Dynamic Queries in Spring Data JPA

I am looking for a solution to dynamically build queries using Spring Data JPA. I have a GameController which has a RESTful service endpoint /games which takes 4 optional parameters: genre, platform, year, title. The API may be passed none of those, all 4, and every combination in between. If any parameter is not passed it defaults to null. I need a method in the Repository that will build the appropriate query and ideally also still allow Spring Data JPA Paging, although I'm not sure if that is possible.
I found this article but this doesn't seem to be what I need unless I am misunderstanding. http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
I know JPA has a Query Criteria API but really have no idea how to implement this.
I realize I could create a method for each possible scenario but that seems like really bad practice and a lot of unnecessary code.
GameRepository:
package net.jkratz.igdb.repository;
import net.jkratz.igdb.model.Game;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface GameRepository extends JpaRepository<Game, Long> {
#Query("select g from Game g, GamePlatformMap gpm, Platform p where g = gpm.game and gpm.platform = p and p.id = :platform")
Page<Game> getGamesByPlatform(#Param("platform") Long platformId, Pageable pageable);
#Query("select g from Game g where g.title like :title")
Page<Game> getGamesByTitle(#Param("title") String title, Pageable pageable);
#Query("select g from Game g, GameGenreMap ggm, Genre ge where g = ggm.game and ggm.genre = ge and ge.id = :genreId")
Page<Game> getGamesByGenre(#Param("genre") Long genreId, Pageable pageable);
}
I would say that using QueryDSL is one way of doing what you want.
For example I have a repository defined as below:
public interface UserRepository extends PagingAndSortingRepository<User, Long>, QueryDslPredicateExecutor<User> {
public Page<User> findAll(Predicate predicate, Pageable p);
}
I can call this method with any combination of parameters, like below:
public class UserRepositoryTest{
#Autowired
private UserRepository userRepository;
#Test
public void testFindByGender() {
List<User> users = userRepository.findAll(QUser.user.gender.eq(Gender.M));
Assert.assertEquals(4, users.size());
users = userRepository.findAll(QUser.user.gender.eq(Gender.F));
Assert.assertEquals(2, users.size());
}
#Test
public void testFindByCity() {
List<User> users = userRepository.findAll(QUser.user.address.town.eq("Edinburgh"));
Assert.assertEquals(2, users.size());
users = userRepository.findAll(QUser.user.address.town.eq("Stirling"));
Assert.assertEquals(1, users.size());
}
#Test
public void testFindByGenderAndCity() {
List<User> users = userRepository.findAll(QUser.user.address.town.eq("Glasgow").and(QUser.user.gender.eq(Gender.M)));
Assert.assertEquals(2, users.size());
users = userRepository.findAll(QUser.user.address.town.eq("Glasgow").and(QUser.user.gender.eq(Gender.F)));
Assert.assertEquals(1, users.size());
}
}
For those using Kotlin (and Spring Data JPA), we've just open-sourced a Kotlin JPA Specification DSL library which lets you create type-safe dynamic queries for a JPA Repository.
It uses Spring Data's JpaSpecificationExecutor (i.e. JPA criteria queries), but without the need for any boilerplate or generated metamodel.
The readme has more details on how it works internally, but here's the relevant code examples for a quick intro.
import au.com.console.jpaspecificationsdsl.* // 1. Import Kotlin magic
////
// 2. Declare JPA Entities
#Entity
data class TvShow(
#Id
#GeneratedValue
val id: Int = 0,
val name: String = "",
val synopsis: String = "",
val availableOnNetflix: Boolean = false,
val releaseDate: String? = null,
#OneToMany(cascade = arrayOf(javax.persistence.CascadeType.ALL))
val starRatings: Set<StarRating> = emptySet())
#Entity
data class StarRating(
#Id
#GeneratedValue
val id: Int = 0,
val stars: Int = 0)
////
// 3. Declare JPA Repository with JpaSpecificationExecutor
#Repository
interface TvShowRepository : CrudRepository<TvShow, Int>, JpaSpecificationExecutor<TvShow>
////
// 4. Kotlin Properties are now usable to create fluent specifications
#Service
class MyService #Inject constructor(val tvShowRepo: TvShowRepository) {
fun findShowsReleasedIn2010NotOnNetflix(): List<TvShow> {
return tvShowRepo.findAll(TvShow::availableOnNetflix.isFalse() and TvShow::releaseDate.equal("2010"))
}
/* Fall back to spring API with some extra helpers for more complex join queries */
fun findShowsWithComplexQuery(): List<TvShow> {
return tvShowRepo.findAll(where { equal(it.join(TvShow::starRatings).get(StarRating::stars), 2) })
}
}
For more complex and dynamic queries it's good practice to create functions that use the DSL to make queries more readable (as you would for QueryDSL), and to allow for their composition in complex dynamic queries.
fun hasName(name: String?): Specifications<TvShow>? = name?.let {
TvShow::name.equal(it)
}
fun availableOnNetflix(available: Boolean?): Specifications<TvShow>? = available?.let {
TvShow::availableOnNetflix.equal(it)
}
fun hasKeywordIn(keywords: List<String>?): Specifications<TvShow>? = keywords?.let {
or(keywords.map { hasKeyword(it) })
}
fun hasKeyword(keyword: String?): Specifications<TvShow>? = keyword?.let {
TvShow::synopsis.like("%$keyword%")
}
These functions can be combined with and() and or() for complex nested queries:
val shows = tvShowRepo.findAll(
or(
and(
availableOnNetflix(false),
hasKeywordIn(listOf("Jimmy"))
),
and(
availableOnNetflix(true),
or(
hasKeyword("killer"),
hasKeyword("monster")
)
)
)
)
Or they can be combined with a service-layer query DTO and mapping extension function
/**
* A TV show query DTO - typically used at the service layer.
*/
data class TvShowQuery(
val name: String? = null,
val availableOnNetflix: Boolean? = null,
val keywords: List<String> = listOf()
)
/**
* A single TvShowQuery is equivalent to an AND of all supplied criteria.
* Note: any criteria that is null will be ignored (not included in the query).
*/
fun TvShowQuery.toSpecification(): Specifications<TvShow> = and(
hasName(name),
availableOnNetflix(availableOnNetflix),
hasKeywordIn(keywords)
)
for powerful dynamic queries:
val query = TvShowQuery(availableOnNetflix = false, keywords = listOf("Rick", "Jimmy"))
val shows = tvShowRepo.findAll(query.toSpecification())
JpaSpecificationExecutor supports paging, so you can achieve pageable, type-safe, dynamic queries!
I have got a solution for this. I wrote some code to extend the spring-data-jpa .
I call it spring-data-jpa-extra
spring-data-jpa-extra comes to solve three problem:
dynamic native query support like mybatis
return type can be anything
no code, just sql
You can try it : )

QueryDsl MongoDb Relation

I am trying to use Mongodb with spring-data and QueryDsl.
I have following entitys:
#QueryEntity
#Document(collection="groups")
public class GroupImpl implements Group {
private String name;
#DBref
private List<User> groupMembers;
and
#QueryEntity
#Document(collection="users")
public class UserImpl implements User{
public static final String FIRST_NAME = "firstName";
public static final String LAST_NAME = "lastName";
My Repositories are implemented like this:
public interface GroupRepository extends Repository<Group, String>,QueryDslPredicateExecutor<Group>{}
Every normal CRUD operations were running successfully.
Also operations like "getUserByEmail" etc working successfully.
Now I am trying to get all Groups of an User in a UnitTest.
#Before
public void setup(){
roles = Arrays.asList(new String[]{"admin","user","guest"});
user = new UserImpl();
user.setFirstName("Foo");
user.setLastName("Bar");
user.setShortname("fbar");
user.setEMail("foo#bar.com");
user.setRoles(roles);
user2 = new UserImpl();
user2.setFirstName("Foo");
user2.setLastName("Bar");
user2.setShortname("fbar");
user2.setEMail("foo#bar.com");
user2.setRoles(roles);
user = userRepository.save(user);
user2 = userRepository.save(user2);
group = new GroupImpl();
group.setGroupMembers(Arrays.asList(new User[]{user,user2}));
group.setName("testGroup2");
group2 = new GroupImpl();
group2.setGroupMembers(Arrays.asList(new User[]{user,user2}));
group2.setName("testGroup2");
}
#Test
public void findGroupsByUser(){
Group savedGroup = repository.save(group);
Group savedGroup2 = repository.save(group2);
Assert.assertTrue(savedGroup2.getGroupMembers().contains(user));
List<Group> foundGroup = (List<Group>)repository.findAll(QGroupImpl.groupImpl.groupMembers.contains(user));
Assert.assertNotNull(foundGroup);
Assert.assertEquals(2, foundGroup.size());
}
This test fails "expected:<2> but was:<0>"
I am confused because savedGroup2 contains the user but if I try to get all Groups with this groupmember using QueryDsl I get no result.
I already checked the database and the users are stored successfully.
I also debugged and checked if the "_id"s of the given user and the user in the database are equal.
I have no Idea whats wrong.

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