spring data mongodb LocalDateTime conversion - java

I have got a simple Model & Repository classes as shown below:
StudentModel class:
#Document(collection = "student")
public final class StudentModel {
#Id
private final String name;
private final LocalDateTime joiningDateTime;
public StudentModel(String name, LocalDateTime joiningDateTime) {
this.name = name;
this.joiningDateTime = joiningDateTime;
}
public String getName() {
return name;
}
public LocalDateTime getJoiningDateTime() {
return joiningDateTime;
}
#Override
public String toString() {
return "StudentModel{" +
"name='" + name + '\'' +
", joiningDateTime=" + joiningDateTime +
'}';
}
}
StudentRepository class:
#Repository
public interface StudentRepository extends MongoRepository<StudentModel, String> {
}
StudentTestingService class:
#Service
public class StudentTestingService {
#Autowired
private StudentRepository studentRepository;
#PostConstruct
public void init() {
studentRepository.save(new StudentModel("JOHN",
LocalDateTime.of(2018, 4, 15, 9, 30, 0)));//line1
System.out.println(" student saved to database !!!!!!! ");//line2
StudentModel student = studentRepository.findOne("JOHN");//line3
System.out.println("Joining DATE :"+student.getJoiningDateTime());//line4
}
}
I run the above code inside a spring boot application (server runs at BST timezone).
As you could see above, my StudentTestingService class stores the joiningDateTime for the student (in BST) as "15-APR-2018 09:30:00" (line1 above)
which is being saved inside the MongoDB database with the GMT time (i.e., "15-APR-2018 08:30:00") as shown in the below screen shot:
Now, when I query the record (at line3) and print it (line4), it prints in BST (though inside MongoDB database it is being stored as GMT time).
So, my question is, how and where does inside "spring-data-mongodb" code, these time conversions (locat time to GMT & GMT to localtime again) being handled/coded?
This seems quite basic and I am sure I am missing something here & lost.
Could you please point me to the "spring-data-mongodb" code base for this? If these conversions are not being handled inside "spring-data-mongodb", where does these being handled i.e., is it inside "mongo-java-driver" library classes ?
Versions:
Spring boot version: 1.5.10
Mongo DB version: 3.4.9

Related

org.springframework.dao.IncorrectResultSizeDataAccessException Mongo Limit dosen't work?

GoodEvening
i'm new about mongo, and i choose to use a mix & match of RepositoryPattern and Mongotemplate
Now i find a very strange error when i want to use MongoTemplate to retrive a specific value of an inner object in order to calculate a progressive ID
It seems my code work for 0 or 1 document inside a collection, but when i have equal or more than 2 documents, the method will throw a
org.springframework.dao.IncorrectResultSizeDataAccessException: Query { "$java" : Query: {}, Fields: {}, Sort: {} } returned non unique result.
as if limit dosen't work.
I have just, for now, a rest and a repository pattern, but in the service layer i have another autowire whit mongotemplate class, this template is used by just 1 method.
public DetailedOrder findTheBiggestBy(String byWhat){
Query query = new Query();
query.with(Sort.by(Sort.Direction.DESC,byWhat)).limit(1);
return mongoDb.findOne(query,DetailedOrder.class);
}
As you can see is pretty simple and it actually work, becouse before moving the Template inside Service it actually work whit various documents inside collection, when i autowired it over the rest layer.
There is something i miss and is not rly related to mongo but related to spring autowire?
My detailorderClass is
package com.service.backend.BK.Pojo;
import com.service.backend.BK.Constants.Constant;
import org.bson.codecs.pojo.annotations.BsonId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;
import org.springframework.data.mongodb.core.mapping.MongoId;
import java.util.List;
#Document("Orders")
public class DetailedOrder {
#MongoId(FieldType.OBJECT_ID)
private String id;
#Field
private List<Integer> category;
// 1 junk to 10 Pristine
#Field
private Integer quality;
#Field
private BaseOrder baseOrder;
#Field
private String qualityDescription;
public DetailedOrder(){}
public DetailedOrder(String Description,Double price, List<Integer> category, Integer quality) {
this.category = category;
this.quality = quality;
this.qualityDescription = qualityDescriptionPairFactory(quality);
this.baseOrder=new BaseOrder(Description,price);
}
public List<Integer> getCategory() {
return category;
}
public void setCategory(List<Integer> category) {
this.category = category;
}
public Integer getQuality() {
return quality;
}
public void setQuality(Integer quality) {
this.quality = quality;
}
public String getQualityDescription() {
return qualityDescription;
}
public void setQualityDescription(String qualityDescription) {
this.qualityDescription = qualityDescription;}
private String qualityDescriptionPairFactory(int quality){
switch (quality){
case 1:return Constant.Quality.NOGRAD.label;
case 2:return Constant.Quality.HEAVYDMN.label;
case 3:return Constant.Quality.LOOSE.label;
case 4:return Constant.Quality.POOR.label;
case 5:return Constant.Quality.LIGHTDMN.label;
case 6:return Constant.Quality.GOOD.label;
case 7:return Constant.Quality.EXCELENT.label;
case 8:return Constant.Quality.NEARMINT.label;
case 9:return Constant.Quality.MINT.label;
case 10:return Constant.Quality.NEWUNRL.label;
default:return Constant.Quality.NOGRAD.label; }
}
#Override
public String toString() {
return "DetailedOrder{" +
"id='" + id + '\'' +
", baseOrder='" + getBaseOrder().returnBaseOrder() + '\'' +
", category=" + category +
", quality=" + quality +
", qualityDescription='" + qualityDescription + '\'' +
'}';
}
public BaseOrder getBaseOrder() {
return baseOrder;
}
public void setBaseOrder(BaseOrder baseOrder) {
this.baseOrder = baseOrder;
}
public void setId(String id) {
this.id = id;
}
}
and that lead to baseOrderClass
package com.service.backend.BK.Pojo;
import org.joda.time.DateTime;
import org.springframework.data.mongodb.core.mapping.Document;
public class BaseOrder {
private String id;
private String description;
private Double desideredPrice;
//Pending,active,Rejected,Hault,etc etc
private int status;
protected BaseOrder(){}
protected BaseOrder(String description, Double desideredPrice) {
this.description = description;
this.desideredPrice = desideredPrice;
}
public String returnBaseOrder(){
return "BaseOrder{" +
"id='" + id + '\'' +
", description='" + description + '\'' +
", desideredPrice=" + desideredPrice +
", status=" + status +
'}';
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getDesideredPrice() {
return desideredPrice;
}
public void setDesideredPrice(Double desideredPrice) {
this.desideredPrice = desideredPrice;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
//###############################################################################################################//
}
The OPs question related to a spring context issue. By just accessing the repository directly the problem could be resolved.
Within the comments an additional question/target has been found, i would like to provide an answer for that.
How to have persistence access within the domain layer?
I have created a repository at GitHub where you can find a working solution. This code is not best practice but will provide a solution that works.
Every constructed domain object needs access to the declared repositories. That can be a problem when using dependency injection. In order to keep entity construction pristine an abstract class has been introduces that takes the task to get the concrete repositories.
The core idea is to make the Spring ApplicationContext accessable statically. Having this context an access to the repositories is just one call away.
Beans having the ApplicationContextAware interface will be called in a very early stage of Spring Boots startup. When using this class to access the context every consumer has to call it after Spring has been loaded. This can be achieved by using #Component, #Configuration, #Bean to run code and there will be no race condition.
Further details are within the repositories readme file.
I hope that this helps you :)

Java 11 + Spring Boot + HATEOAS + JAXBException: Class *** nor any of its super class is known to this context

I am trying to implement a simple service and use the HATEOAS resource from spring-boot in order to display a link. When the service run, it throws a WARN message in the console with the following:
javax.xml.bind.JAXBException: class com.in28minutes.rest.webservices.restfulwebservices.user.User nor any of its super class is known to this context
I am using JDK 11, which forced me to add the dependency, since I was getting a ClassNotFoundException:
"org.glassfish.jaxb:jaxb-runtime"
But after adding that dependency the spring Resource HATEOAS class is not able to be marshalled.
public class User {
private Integer id;
#Size(min=2, message="The name should have at least 2 characters")
private String name;
#Past
private LocalDate birthDate;
public User() {
}
public User(Integer id, String name, LocalDate birthDate) {
super();
this.id = id;
this.name = name;
this.birthDate = birthDate;
}
...
}
#GetMapping("/users/{id}")
public Resource<User> retrieveUser(#PathVariable("id") int theId) {
User aUserResult = service.findOne(theId);
if (aUserResult == null) {
throw new UserNotFoundException("id-" + theId);
}
Resource<User> aUserResource = new Resource<User>(aUserResult);
ControllerLinkBuilder aLinkTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
aUserResource.add(aLinkTo.withRel("all-users"));
return aUserResource;
}
strangely, this is related to browser. it should work if you call the endpoint using client like "curl" instead of browser.
workaround which helped for me - add:
, produces="application/json; charset=UTF-8"
to GetMapping()
more details at:
https://github.com/spring-guides/tut-rest/issues/64

Room Abstract Pojo

I'm creating for fun an android application that tracks the spendings. I'm using Room to persist the user's data and I have POJOs that show the daily/weekly/monthly summaries.
These classes are quite similar, thus I would like to have one abstract POJO that contains the fields and extensions of it that reformat to the correct format. Something like:
public abstract class PeriodInformation {
PeriodInformation(#NonNull Calendar mCalendar, Integer mPeriodSpendingCount, Float mPeriodSpendingSum) {
this.mCalendar = mCalendar;
this.mPeriodSpendingCount = mPeriodSpendingCount;
this.mPeriodSpendingSum = mPeriodSpendingSum;
}
#ColumnInfo(name = "DateTime")
private final Calendar mCalendar;
#ColumnInfo(name = "SpendingCount")
private Integer mPeriodSpendingCount;
#ColumnInfo(name = "SpendingSum")
private Float mPeriodSpendingSum;
// Some other code, e.g., getters, equal override,...
}
Here the extension:
public class WeekInformation extends PeriodInformation{
public WeekInformation(#NonNull Calendar mCalendar, Integer mPeriodSpendingCount, Float mMonthSpendingSum) {
super(mCalendar, mPeriodSpendingCount, mMonthSpendingSum);
}
#Override
public String getPeriodRepresentation() {
//return representation;
}
}
However, I get following error message for the WeekInformation Class:
error: Entities and Pojos must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type).
So it seems that this is not possible in Room, thus I would be happy to get some suggestion how to not have to copy the same code too often.
thank you.
EDIT:
I use following DAO code to aggregate to the POJO, the column calendarDate has following format "yyyy-MM-dd'T'HH:mm:ss.SSSXXX":
#Query("SELECT date(datetime(calendarDate)) AS 'DateTime', count(uID) AS 'SpendingCount', sum(value) AS 'SpendingSum' from spending GROUP BY date(datetime(calendarDate))")
LiveData<List<DayInformation>> loadDayInformation();
I was able to make this work for me, using the Embedded annotation, allowing direct access to the fields of the embedded data type.
public class DayInformation {
#Embedded
public PeriodInformation periodInformation;
#Override
public String toString() {
return "DayInformation{" +
"periodInformation=" + periodInformation +
'}';
}
}
and
public class PeriodInformation {
PeriodInformation(Calendar timestamp,
int periodSpendingCount,
float periodSpendingSum) {
this.timestamp = timestamp;
this.periodSpendingCount = periodSpendingCount;
this.periodSpendingSum = periodSpendingSum;
}
#ColumnInfo(name = "DateTime")
public final Calendar timestamp;
#ColumnInfo(name = "SpendingCount")
public Integer periodSpendingCount;
#ColumnInfo(name = "SpendingSum")
public Float periodSpendingSum;
#Override
public String toString() {
final DateFormat dateInstance = SimpleDateFormat.getDateInstance();
String date = timestamp == null ? "null" : dateInstance.format(timestamp.getTime());
return "PeriodInformation{" +
"timestamp='" + date + '\'' +
", periodSpendingCount=" + periodSpendingCount +
", periodSpendingSum=" + periodSpendingSum +
'}';
}
}
plus
#Entity
public class Spending {
#PrimaryKey(autoGenerate = true)
public int uid;
#ColumnInfo(name = "calendarDate")
public Calendar timestamp;
#ColumnInfo(name = "value")
public float value;
public Spending(#NonNull Calendar timestamp, float value) {
this.timestamp = timestamp;
this.value = value;
}
#Override
public String toString() {
final DateFormat dateInstance = SimpleDateFormat.getDateInstance();
String date = timestamp == null ? "null" : dateInstance.format(timestamp.getTime());
return "Spending{" +
"uid=" + uid +
", timestamp='" + date + '\'' +
", value=" + value +
'}';
}
}
and a DAO
#Dao
public interface SpendingDao {
#Insert
void insertAll(Spending... spendings);
#Query("SELECT * FROM spending")
LiveData<List<Spending>> findAll();
#Query("SELECT calendarDate AS 'DateTime', count(uID) AS 'SpendingCount', sum(value) AS 'SpendingSum' from spending GROUP BY date(datetime(calendarDate))")
LiveData<List<DayInformation>> loadDayInformation();
}
gives the following output
aggregated data is
DayInformation{periodInformation=PeriodInformation{timestamp='Jun 26, 2018', periodSpendingCount=8, periodSpendingSum=184.0}}
spending data is Spending{uid=1, timestamp='Jun 26, 2018', value=23.0}
spending data is Spending{uid=2, timestamp='Jun 26, 2018', value=23.0}
spending data is Spending{uid=3, timestamp='Jun 26, 2018', value=23.0}
spending data is Spending{uid=4, timestamp='Jun 26, 2018', value=23.0}
spending data is Spending{uid=5, timestamp='Jun 26, 2018', value=23.0}
spending data is Spending{uid=6, timestamp='Jun 26, 2018', value=23.0}
spending data is Spending{uid=7, timestamp='Jun 26, 2018', value=23.0}
spending data is Spending{uid=8, timestamp='Jun 26, 2018', value=23.0}

Spring Boot - Required bean not found - but interface is defined in same class

When trying to run the following sample spring boot code during runtime I get the error
Parameter 0 of method runner in
springbootdemo.SpringbootDemoApplication required a bean of type
'springbootdemo.SpringbootDemoApplication$ReservationRepository' that
could not be found.
This is my sample code:
#SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
#Bean
CommandLineRunner runner(ReservationRepository rr) {
return strings -> {
Arrays.asList("Les, Josh, Phil, Sasha, Peter".split(","))
.forEach(n -> rr.save(new Reservation(n)));
rr.findAll().forEach(System.out::println);
rr.findByReservationName("Les").forEach(System.out::println);
};
}
interface ReservationRepository extends JpaRepository<Reservation, Long> {
// select * from reservation where reservation_name = :rn
Collection<Reservation> findByReservationName(String rn);
}
#Entity
class Reservation {
#Id
#GeneratedValue
private Long id;
private String reservationName;
public Reservation() { // for JPA - god sake why :-(
}
public Reservation(String reservationName) {
this.reservationName = reservationName;
}
#Override
public String toString() {
return "Reservations{" +
"id=" + id +
", reservationName='" + reservationName + '\'' +
'}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
}
I can't figure out what is causing that problem as the interface is present, isn't it?
I am using a fresh starter from start.spring.io for release 1.4.7.RELEASE
By default, Spring is not parsing inner repositories, so you need to explicitly enable this functionality.
Just use #EnableJpaRepositories on your configuration class (or Application starter class, it is also a configuration on your case) with the flag considerNestedRepositories = true.
#SpringBootApplication
#EnableJpaRepositories(considerNestedRepositories = true)
public class SpringbootDemoApplication
EnableJpaRepository doc.

Spring Data MongoDB Annotation #CreatedDate isn't working, when ID is assigned manually

I'm trying to use auditing to save dateCreated and dateUpdated in my objects, but since I set ID manually, there's some additional work.
Following Oliver Gierke's suggestion in DATAMONGO-946
I'm trying to figure out how to correctly implement it.
As original poster in Jira task above, I've downloaded example from here https://github.com/spring-guides/gs-accessing-data-mongodb.git and modified it a bit:
package hello;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.domain.Persistable;
import java.util.Date;
public class Customer implements Persistable<String> {
#Id
private String id;
#CreatedDate
private Date createdDate;
#LastModifiedDate
private Date lastModifiedDate;
private String firstName;
private String lastName;
private boolean persisted;
public Customer() {
}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public void setPersisted(boolean persisted) {
this.persisted = persisted;
}
#Override
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#Override
public boolean isNew() {
return !persisted;
}
#Override
public String toString() {
return String.format(
"Customer[id=%s, createdDate=%s, lastModifiedDate=%s, firstName='%s', lastName='%s']",
id, createdDate, lastModifiedDate, firstName, lastName);
}
}
and
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
#SpringBootApplication
#EnableMongoAuditing
public class Application implements CommandLineRunner {
#Autowired
private CustomerRepository repository;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
repository.deleteAll();
// create a customer
Customer c = new Customer("Alice", "Smith");
c.setId("test_id");
// save a customer
repository.save(c);
// fetch all customers
System.out.println("Customers found with findAll():");
System.out.println("-------------------------------");
for (Customer customer : repository.findAll()) {
System.out.println(customer);
}
System.out.println();
// create another customer with same id
c = new Customer("Bob", "Smith");
c.setId("test_id");
c.setPersisted(true);
repository.save(c);
// fetch all customers
System.out.println("Customers found with findAll():");
System.out.println("-------------------------------");
for (Customer customer : repository.findAll()) {
System.out.println(customer);
}
System.out.println();
}
}
and a result of execution is this:
Customers found with findAll():
-------------------------------
Customer[id=test_id, createdDate=Wed Feb 24 00:43:47 WITA 2016, lastModifiedDate=Wed Feb 24 00:43:47 WITA 2016, firstName='Alice', lastName='Smith']
Customers found with findAll():
-------------------------------
Customer[id=test_id, createdDate=null, lastModifiedDate=Wed Feb 24 00:43:47 WITA 2016, firstName='Bob', lastName='Smith']
createdDate becomes null after object update.
What am I missing here? And how to correctly implement Persistable to make auditing work properly?
Your code is working as expected. After you've implemented Persistable you can see that #CreatedDate annotation is working.
Sure that createdDate is null on the second call of save because the object already exists in the database and you updated it with createdDate = null. As you can see from the documentation for #CreatedDate:
#CreatedDate annotation. This identifies the field whose value is set
when the entity is persisted to the database for the first time.
So not to overwrite your createdDate with null on the second call you should retrieve your customer from the database with c = repository.findOne("test_id"); and then update it.
Add #EnableMongoAuditing to the main method in your spring boot application.
The simplest solution is to add a version property (annotated with #Version) to your Customer class and leave it uninitialized. this will assign the value of 0 to any newly created object which in turn tells spring that this is a new object.
#Version private Long version;
Note: that this version will be automatically incremented upon each modifications on this object

Categories