MongoDB POJO and inner/nested classes - java

I've got a question regarding MongoDB Java driver and POJOs/serialization.
I'd like to build up my Java classes as they are represented in the MongoDB collection and then use the (new) POJO feature of MongoDB for fetching data. See: http://mongodb.github.io/mongo-java-driver/3.8/driver-async/getting-started/quick-start-pojo/ and http://mongodb.github.io/mongo-java-driver/3.8/bson/pojos/
Right now this only works if I have two different classes, like
User.class
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.bson.codecs.pojo.annotations.BsonId;
import org.bson.codecs.pojo.annotations.BsonProperty;
public class User {
#BsonId
#BsonProperty("_id")
UUID id;
private List<UserSession> sessions = new ArrayList<>();
}
UserSession.class
import java.time.Instant;
public class UserSession {
Instant start;
Instant end;
}
But as my collection looks more like the following…
{
_id: XYZ,
sessions: {
{start: XYZ, end: XYZ},
{start: XYZ, end: XYZ},
{start: XYZ, end: XYZ}
}
}
… I'd like to have a class that looks like this:
User.class
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.time.Instant;
import org.bson.codecs.pojo.annotations.BsonId;
import org.bson.codecs.pojo.annotations.BsonProperty;
public class User {
#BsonId
#BsonProperty("_id")
UUID id;
private List<Session> sessions = new ArrayList<>();
public class Session {
Instant start;
Instant end;
}
}
This makes sense as every unique sessions belongs directly to a user and with being a nested class I'd be able to access fields of the parent User object from within the Session object.
The problem is that the Java driver now complains about not having an empty/no arguments constructor for my Session class ("By default all POJOs must include a public or protected, empty, no arguments, constructor.").
My CodecProvider is like following:
CodecProvider codecProvider = PojoCodecProvider.builder()
.register(User.class, User.Session.class);
Anyone here having an idea how to solve this issue?
Really appreciate your help!
Thanks a lot!
Side note: The code snippets above are just short examples how my code kinda looks like. They are not the full code I'm using so there might be syntax errors in it.

Related

Order of the variables when using com.fasterxml.jackson.databind.ObjectWriter

I'm having a class which used to convert POJO to json string and compare it with another json string(read from logs). Recently we added 2 new boolean parameters to json and POJO and now the order of those 2 newly added variables is getting vary time to time.
I know by adding #JsonPropertyOrder(alphabetic = true) or #JsonPropertyOrder({ "att1", "att2", "att3" }) annotations, we can ensure the order. But I just want to know, how we got the same output earlier(with same order. Note: we had that code since 5years back)
Please find the sample code :
package com.....api;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
public class TestClass {
public String writeAsJson(Object object) throws JsonProcessingException {
JsonMapper.Builder jsonMapperBuilder = JsonMapper.builder();
jsonMapperBuilder.addModule(new Jdk8Module())
.addModule((new JavaTimeModule()))
.addModule((new SimpleModule()))
.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
.disable(new SerializationFeature[]{SerializationFeature.WRITE_DATES_AS_TIMESTAMPS})
.enable(new MapperFeature[]{MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS})
.disable(new MapperFeature[]{MapperFeature.DEFAULT_VIEW_INCLUSION})
.disable(new DeserializationFeature[]{DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES});
ObjectMapper objectMapper = jsonMapperBuilder.build();
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
}
}
As per I checked, we can not ensure the order of the variables in the POJO when it serializing and deserializing json<--> POJO. But I'm curious how we got the same output for such a long period and suddenly how it fail after adding 2 new boolean variables.

How to use AbstractAggregateRoot<T> or the annotation DomainEvents with Java Records

I am trying to evolve a domain which it includes and Aggregate-root implemented with Java Records and I am not able to find a way to use the Domain Event concept to propagate events from one Aggregate-root.
https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#core.domain-events
Compilation issue with the following syntax:
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.domain.AbstractAggregateRoot;
#Table("BALANCE")
public record Balance extends AbstractAggregateRoot<Balance> (
#Id
#Column("ID_BALANCE")
Long balanceId,
#Column("BALANCE")
BigDecimal balance,
#Column("ID_CUSTOMER")
Long customerId,
#Column("LAST_UPDATE")
Timestamp lastUpdate,
#Column("WITHDRAW_LIMIT")
BigDecimal withdrawLimit
) {
//Business logic
}
No problem with this syntax:
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.domain.AbstractAggregateRoot;
public class BalanceDemo extends AbstractAggregateRoot<BalanceDemo> {
#Id
#Column("ID_BALANCE")
Long balanceId;
#Column("BALANCE")
BigDecimal balance;
#Column("ID_CUSTOMER")
Long customerId;
#Column("LAST_UPDATE")
Timestamp lastUpdate;
#Column("WITHDRAW_LIMIT")
BigDecimal withdrawLimit;
//Constructors, Get, HashCode, Equals, toString
//Business Logic
}
What is wrong? Is it not possible to use Java records in combination with Domain Events?
As Tim Moore wrote in a comment a Java Record cannot extend another class since it already extends java.lang.Record implicitly.
So you can either copy the relevant code from AbstractAggregateRoot into your record or have an instance of it in your record and delegate to it in the relevant method implementations.

can i retrieve data from another collection using MongoRepository?

i have a class called "Invoice" and a MongoRepository
and what i want is to extract from my mongo database all validated invoices (those created in a given time range)
so here is my mongo repository :
import java.util.Date;
import java.util.List;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import Invoices.Invoice;
#Repository
public interface InvoiceRepositoryMongo extends MongoRepository<Invoice,Integer>{
#Query("db.invoices_bis.find({createdAt : {$gte : new ISODate('2013-04-30T17:24:16.000+00:00') , $lte : new ISODate('2013-05-30T17:24:16.000+00:00')}})")
List<Document> testrequete(Date start, Date ed);
}
dont pay too much attention to the query it is just for testing , but the problem is when i run this , i have this error :
nested exception is org.springframework.data.mapping.PropertyReferenceException: No property testrequete found for type Invoice!
i think the problem is that the method return a list of but i'm not sure
thanks !
i think te problem is that your Entity calls Invoice,
MongoRepository<Invoice,Integer>
so the result should be something like :
List<Invoice> testrequete(Date start, Date ed);

Spring Data Mongo cannot find PersistentEntity for Enum

Edit: I found a related question here, but the only 2 answers contradict each other, and there was not enough information to address my use case.
I am trying to use Spring Data Mongo to load records from a collection. One of the fields within those records is an Enum, defined as such:
#AllArgsConstructor
#Getter
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Action {
APPROVED("Approved"),
SAVED("Saved"),
CORRECTED("Corrected");
private String name;
#JsonCreator
static Action findValue(#JsonProperty("name") String name) {
return Arrays.stream(Action.values()).filter(v -> v.name.equals(name)).findFirst().get();
}
}
This should define enums to be serialized and deserialized according to a JSON representation: {"name": "Saved"} for example.
Jackson seems to be working fine, since I threw an API call at it and told it to expect an Action type, and it read the enum without any issues.
public void save(#RequestBody #Valid Action action) {
System.out.println(action.getName());
} // successfully prints the name of whatever Action I give
However, when I try to read an object with an Action field using Spring Data Mongo, I get the following:
Expected to read Document Document{{name=Corrected}} into type class package.structure.for.some.proprietary.stuff.constants.Action but didn't find a PersistentEntity for the latter!
So I'm thinking Spring Data Mongo just can't make heads or tails of these enums for whatever reason. But I'm not sure how to help it register that as a PersistentEntity. The main class of my Spring Boot app is in package package.structure.for.some.proprietary.stuff and is annotated as such:
#ComponentScan("package.structure")
#EnableTransactionManagement
#EnableAutoConfiguration
#SpringBootApplication
The object in particular I'm trying to read is defined by this POJO:
import java.util.Date;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
import lombok.NonNull;
import package.structure.for.some.proprietary.stuff.constants.Action;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"timeStamp",
"action",
})
#Data
#Document(collection = "sample_coll")
public class Sample {
#Id
#JsonIgnore
private String id = null;
#JsonProperty("timeStamp")
#NonNull
private Date timeStamp;
#JsonProperty("action")
#NonNull
private Action action;
}
and is queried from the collection with a MongoRepository:
public interface SampleRepository extends MongoRepository<Sample, String> {
}
using SampleRepository.findAll();
So my big question is, how do I get Spring Data Mongo to recognize this enum Action as a PersistentEntity?
Try #Enumerated
#Enumerated
#JsonProperty("action")
#NonNull
private Action action;

Spring Boot REST Controller issues

I am having a very strange issue with a Rest Controller. I have a very basic rest controller.
package com.therealdanvega.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.therealdanvega.domain.Post;
import com.therealdanvega.service.PostService;
#RestController
public class PostController {
private PostService postService;
#Autowired
public PostController(PostService postService){
this.postService = postService;
}
#RequestMapping("posts/test")
public String test(){
return "test...";
}
#RequestMapping( name="/posts/", method=RequestMethod.GET )
public Iterable<Post> list(){
return postService.list();
}
}
That calls a service
package com.therealdanvega.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.therealdanvega.domain.Post;
import com.therealdanvega.repository.PostRepository;
#Service
public class PostService {
private PostRepository postRepository;
#Autowired
public PostService(PostRepository postRepository){
this.postRepository = postRepository;
}
public Iterable<Post> list(){
return postRepository.findAll();
}
}
That calls a repository to fetch the data.
package com.therealdanvega.repository;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.therealdanvega.domain.Post;
#Repository
public interface PostRepository extends CrudRepository<Post, Long> {
Post findFirstByOrderByPostedOnDesc();
List<Post> findAllByOrderByPostedOnDesc();
Post findBySlug(String slug);
}
I am using an H2 in memory database and I only have a single Post record in there and can confirm so by going to the H2 console and running a select again the Post table.
If I visit the /test URL I get exactly what I am expecting which is the string "test..." printed to the browser. If I try and list all of the posts (which again is only 1) the browser starts looping over and over and continue to print out a JSON representing of the 1 post so many times that the application crashes and I see this in the console
2015-11-07 17:58:42.959 ERROR 5546 --- [nio-8080-exec-1]
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for
servlet dispatcherServlet threw exception
java.lang.IllegalStateException: getOutputStream() has already been
called for this response
This is what my browser looks like when I visit /posts which should only list 1
Post Domain Class
package com.therealdanvega.domain;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.springframework.data.annotation.CreatedDate;
#Entity
public class Post {
#Id #GeneratedValue
private Long id;
private String title;
#Column(columnDefinition = "TEXT")
private String body;
#Column(columnDefinition = "TEXT")
private String teaser;
private String slug;
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
private Date postedOn;
#ManyToOne
private Author author;
#SuppressWarnings("unused")
private Post(){
}
public Post(String title){
this.setTitle(title);
}
// getters & setters
}
Does anyone know what I am doing wrong or missing here? Why isn't it just display the 1 record in JSON format?
It seems that your Post object has a circular reference. The Author object in your Post object has a list of Posts objects and so on. Try putting the #JsonIgnore annotation on the author attribute of your post object.
You can also use the #JsonBackReference and #JsonManagedReference to solve the problem.
From the Jackson documentation :
Object references, identity
#JsonManagedReference, #JsonBackReference: pair of annotations used to
indicate and handle parent/child relationships expressed with pair of
matching properties #JsonIdentityInfo: class/property annotation used
to indicate that Object Identity is to be used when
serializing/deserializing values, such that multiple references to a
single Java Object can be properly deserialized. This can be used to
properly deal with cyclic object graphs and directed-acyclic graphs.
I believe your Posts domain object contains Author domain object, that in turn in it's posts field contains all the posts by that author, which in turn contains author that contains posts... you see where I'm going with this.
It's probably best that you use fetch or load graphs to optimize your query's fetch strategy.

Categories

Resources