Mapping BIGDECIMAL from Mongo, lack CONSTRUCTOR - json

I have a problem when I want get a object from mongo with a BigDecimal field.
I have the next structure in mongo:
{
"_id":ObjectId("546b07420c74bf96c7c3cd5f"),
"accountId":"1",
"modelVersion":"seasonal_optimized",
"yearMonth":"20143",
"income":{
"unscaled":{
"$numberLong":"68500"
},
"scale":2
},
"expense":{
"unscaled":{
"$numberLong":"125900"
},
"scale":2
}
}
And the entity is :
#Data
#NoArgsConstructor
#AllArgsConstructor
#Document(collection = "forecasts")
public class Forecast {
private String accountId;
private LocalDate monthYear;
private String modelVersion;
private BigDecimal income;
private BigDecimal expense;
}
and I'm trying retrieving a object from mongo, but I got the next error:
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate java.math.BigDecimal using constructor NO_CONSTRUCTOR with arguments.
Anybody can help me?
Thank you!!!!

It's an old question but I had the same issue using Spring data mongo and the aggregation framework.
Specifically when I want a BigDecimal as return type.
A possible workaround is to wrap your BigDecimal in a class and provide it as your return OutPutType, ie:
#Data
public class AverageTemperature {
private BigDecimal averageTemperature;
}
#Override
public AverageTemperature averageTemperatureByDeviceIdAndDateRange(String deviceId, Date from, Date to) {
final Aggregation aggregation = newAggregation(
match(
Criteria.where("deviceId")
.is(deviceId)
.andOperator(
Criteria.where("time").gte(from),
Criteria.where("time").lte(to)
)
),
group("deviceId")
.avg("temperature").as("averageTemperature"),
project("averageTemperature")
);
AggregationResults<AverageTemperature> result = mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(YourDocumentClass.class), AverageTemperature.class);
List<AverageTemperature> mappedResults = result.getMappedResults();
return (mappedResults != null ? mappedResults.get(0) : null);
}
In the example above the aggregation calculates the average temperature as BigDecimal.
Keep in mind that the default BigDecimal converter map it to a string in mongodb when saving. mapping-conversion
Retrieving the document as is should not be a problem anymore with Spring data mongo, versions I used:
org.springframework.boot:spring-boot-starter-data-mongodb:jar:2.0.1.RELEASE
spring-data-mongodb:jar:2.0.6.RELEASE
org.mongodb:mongodb-driver:jar:3.6.3

Related

How to access nested Json Object Value into JPA Entity Class

I have a payload like this
{
"eventId":"ep9_0579af51",
"eventTime":"5/11/2022 5:50:58 PM",
"eventType":"UpdateTransaction",
"meta":{
"userId":"vkp",
"resourceType":"Transaction/DataDocs"
}
}
I need to map this json fields into a single entity class .
#PostMapping(path = "/id", consumes = "application/json")
public ResponseEntity<ImportTrans> import(#RequestBody ImportTrans importTrans) {
return ResponseEntity.of(Optional.ofNullable(repository.save(importTrans););
}
#Table(name = "IMPORT_TRANS")
#Entity
public class ImportTrans implements Serializable {
#Id
private Long processId;// AutoGenerator
private String eventId;
private Date eventTime;
private String eventType;
private String userId; // I dont want create new class for meta . Is there any way i
//can access meta.userId in ImportTrans class.
private String resourceType;
}
How can I access data from meta from ImportTrans without creating a separate class for it?
You should modify your request body before reaching the controller.
"You must consider the application performance factors on your own
before implementation"
Option 1. Using RequestBodyAdvice.
Option 2. Using Spring HandlerInterceptor.
Option 3. Use AOP
Option 4. Using HTTP Filter.
The below solution only works if you are using a separate DTO class.
private Map<String, String> meta = new HashMap<>();
String userID = importTrans.getMeta().get("userId");
I hope the above pieces of information answered your question.

Mongojack: invalid hexadecimal representation of an ObjectId

Goal
I am trying to push some data to a mongo db using mongojack.
I expect the result to be something like this in the db:
{
"_id": "840617013772681266",
"messageCount": 69420,
"seedCount": 18,
"prefix": "f!",
"language": "en"
}
Problem
Instead, I get this error in my console.
Caused by: java.lang.IllegalArgumentException: invalid hexadecimal representation of an ObjectId: [840617013772681266]
at org.bson.types.ObjectId.parseHexString(ObjectId.java:390)
at org.bson.types.ObjectId.<init>(ObjectId.java:193)
at org.mongojack.internal.ObjectIdSerializer.serialiseObject(ObjectIdSerializer.java:66)
at org.mongojack.internal.ObjectIdSerializer.serialize(ObjectIdSerializer.java:49)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770)
... 59 more
Code
This is the code that gets called when I try to create a new Guild in the db:
public static Guild getGuild(String id) throws ExecutionException {
return cache.get(id);
}
cache is the following (load get executed):
private static LoadingCache<String, Guild> cache = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(
new CacheLoader<>() {
#Override
public Guild load(#NotNull String id) {
return findGuild(id).orElseGet(() -> new Guild(id, "f!"));
}
});
The findGuild method that gets called first:
public static Optional<Guild> findGuild(String id) {
return Optional.ofNullable(guildCollection.find()
.filter(Filters.eq("_id", id)).first());
}
And finally the Guild document.
#Getter
#Setter
public class Guild implements Model {
public Guild(String id, String prefix) {
this.id = id;
this.prefix = prefix;
}
public Guild() {
}
private String id;
/*
If a Discord guild sent 1,000,000,000 messages per second,
it would take roughly 292471 years to reach the long primitive limit.
*/
private long messageCount;
private long seedCount;
// The default language is specified in BotValues.java's bot.yaml.
private String language;
private String prefix;
#ObjectId
#JsonProperty("_id")
public String getId() {
return id;
}
#ObjectId
#JsonProperty("_id")
public void setId(String id) {
this.id = id;
}
}
What I've tried
I've tried multiple things, such as doing Long.toHexString(Long.parseLong(id)) truth is I don't understand the error completely and after seeing documentation I'm left with more questions than answers.
ObjectId is a 12-byte value that is commonly represented as a sequence of 24 hex digits. It is not an integer.
You can either create ObjectId values using the appropriate ObjectId constructor or parse a 24-hex-digit string. You appear to be trying to perform an integer conversion to ObjectId which generally isn't a supported operation.
You can technically convert the integer 840617013772681266 to an ObjectId by zero-padding it to 12 bytes, but standard MongoDB driver tooling doesn't do that for you and considers this invalid input (either as an integer or as a string) for conversion to ObjectId.
Example in Ruby:
irb(main):011:0> (v = '%x' % 840617013772681266) + '0' * (24 - v.length)
=> "baa78b862120032000000000"
Note that while the resulting value would be parseable as an ObjectId, it isn't constructed following the ObjectId rules and thus the value cannot be sensibly decomposed into the ObjectId components (machine id, counter and a random value).

How to update only few fields of entity using JPA and Hibernate?

I'm using MySQL DB.
My entity for the table is Account with the following fields:
id(long), balance (double), created_on(Date), currency(Enum).
When I'm doing a PUT request to update the account, I pass in the request body JSON.
I want to update, for example, only the balance, but the other columns' values to be saved.
In that case (I'm not passing the currency type) the balance is updated, but the currency has value NULL. Is that because it's enum?
I've tried using #DynamicUpdate annotation, but still, it doesn't have any change.
#RestController
public class AccountController {
#PutMapping("/accounts/{id}")
public void updateAccount(\#PathVariable long id, #RequestBody AccountDto accountDto) {
accountService.updateAccount(id, accountDto);
}
}
I'm using AccountDto (which I pass in the request body) and I'm calling the accountService
public void updateAccount(long id, AccountDto accountDto) {
Account account = accountRepository.getOne(id);
account.fromDto(accountDto);
this.accountRepository.save(account); }),
which calls the AccountRepository
public void fromDto(AccountDto accountDto) {
this.balance = accountDto.getBalance();
this.currency = accountDto.getCurrency();
}
Here is the AccountDto class:
public class AccountDto {
private long id;
#NotNull #PositiveOrZero
private double balance;
#NotNull #Enumerated(EnumType.STRING)
private Currency currency;
}
You need to perform a select query on Account entity and then update only the desired fields.
(Eg - making assumptions of my own of underlying method being used for accessing DB)
public updateAccount(AccountModel jsonBody) {
Account entity = accountRepository.findById(jsonBody.getAccountId());
entity.setBalance(jsonBody.getBalance());
accountRepository.save(entity);
}
If you get null as currency in the JSON you shouldn't update it:
So fromDto must look like:
public void fromDto(AccountDto accountDto) {
this.balance = accountDto.getBalance();
if (accountDto.getCurrency() != null) {
this.currency = accountDto.getCurrency();
}
}

Flink Kafka - Custom Class Data is always null

Custom Class
Person
class Person
{
private Integer id;
private String name;
//getters and setters
}
Kafka Flink Connector
TypeInformation<Person> info = TypeInformation.of(Person.class);
TypeInformationSerializationSchema schema = new TypeInformationSerializationSchema(info, new ExecutionConfig());
DataStream<Person> input = env.addSource( new FlinkKafkaConsumer08<>("persons", schema , getKafkaProperties()));
Now if I send the below json
{ "id" : 1, "name": Synd }
through Kafka Console Producer, the flink code throws null pointer exception
But if I use SimpleStringSchema instead of CustomSchema as defined before, the stream is getting printed.
What is wrong in the above setup
The TypeInformationSerializationSchema is a de-/serialization schema which uses Flink's serialization stack and, thus, also its serializer. Therefore, when using this SerializationSchema Flink expects that the data has been serialized with Flink's serializer for the Person type.
Given the excerpt of the Person class, Flink will most likely use its PojoTypeSerializer. Feeding JSON input data won't be understood by this serializer.
If you want to use JSON as the input format, then you have to define your own DeserializationSchema which can parse JSON into Person.
Answer for who have the same question
Custom Serializer
class PersonSchema implements DeserializationSchema<Person>{
private ObjectMapper mapper = new ObjectMapper(); //com.fasterxml.jackson.databind.ObjectMapper;
#Override
public Person deserialize(byte[] bytes) throws IOException {
return mapper.readValue( bytes, Person.class );
}
#Override
public boolean isEndOfStream(Person person) {
return false;
}
#Override
public TypeInformation<Person> getProducedType() {
return TypeInformation.of(new TypeHint<Person>(){});
}
}
Using the schema
DataStream<Person> input = env.addSource( new FlinkKafkaConsumer08<>("persons", new PersonSchema() , getKafkaProperties()));

jerse Client using JacksonJsonProvider date deserialization

I have a problem with date deserialization on client side. I have to build a simple desktop java aplication that consumes JSON. My code:
ClientConfig config = new DefaultClientConfig();
config.getClasses().add(JacksonJsonProvider.class);
Client client = Client.create(config);
I've tried to use this solution but it doesn't work for me:
How to deserialize JS date using Jackson?
I need a date in this format: "dd.MM.yyyy.", but I'm always getting this error no matter what:
Can not construct instance of java.util.Date from String value '12.10.1971.': not a valid representation (error: Can not parse date "12.10.1971.": not compatible with any of standard forms ("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd"))
Thank you for your help.
I'm still thinking your linked answer should work, but here is another way which could help you.
Create a Java object for the model you are retrieving.
Let's say it is an item with 2 fields:
public class Item {
private String name;
private String lastModified;
public Item() {}
public String getName() {
return name;
}
public Item setName(String name) {
this.name = name;
return this;
}
public String getLastModified() {
return lastModified;
}
public Modifiable setLastModified(String lastModified) {
this.lastModified = lastModified;
return this;
}
}
Jackson wouldn't try to parse it, because it would have a look into your code and knows it is a string not a date object.
You could than parse it yourself.
If this is to ugly you could hold the lastModified as a date internally, because Jackson is looking for "factory" methods which are taking as a parameter a string, if no date one could be found.