Spring Data MongoDB - mongodb

I'm new to Spring Data MongoDB.
I'm trying to use #DBRef to refer a Document (Comments) from another Document (News) with spring-data-mongodb-1.7.0.RELEASE.jar.
To make things easy, I'm not passing anything as parameters for the rest call. The Comments collection exist in the database. I'm getting the following exception:
SEVERE: Servlet.service() for servlet [appServlet] in context with
path [/news] threw exception [Handler processing failed; nested
exception is java.lang.NoSuchMethodError:
com.mongodb.DBRef.(Ljava/lang/String;Ljava/lang/Object;)V] with
root cause java.lang.NoSuchMethodError:
com.mongodb.DBRef.(Ljava/lang/String;Ljava/lang/Object;)V
The following is my code:
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document
public class Comments{
#Id
private String id;
private String Comment;
public Comments(String comment){
this.id = UUID.randomUUID().toString();
this.comment = comment
}
//getters & setters
}
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
#Document
public class News{
#Id
private String id;
private String Title;
private String summary;
#DBRef
private List<Comments> comments;
public News(){
}
public News(String title, String summary){
this.id = UUID.randomUUID().toString();
this.title = title;
this.summary = summary;
}
//getters and setters
}
#RestController
#RequestMapping(value = "/rest/test")
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
#RequestMapping(value = "/createnews", method = RequestMethod.POST)
public void createNews() {
logger.info("createNews method started");
News news = new News("News Title", "News summary");
List<Comments> commentList = new ArrayList<Comments>();
Comments comments = new Comments("News Comments");
commentList.add( comments );
news.setComments( commentList );
logger.info("createNews method ended");
}
}
Problem:
I am unable to use #DBRef with spring-data-mongodb-1.7.0.RELEASE.jar.
I'm looking for advice from the experts. Thanks in advance.

Just Delete the "#DBRef".
You can't use DBRef to link a document to the list.
Spring data mongodb would transfer the List of your domain class as a child array in the document(As Embedded Documents).
Like this:
{
_id: 100,
Title: "food",
summary: "xyz",
Comments: [ { id: "111", Comment: "shipping" }, { id: "111", Comment: "shipping" } ]
}
See http://docs.mongodb.org/manual/tutorial/query-documents/#read-operations-embedded-documents for more details.

How are you saving your entities. You will first have to save the referred entity and then the parent entity.
News news = new News("News Title", "News summary");
Comments comments = new Comments("News Comments");
news.setComments( Arrays.asList(comments) );
mongoTemplate.save(comments);
mongoTemplate.save(news);

Related

Mongodb custom filed with bson or key-value Object, which is more efficient?

using BSON document
#Document
public class UserEventData {
#Id
private String id;
private org.bson.Document data;
//getter setter
}
......
UserEventData event = new UserEventData();
Document doc = new Document();
doc.append("date", LocalDate.now())
.append("counter", 1)
.append("title", "PHP");
event.setData(doc);;
......
using Key-Value Object
#Document
public class UserEventData {
#Id
private String id;
private List<CustomField> fields;
//getter setter
}
......
UserEventData event = new UserEventData();
List<CustomField> fields = new ArrayList<>();
fields.add(new CustomField("date",LocalDate.now()));
fields.add(new CustomField("counter",1));
fields.add(new CustomField("title","C++"));
event.setFields(fields);
......
If we do the aggregation in the further time, which one will more efficient?
Is there any other way to design the custom filed in Mongodb?

I am trying to make an API using spring boot and MongoDB. How should my entity class be for following template

I am trying to make an API using spring boot and MongoDB.
How should my entity class be for following template
I'm trying to have data in this format
{
"cart_id":"string",
"customer_name":"string"
"total_value":"double",
"line_items":
{
"product_name":"string",
"quantity":"number",
"price":"double"
}
}
Create an entity class like
#Data // lombok
#Document(collection="cart") //cart is your collection name
public class Cart {
#Id
private String id;
private String custname;
private double totval;
private LineItems line_items;
}
Create another class LineItems
#Data
public class LineItems {
private String prodname;
private int quant;
private int price;
}

how to store PostgreSQL jsonb using SpringBoot + JPA?

I'm working on a migration software that will consume unknown data from REST services.
I already think about use MongoDB but I decide to not use it and use PostgreSQL.
After read this I'm trying to implement it in my SpringBoot app using Spring JPA but I don't know to map jsonb in my entity.
Tried this but understood nothing!
Here is where I am:
#Repository
#Transactional
public interface DnitRepository extends JpaRepository<Dnit, Long> {
#Query(value = "insert into dnit(id,data) VALUES (:id,:data)", nativeQuery = true)
void insertdata( #Param("id")Integer id,#Param("data") String data );
}
and ...
#RestController
public class TestController {
#Autowired
DnitRepository dnitRepository;
#RequestMapping(value = "/dnit", method = RequestMethod.GET)
public String testBig() {
dnitRepository.insertdata(2, someJsonDataAsString );
}
}
and the table:
CREATE TABLE public.dnit
(
id integer NOT NULL,
data jsonb,
CONSTRAINT dnit_pkey PRIMARY KEY (id)
)
How can I do this?
Note: I don't want/need an Entity to work on. My JSON will always be String but I need jsonb to query the DB
Tried this but understood nothing!
To fully work with jsonb in Spring Data JPA (Hibernate) project with Vlad Mihalcea's hibernate-types lib you should just do the following:
1) Add this lib to your project:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.2.2</version>
</dependency>
2) Then use its types in your entities, for example:
#Data
#NoArgsConstructor
#Entity
#Table(name = "parents")
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
public class Parent implements Serializable {
#Id
#GeneratedValue(strategy = SEQUENCE)
private Integer id;
#Column(length = 32, nullable = false)
private String name;
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private List<Child> children;
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private Bio bio;
public Parent(String name, List children, Bio bio) {
this.name = name;
this.children = children;
this.bio = bio;
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Child implements Serializable {
private String name;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Bio implements Serializable {
private String text;
}
Then you will be able to use, for example, a simple JpaRepository to work with your objects:
public interface ParentRepo extends JpaRepository<Parent, Integer> {
}
parentRepo.save(new Parent(
"parent1",
asList(new Child("child1"), new Child("child2")),
new Bio("bio1")
)
);
Parent result = parentRepo.findById(1);
List<Child> children = result.getChildren();
Bio bio = result.getBio();
You are making things overly complex by adding Spring Data JPA just to execute a simple insert statement. You aren't using any of the JPA features. Instead do the following
Replace spring-boot-starter-data-jpa with spring-boot-starter-jdbc
Remove your DnitRepository interface
Inject JdbcTemplate where you where injecting DnitRepository
Replace dnitRepository.insertdata(2, someJsonDataAsString ); with jdbcTemplate.executeUpdate("insert into dnit(id, data) VALUES (?,to_json(?))", id, data);
You were already using plain SQL (in a very convoluted way), if you need plain SQL (and don't have need for JPA) then just use SQL.
Ofcourse instead of directly injecting the JdbcTemplate into your controller you probably want to hide that logic/complexity in a repository or service.
There are already several answers and I am pretty sure they work for several cases. I don't wanted to use any more dependencies I don't know, so I look for another solution.
The important parts are the AttributeConverter it maps the jsonb from the db to your object and the other way around. So you have to annotate the property of the jsonb column in your entity with #Convert and link your AttributeConverter and add #Column(columnDefinition = "jsonb") as well, so JPA knows what type this is in the DB. This should already make it possible to start the spring boot application. But you will have issues, whenever you try to save() with the JpaRepository. I received the message:
PSQLException: ERROR: column "myColumn" is of type jsonb but
expression is of type character varying.
Hint: You will need to rewrite or cast the expression.
This happens because postgres takes the types a little to serious.
You can fix this by a change in your conifg:
datasource.hikari.data-source-properties: stringtype=unspecified
datasource.tomcat.connection-properties: stringtype=unspecified
Afterwards it worked for me like a charm, and here is a minimal example.
I use JpaRepositories:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Integer> {
}
The Entity:
import javax.persistence.Column;
import javax.persistence.Convert;
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
#Convert(converter = MyConverter.class)
#Column(columnDefinition = "jsonb")
private MyJsonObject jsonContent;
}
The model for the json:
public class MyJsonObject {
protected String name;
protected int age;
}
The converter, I use Gson here, but you can map it however you like:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class MyConverter implements AttributeConverter<MyJsonObject, String> {
private final static Gson GSON = new Gson();
#Override
public String convertToDatabaseColumn(MyJsonObject mjo) {
return GSON.toJson(mjo);
}
#Override
public MyJsonObject convertToEntityAttribute(String dbData) {
return GSON.fromJson(dbData, MyJsonObject.class);
}
}
SQL:
create table my_entity
(
id serial primary key,
json_content jsonb
);
And my application.yml (application.properties)
datasource:
hikari:
data-source-properties: stringtype=unspecified
tomcat:
connection-properties: stringtype=unspecified
For this case, I use the above tailored converter class, you are free to add it in your library. It is working with the EclipseLink JPA Provider.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.log4j.Logger;
import org.postgresql.util.PGobject;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Map;
#Converter
public final class PgJsonbToMapConverter implements AttributeConverter<Map<String, ? extends Object>, PGobject> {
private static final Logger LOGGER = Logger.getLogger(PgJsonbToMapConverter.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
#Override
public PGobject convertToDatabaseColumn(Map<String, ? extends Object> map) {
PGobject po = new PGobject();
po.setType("jsonb");
try {
po.setValue(map == null ? null : MAPPER.writeValueAsString(map));
} catch (SQLException | JsonProcessingException ex) {
LOGGER.error("Cannot convert JsonObject to PGobject.");
throw new IllegalStateException(ex);
}
return po;
}
#Override
public Map<String, ? extends Object> convertToEntityAttribute(PGobject dbData) {
if (dbData == null || dbData.getValue() == null) {
return null;
}
try {
return MAPPER.readValue(dbData.getValue(), new TypeReference<Map<String, Object>>() {
});
} catch (IOException ex) {
LOGGER.error("Cannot convert JsonObject to PGobject.");
return null;
}
}
}
Usage example, for an entity named Customer.
#Entity
#Table(schema = "web", name = "customer")
public class Customer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Convert(converter = PgJsonbToMapConverter.class)
private Map<String, String> info;
public Customer() {
this.id = null;
this.info = null;
}
// Getters and setter omitted.
If you're using R2DBC you can use dependency io.r2dbc:r2dbc-postgresql, and use type io.r2dbc.postgresql.codec.Json in your member attributes of an entity class, e.g.:
public class Rule {
#Id
private String client_id;
private String username;
private String password;
private Json publish_acl;
private Json subscribe_acl;
}

How to delete a nested embedded sub document using MongoTemplate

Scenario
I have 1 Collection: Discussion
Discussion Collection contains 2 embedded Documents(one inside another)
Discussion contains reply, reply contains comment
I am using MongoTemplate in my Project (Spring with MongoDB)
My POJO Details:
Discussion
#Document(collection = "discussion")
public class Discussion {
private String discussion_id;
private String title;
private String description;
private Date discussion_time;
private String initiated_by;
private Set<String> topic_tags;
private int replies_count;
// List of Replies
private List<Reply> replies;
}
Reply
public class Reply {
private String reply_id;
private String replied_by;
private String reply_content;
private Date replied_date;
private float rating;
//List of Comments
private List<Comment> comments;
}
Comment
public class Comment {
private String comment_id;
private String commented_by;
private String comment_content;
private Date commented_date;
}
Hint
I can able to delete reply from Discussion Collection.Code as follows
public void deleteReply(String reply_id, String replied_by, String initiated_by) {
if (replied_by.equals(initiated_by)) {
Query query = new Query(Criteria.where("initiated_by").is(initiated_by));
query.fields().elemMatch("replies", Criteria.where("reply_id").is(reply_id));
Update update = new Update();
update.pull("replies", new BasicDBObject("reply_id", reply_id));
mongoTemplate.updateMulti(query, update, COLLECTION_NAME);
logger.info("deleted reply successfully ");
} else {
logger.info(" Unauthoriszed to delete ");
}
}
I want to delete a comment from Discussion Collection. can any one provide the solution.

use of GeoJsonPoint with mongoDB and spring data

I tried hard to build an example to save my enity like below:
Resource class has ResourceDetail in which I defined GeoJsonPoint.
#Document(collection = "Resource")
public class Resource implements Serializable {
/**
*
*/
private static final Long serialVersionUID = 1L;
#Id
private Long id;
private String name;
private Integer age;
private String orgName;
private String resourceType;
private String department;
private boolean showPrice;
private Integer price;
private ResourceDetail resourceDetail;
}
#Document(collection = "ResourceDetail")
public class ResourceDetail implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String identityNumber;
#DateTimeFormat(iso = ISO.DATE)
private Date birthDate;
private String addressLine1;
private String addressLine2;
private String specialization;
private GeoJsonPoint location;
}
I have added following mapper also in Appconfig:
#Configuration
#ComponentScan(basePackages = "com.test")
#EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
#Bean
public MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(new MongoClient(), "test");
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory());
return mongoTemplate;
}
/**
* Read JSON data from disk and insert those stores.
*
* #return
*/
public #Bean ObjectMapper repositoryPopulator() {
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(GeoJsonPoint.class, GeoJsonPointMixin.class);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
static abstract class GeoJsonPointMixin {
GeoJsonPointMixin(#JsonProperty("longitude") double x, #JsonProperty("latitude") double y) {
}
}
}
I am getting this error:
{
"timestamp": 1445857673601,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "Could not read JSON: No suitable constructor found for type [simple type, class org.springframework.data.mongodb.core.geo.GeoJsonPoint]: can not instantiate from JSON object (need to add/enable type information?)\n at [Source: java.io.PushbackInputStream#d7eff8; line: 13, column: 25] (through reference chain: com.appointment.domain.Resource[\"resourceDetail\"]->com.appointment.domain.ResourceDetail[\"location\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class org.springframework.data.mongodb.core.geo.GeoJsonPoint]: can not instantiate from JSON object (need to add/enable type information?)\n at [Source: java.io.PushbackInputStream#d7eff8; line: 13, column: 25] (through reference chain: com.appointment.domain.Resource[\"resourceDetail\"]->com.appointment.domain.ResourceDetail[\"location\"])",
"path": "/rest/resource/add"
}
I used this format to save my enity with GeoJsonPoint:
{
"name":"Test",
"age": 32,
"orgName":"testOrg",
"resourceType":"testresourcType",
"price":1200,
"department":"testDepartment",
"resourceDetail": {
"identityNumber": "3",
"birthDate": "2000-10-10",
"location" : { "latitude":40.743827, "longitude":-73.989015 }
}
Please help me to solve this. Thanks
Probably late, but you need to use GeoJSON format in your mongo.
"location" : {
"type" : "Point",
"coordinates" : [
-2.6637,
54.6944
]
}
be aware that coordinates are in this order : longitude, latitude
See more :
http://geojson.org/
http://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb/core/geo/GeoJson.html
Tested your Mixin code an it worked. Although, I would suggest you to make sure you are sending application/json as content type and the json structure is correct (In your example there is a missing }).
There is a similar question like yours and this issue can be solved by registering a GeoJsonModule as well: https://stackoverflow.com/a/37340077/3697851