Springboot save hashmap in mongo repository - java

I have an entity class that has a hashmap field :
#Document(collection = "tenant")
#Getter() #Setter #ToString
public class Tenant extends DateAudit {
#Id
private String tenantId=UUID.randomUUID().toString();
#Indexed(unique = true)
#Field("name")
private String name;
#Field("app_registration_list")
private HashMap<String, ArrayList<AppRegistration>> appRegistrationList = new HashMap<String, ArrayList<AppRegistration>>();
}
But while adding data to the field appRegistrationList I get this error on my controller:
"Invalid BSON field name $hashCodeCache"
I am using Microsoft Cosmos Db instead of mongoDb. As read in another issue, my key is also a string value. Not sure what can be changed. I am open to changing the data type that will still satisfy this key and list purpose.
EDIT:
I can see this code working if I change the field to the following:
#Field("app_registration_list")
private HashMap<String,String> appRegistrationList = new HashMap<>();
However, this doesn't solve the purpose I was looking for.

Related

How to save collection of user defined type to ScyllaDB with Spring Data Cassandra?

When I try to save entity with a list or set of user defined type, I get error:
Failed to convert from type [java.util.ImmutableCollections$Set12<?>] to type [com.datastax.oss.driver.api.core.data.UdtValue] for value '[scrubbed.entity.Article$AttachmentInfo#3337c2b5]'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [scrubbed.entity.Article$AttachmentInfo] to type [com.datastax.oss.driver.api.core.data.UdtValue]]
That's the definition that is in ScyllaDB database:
CREATE TYPE IF NOT EXISTS attachment_info (
id UUID,
fileName TEXT,
mimeType TEXT,
sizeBytes BIGINT
);
CREATE TABLE IF NOT EXISTS articles
(
id UUID,
attachments SET<frozen<attachment_info>>,
PRIMARY KEY (id)
);
Entity in Java:
#Getter
#Setter
#Table("articles")
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
public class Article {
#PrimaryKeyColumn(name = "id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private UUID id;
#Column("attachments")
#CassandraType(type = CassandraType.Name.UDT, userTypeName = "attachment_info")
private Set<AttachmentInfo> attachments = new HashSet<>();
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#UserDefinedType("attachment_info")
public static class AttachmentInfo {
private UUID id;
private String fileName;
private String mimeType;
private Long sizeBytes;
}
}
And saving to database:
var metadata = new Article.AttachmentInfo();
metadata.setFileName("Filename");
metadata.setId(UUID.randomUUID());
metadata.setMimeType("image/png");
metadata.setSizeBytes(12345L);
var article = new Article();
article.setId(UUID.randomUUID());
article.setAttachments(Set.of(metadata));
articleRepository.insert(article);
Using version 3.2.0 of spring-data-cassandra
I'm not a Spring expert (just a Scylla expert ;-)), so please forgive me if my answer doesn't work.
It seems to me that the annotation
#Column("attachments")
#CassandraType(type = CassandraType.Name.UDT, userTypeName = "attachment_info")
private Set<AttachmentInfo> attachments = new HashSet<>();
Is wrong because it tells spring-data-cassandra that the "attachments" column is a UDT, while in fact it isn't - it's a set (of UDTs).
I think that the correct annotation for a set of UDTs would be something like:
#CassandraType(type = DataType.Name.SET, typeArguments = DataType.Name.UDT)
But I'm not sure (I couldn't find an example in documentation) where to put the userTypeName. If I understand the documentation correctly, you don't need the that userTypeName explicitly because you annotated the AttachmentInfo class with a UserDefinedType annotation saying that its scylla name is "attachment_info" and that should hopefully be enough.
Moreover, according to some documentation I read, you may not need the CassandraType annotation on attachements at all, because spring-data-cassandra understands Set<AttachmentInfo> because it knows the primitive type Set, and you already annotated AttachmentInfo as a UDT.

Spring Data MongoDB Unique Embedded Fields

So, I have one #Document class which has a embedded pojo field which I want it to be unique for the document based on a key in the pojo class. I tried using #CompoundIndex & #Indexed to mark it as unique but it doesn't seem to work.
#Document
public class Project {
private String id;
private String name;
private List<Details> details = new ArrayList<>();
}
public class Details{
private String key;
private String description;
}
What I want to achieve is that a project document should have unique details field in it with it's key being unique. But when I have the
#CompoundIndexes({ #CompoundIndex(name = "details_key", def = "{'details.key':1}", unique = true) }) on the Project class it doesn't work. Which I thought it should. Or am I wrong somewhere with my understanding. As I am new to this.

Using ObjectId as String in Java (Manual reference) with spring-data mongodb

In MongoDB documentation they suggest to use ObjecId for manual references.
please see https://docs.mongodb.com/manual/reference/database-references/#document-references
original_id = ObjectId()
db.places.insert({
"_id": original_id,
"name": "Broadway Center",
"url": "bc.example.net"
})
db.people.insert({
"name": "Erin",
"places_id": original_id,
"url": "bc.example.net/Erin"
})
I'm using spring-data-mongodb and what I'm looking for is to have a People class defined like this:
#Document
public class People {
private String name;
#Reference // or any Annotation to convert an ObjectId to a String
private String placesId;
private String url;
}
How to have a "places_id" as ObjectId in mongoDB but mapped to a String in our POJO ?
I was expecting to have an annotation like #Reference but it seems to not be implemented.
I don't understand why we don't have this kind of annotation in spring-data-mongodb. I don't want to implement an explicit converter like suggested in spring documentation for all documents that use manual references.
Maybe it's not the right approach.
Did I miss something ?
UPDATE :
I like the idea to have a POJO using only String instead of ObjectId. Let's say I've got a class Place like this :
#Document
public class Place {
#Id
private String id;
private String name;
}
place.getId() will be a String but people.getPlaceId() will be an ObjectId. I want to avoid this unnecessary mapping.
The solution would be:
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;
public class People {
#Field(targetType = FieldType.OBJECT_ID)
private String placesId;
}
This will map POJO string to ObjectId in MongoDB.
Why don't you leave the field as ObjectId?
#Document
public class People {
private String name;
private ObjectId placesId;
private String url;
}
If you want to query by this field you can do this:
For lists
List<String> ids // the ids as strings
List<ObjectId> objIds = ids .stream()
.map(i -> new ObjectId(i))
.collect(Collectors.toList());
For single String
String id // single id
ObjectId objId = new ObjectId(id);
If you want to make a real reference to an other object in your database, use the #DBRef annotation which is provided by Spring Data.
Your updated code could look like the following:
#Document
public class People {
private String name;
#DBRef
private Place place;
private String url;
}
Spring Data will then automatically map a Place object to your People object. Internally this is done with a reference to the unique ObjectId. Try this code and have a look at your mongo database.
For more information have a look at: MongoDb with java foreign key
I have a solution very simple:
#JsonSerialize(using= ToStringSerializer.class)
private ObjectId brandId;
...
put that on the attribute that is Object Id, and the ObjectId gets and inserts like string

Is it possible to use a MongoRepository with not-fixed document structure? [duplicate]

Mongodb is a no-schema document database, but in spring data, it's necessary to define entity class and repository class, like following:
Entity class:
#Document(collection = "users")
public class User implements UserDetails {
#Id private String userId;
#NotNull #Indexed(unique = true) private String username;
#NotNull private String password;
#NotNull private String name;
#NotNull private String email;
}
Repository class:
public interface UserRepository extends MongoRepository<User, String> {
User findByUsername(String username);
}
Is there anyway to use map not class in spring data mongodb so that the server can accept any dynamic JSON data then store it in BSON without any pre-class define?
First, a few insightful links about schemaless data:
what does “schemaless” even mean anyway?
“schemaless” doesn't mean “schemafree”
Second... one may wonder if Spring, or Java, is the right solution for your problem - why not a more dynamic tool, such a Ruby, Python or the Mongoshell?
That being said, let's focus on the technical issue.
If your goal is only to store random data, you could basically just define your own controller and use the MongoDB Java Driver directly.
If you really insist on having no predefined schema for your domain object class, use this:
#Document(collection = "users")
public class User implements UserDetails {
#Id
private String id;
private Map<String, Object> schemalessData;
// getters/setters omitted
}
Basically it gives you a container in which you can put whatever you want, but watch out for serialization/deserialization issues (this may become tricky if you had ObjectIds and DBRefs in your nested document). Also, updating data may become nasty if your data hierarchy becomes too complex.
Still, at some point, you'll realize your data indeed has a schema that can be pinpointed and put into well-defined POJOs.
Update
A late update since people still happen to read this post in 2020: the Jackson annotations JsonAnyGetter and JsonAnySetter let you hide the root of the schemaless-data container so your unknown fields can be sent as top-level fields in your payload. They will still be stored nested in your MongoDB document, but will appear as top-level fields when the ressource is requested through Spring.
#Document(collection = "users")
public class User implements UserDetails {
#Id
private String id;
// add all other expected fields (getters/setters omitted)
private String foo;
private String bar;
// a container for all unexpected fields
private Map<String, Object> schemalessData;
#JsonAnySetter
public void add(String key, Object value) {
if (null == schemalessData) {
schemalessData = new HashMap<>();
}
schemalessData.put(key, value);
}
#JsonAnyGetter
public Map<String, Object> get() {
return schemalessData;
}
// getters/setters omitted
}

#Indexed on nested property not working in Spring-data for mongo

I have the following object structure:
#Document(collection = "user")
#TypeAlias("user")
public class User {
#Id
private ObjectId id;
private Contact info = new Contact();
}
and here is the Contact pojo:
public class Contact {
#Indexed(unique = true)
private String mail;
}
But for some reasons not known to me, I don't see Spring-data creating a unique index for the property info.mail
To summarize, I have this json structure of user object:
{_id:xxxxx,info:{mail:"abc#xyz.shoes"}}
And I want to create a unique index on info.mail using Spring data with the above pojo structure. Please help.
As far as I remember, annotating embedded fields with #Indexed will not work. #CompoundIndex is the way to go:
#Document(collection = "user")
#TypeAlias("user")
#CompoundIndexes({
#CompoundIndex(name = "contact_email", def = "{ 'contact.mail': 1 }", unique = true)
})
public class User {
#Id
private ObjectId id;
private Contact info = new Contact();
}
In my case I had a fresh spring boot application 2.3.0 with just #Document, #Id and #Indexed annotations. I was able to retrieve and insert documents but it refused to create the index other than the PK. Finally I figured that there is a property that you need to enable.
spring.data.mongodb.auto-index-creation = true
As a matter of fact it even works on nested objects without #Document annotation.
Hope this helps :)
Obsolete answer, this was with and older version of mongodb 1.x.
Had the same issue, it seems that your Contact class is missing the #Document annotation i.e.
#Document
public class Contact {
#Indexed(unique = true)
private String mail;
}
Should work, quote from the spring mongodb reference
Automatic index creation is only done for types annotated with #Document.
Extending #Xenobius's answer:
If any configuration extending AbstractMongoClientConfiguration is set, MongoMappingContext will back off. The result is:
spring.data.mongodb.auto-index-creation = true will not be effective
You will need add this into your own configuration:
#Override
protected boolean autoIndexCreation() {
return true;
}
ref: https://github.com/spring-projects/spring-boot/issues/28478#issuecomment-954627106

Categories