how does Chronicle-wire support schema evolution? - chronicle

I am new to Chronicle-wire. In the document it claims support for "setting of fields to the default, if not available" in the schema evolution section.
Do we have an example of how this works?
I have an example of adding an array field to a simple Marshallable object. When reading the journals contains the old version of the object, how can we set a default value (eg. new String[0]) to the field instead of a null?

There're a few ways to achieve that, one example is below:
public class TestMarshallable implements Marshallable {
private long a;
private int b;
private String newField = "defaultValue";
#Override
public void readMarshallable(#NotNull WireIn wire) throws IORuntimeException {
a = wire.read("a").int64();
b = wire.read("b").int32();
if (wire.bytes().readRemaining() > 0)
newField = wire.read("newField").text();
}
}
In this example, it is assumed that your new field will be written last, hence you can simply check if there's more to read - and do so. Default value is the one you assign to the field.
More complicated, but way more flexible way:
public class TestMarshallable implements Marshallable {
private long a = 0;
private int b = 1;
private String newField = "defaultValue";
#Override
public void readMarshallable(#NotNull WireIn wire) throws IORuntimeException {
#NotNull StringBuilder name = new StringBuilder();
while (!wire.isEmpty()) {
#NotNull ValueIn in = wire.read(name);
if (StringUtils.isEqual(name, "a"))
a = in.int64();
else if (StringUtils.isEqual(name, "b"))
b = in.int32();
else if (StringUtils.isEqual(name, "newField"))
newField = in.text();
else
unexpectedField(name, in);
wire.consumePadding();
}
}
}
In the last example readMarshallable simply overwrites the fields it could find in the stream leaving others with default values (NB this can also be used to save certain amount of writes, if you often write default values you can skip them altogether in writeMarshallable)

Related

How to sum multiple fields of a list of objects using stream in java

I'm trying to get the sums of two fields from a Model class. and return it using using a pojo but kept getting syntax errors. What I am trying to achieve is similar to the highest voted answer in This: Summing multiple different fields in a list of objects using the streams api? but I got syntax error. Here is my model:
public class BranchAccount {
#NotNull(message = "Account balance is required")
private Double accountBalance;
#NotNull(message = "Profit is required")
private Double profit;
#Temporal(TemporalType.TIMESTAMP)
private Date dateCreated;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated;
}
My Pojo:
public class ProfitBalanceDto {
private Double accountBalance;
private Double profit;
}
My code to get sum of accountBalance and profit from BranchAccount:
public ProfitBalanceDto getAllBranchAccount() {
List<BranchAccount> branchAccounts = branchAccountRepository.findAll();
branchAccounts.stream()
.reduce(new ProfitBalanceDto(0.0, 0.0), (branchAccount1, branchAccount2) -> {
return new ProfitBalanceDto(
branchAccount1.getAccountBalance() + branchAccount2.getAccountBalance(),
branchAccount1.getProfit() + branchAccount2.getProfit());
});
return null;
}
My errors:
Please am I doing wrong?
PS: I want to use stream for this.
As #Holger mentioned in his comment, you can map to ProfitBalanceDto before reducing
public ProfitBalanceDto getAllBranchAccount2() {
List<BranchAccount> branchAccounts = branchAccountRepository.findAll();
return branchAccounts.stream()
.map(acc -> new ProfitBalanceDto(acc.getAccountBalance(), acc.getProfit()))
.reduce(new ProfitBalanceDto(0.0, 0.0),
(prof1, prof2) -> new ProfitBalanceDto(prof1.getAccountBalance()+ prof2.getAccountBalance(),
prof1.getProfit() + prof2.getProfit()));
}
If you are using Java 12 or higher using the teeing collector might be a better option
public ProfitBalanceDto getAllBranchAccount() {
List<BranchAccount> branchAccounts = branchAccountRepository.findAll();
return branchAccounts.stream()
.collect(Collectors.teeing(
Collectors.summingDouble(BranchAccount::getAccountBalance),
Collectors.summingDouble(BranchAccount::getProfit),
ProfitBalanceDto::new));
}

How can I save a SparseArray in a Room database?

I'm trying to persist a SparseArray in a Room database and can not get it to compile. I keep getting the "Not sure how to convert a Cursor to this method's return type" error message along with "The query returns some columns [plannerLineData] which are not use by android.util.SparseArray."
I have tried using a single field in the PlannerLine Entity alone with a separate PlannerLineData class.
I have data converters to convert SparseArray to String and to convert String back to SparseArray.
I have checked several questions on stackoverflow and have successfully used the Date to Long and the Long to Date converters in other projects, but I seem to be missing something somewhere.
Data Files:
#Entity
public class PlannerLine implements Serializable {
private static final long serialVersionUID = 1L;
#TypeConverters(Converters.class)
#PrimaryKey
#SerializedName("planner_line")
#NonNull
public SparseArray plannerLineData;
public SparseArray getPlannerLineData() {
return plannerLineData;
}
public void setPlannerLineData(SparseArray plannerLineData) {
this.plannerLineData = plannerLineData;
}
public class PlannerLineData implements Serializable {
#SerializedName("lineId")
public int lineId;
#SerializedName("plan_text")
public String planText;
public int getLineId() {
return lineId;
}
public void setLineId(int lineId) {
this.lineId = lineId;
}
public String getPlanText() {
return planText;
}
public void setPlanText(String planText) {
this.planText = planText;
}
}
DAO problem area:
#Dao
public interface PlannerDao {
#Query("SELECT * from PlannerLine")
public SparseArray getPlannerLine(); <---Doesn't like this line
I have also tried returning SparseArray<PlannerLine> and SparseArray<PlannerLineData>, but no joy.
Converters class:
public class Converters {
#TypeConverter
public static String sparseArrayToString(SparseArray sparseArray) {
if (sparseArray == null) {
return null;
}
int size = sparseArray.size();
if (size <= 0) {
return "{}";
}
StringBuilder buffer = new StringBuilder(size * 28);
buffer.append('{');
for (int i = 0; i < size; i++) {
if (i > 0) {
buffer.append("-,- ");
}
int key = sparseArray.keyAt(i);
buffer.append(key);
buffer.append("-=-");
Object value = sparseArray.valueAt(i);
buffer.append(value);
}
buffer.append('}');
return buffer.toString();
}
#TypeConverter
public static SparseArray stringToSparseArray(String string) {
if (string == null) {
return null;
}
String entrySeparator = "-=-";
String elementSeparator = "-,-";
SparseArray sparseArray = new SparseArray();
String[] entries = StringUtils.splitByWholeSeparator(string, elementSeparator);
for (int i = 0; i < entries.length; i++) {
String[] parts = StringUtils.splitByWholeSeparator(entries[i], entrySeparator);
int key = Integer.parseInt(parts[0]);
String text = parts[1];
sparseArray.append(key, text);
}
return sparseArray;
}
Suggestions would be appreciated. Thanks
Edit:
My original vision for this app was to store all the plan lines in a single SparseArray, along with two additional SparseIntArrays (which I did not mention before because the solution would be similar to the SparseArray) to hold info on how the plan lines interact with each other.
After reading through #dglozano's helpful responses, I have decided to re-design the app to just store regular DB files in Room and load the data into the SparseArray (and the two SparseIntArrays) at startup, use only the in memory SparseArray and SparseIntArrays while the app is active, then write changes in the Sparse Arrays to the DB during onStop(). I am also considering updating the DB in the background as I work through app.
Because the answers and suggestions provided by #dglozano led me to the re-design decision, I am accepting his answer as the solution.
Thanks for the help.
It seems that you are doing the Conversion properly. However, the problem is in your DAO Query:
#Query("SELECT * from PlannerLine") // This returns a List of PlannerLine, not a SparseArray
public SparseArray getPlannerLine(); // The return type is SparseArray, not a List of PlannerLine
Therefore, you can try two different things:
1 - Change the Query to #Query("SELECT plannerLineData FROM PlannerLine WHERE lineId == :lineId") , so that the query returns the SparseArray inside the PlannerLine with id lineId. You should change the method signature so it accepts the parameter lineId
#Query("SELECT plannerLineData FROM PlannerLine WHERE lineId == :lineId")
public SparseArray getPlannerLine(int lineId);
2 - If you want to return the full PlannerLine object and then access to its SparseArray field, then you should change the return type. You should also add the lineId parameter to return just one record and not a list of all the PlannerLine stored in the database table.
#Query("SELECT * FROM PlannerLine WHERE lineId == :lineId")
public PlannerLine getPlannerLine(int lineId);
UPDATE
If you want to get a List<PlannerLine> with all the PlannerLine stored in the database, use the following query in your Dao.
#Query("SELECT * FROM PlannerLine")
public List<PlannerLine> getAllPlannerLines();
Then you can access to the SparseArray of each PlannerLine in the list as usual.

Add data to database from Controller, different methods but same row

I have an entity model, for simplification purposes let's say it looks like this :
public class Results {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long firstUser;
private Long secondUser;
private Double average;
private Double median;
private Double score;
}
This is my ResultsService Implementation:
public class ResultsServiceImpl implements ResultsService{
#Autowired
private CalculateDataRepository calculateDataRepository;;
#Autowired
private ResultsService resultsService;
Results results=new Results();
public void Average(Long id1, Long id2){
UserData firstClient = calculateDataRepository.findOne(id1);
userData secondClient = calculateDataRepository.findOne(id2);
clientId = firstClient.getClient().getId();
secondId = secondClient.getClient().getId();
Double average=(firstClient.getA()+secondClient.getA())/2;
results.setAverage(average);
}
public void Score(Long id1, Long id2){
SurveyData firstClient = surveyDataRepository.findOne(id1);
SurveyData secondClient = surveyDataRepository.findOne(id2);
clientId = firstClient.getClient().getId();
secondId = secondClient.getClient().getId();
Double average=(firstClient.getB()+secondClient.getB());
results.setScore(average);
results.setFirstUser(clientId );
results.setSecondUser(secondId );
resultsService.save(results);
}
....
I tried declaring Results results=new Results(); inside every method, but when I save them they get saved in different rows, instead of the same one.
How do I hold the reference so that when I call the setter of a field in one function, it's in the same row as the setter of a field in the other function.
To keep the problem focused, I tried to avoid showing the implementation of calculateDataRepository which is just the repository of an entity where some results are saved for different users.
The Results Method has no foreign field reference nor a reference from somewhere else, as there are fields firstUser and secondUser which I set from one of the methods;
Thank you.
Edit:
Results results=resultsService.findByFirstUserAndSecondUser(clientId, secondId);
if(results==null) {
results= new Results();
// Store to db ?
}
results.setAverage();
resultsService.save(results);
Actually you need a method in ResultsRepository
Results findByFirstAndSecond(Long first, Long second);
In the each Average and Score methods (BTW Java naming convention requires to have method names start from lowercase letter) call the findByFirstAndSecond(id1, id2)
If the method returns null (no such result) create a new instance and save in the DB (INSERT). If some Results is returned store the info there and save changes in DB (UPDATE).

Guarantee unique number generation with spring data and hibernate

I'm developing a spring mvc webapp with spring data and hibernate.
I've an entity composed by a boolean field and by an Integer field.
At beginning the boolean field is false and Integer field is null.
When boolean field become true I need to assign to the Integer field a unique value equals to MAX(seq) +1
To do it, I write my service method in this way:
#Override
public synchronized Obj save(Obj entry) {
if (entry.getBool() && entry.getSeq() == null){
Integer seq = objRepository.getLastSeq();
if (seq == null){
seq = 1;
}
entry.setSeq(seq);
}
return entry.save(entry);
}
And in my Reposiroty:
#Query("select MAX(seq)+1 FROM Obj")
Integer getLastSeq();
I put synchronized keyword to service method in order to be sure that only a thread at a time can get an unique MAX+1 number.. but I'm not sure if it works in all situation and if it is the right way.
Can I be sure that it guarantee unicity of seq?
Thank you
Marco
It seems like your Integer is entry id or isn't it ? So why not to use database sequence, with #id adnotation for example :
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
Example how to use initializing bean for that purpose.
#Service
public class SequenceInitializer implements InitializingBean{
#Autowired
private ObjRepository objRepository;
#Autowired
private Service service;
#Override
public void afterPropertiesSet() throws Exception {
try {
Integer max = objRepository.getLastSeq();
service.setLastSeq(max);
} catch(Exception e){...}
}
In your service setLastSeq will set AtomicInteger field.

Spring Data MongoDB: Accessing and updating sub documents

First experiments with Spring Data and MongoDB were great. Now I've got the following structure (simplified):
public class Letter {
#Id
private String id;
private List<Section> sections;
}
public class Section {
private String id;
private String content;
}
Loading and saving entire Letter objects/documents works like a charm. (I use ObjectId to generate unique IDs for the Section.id field.)
Letter letter1 = mongoTemplate.findById(id, Letter.class)
mongoTemplate.insert(letter2);
mongoTemplate.save(letter3);
As documents are big (200K) and sometimes only sub-parts are needed by the application: Is there a possibility to query for a sub-document (section), modify and save it?
I'd like to implement a method like
Section s = findLetterSection(letterId, sectionId);
s.setText("blubb");
replaceLetterSection(letterId, sectionId, s);
And of course methods like:
addLetterSection(letterId, s); // add after last section
insertLetterSection(letterId, sectionId, s); // insert before given section
deleteLetterSection(letterId, sectionId); // delete given section
I see that the last three methods are somewhat "strange", i.e. loading the entire document, modifying the collection and saving it again may be the better approach from an object-oriented point of view; but the first use case ("navigating" to a sub-document/sub-object and working in the scope of this object) seems natural.
I think MongoDB can update sub-documents, but can SpringData be used for object mapping? Thanks for any pointers.
I figured out the following approach for slicing and loading only one subobject. Does it seem ok? I am aware of problems with concurrent modifications.
Query query1 = Query.query(Criteria.where("_id").is(instance));
query1.fields().include("sections._id");
LetterInstance letter1 = mongoTemplate.findOne(query1, LetterInstance.class);
LetterSection emptySection = letter1.findSectionById(sectionId);
int index = letter1.getSections().indexOf(emptySection);
Query query2 = Query.query(Criteria.where("_id").is(instance));
query2.fields().include("sections").slice("sections", index, 1);
LetterInstance letter2 = mongoTemplate.findOne(query2, LetterInstance.class);
LetterSection section = letter2.getSections().get(0);
This is an alternative solution loading all sections, but omitting the other (large) fields.
Query query = Query.query(Criteria.where("_id").is(instance));
query.fields().include("sections");
LetterInstance letter = mongoTemplate.findOne(query, LetterInstance.class);
LetterSection section = letter.findSectionById(sectionId);
This is the code I use for storing only a single collection element:
MongoConverter converter = mongoTemplate.getConverter();
DBObject newSectionRec = (DBObject)converter.convertToMongoType(newSection);
Query query = Query.query(Criteria.where("_id").is(instance).and("sections._id").is(new ObjectId(newSection.getSectionId())));
Update update = new Update().set("sections.$", newSectionRec);
mongoTemplate.updateFirst(query, update, LetterInstance.class);
It is nice to see how Spring Data can be used with "partial results" from MongoDB.
Any comments highly appreciated!
I think Matthias Wuttke's answer is great, for anyone looking for a generic version of his answer see code below:
#Service
public class MongoUtils {
#Autowired
private MongoTemplate mongo;
public <D, N extends Domain> N findNestedDocument(Class<D> docClass, String collectionName, UUID outerId, UUID innerId,
Function<D, List<N>> collectionGetter) {
// get index of subdocument in array
Query query = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query.fields().include(collectionName + "._id");
D obj = mongo.findOne(query, docClass);
if (obj == null) {
return null;
}
List<UUID> itemIds = collectionGetter.apply(obj).stream().map(N::getId).collect(Collectors.toList());
int index = itemIds.indexOf(innerId);
if (index == -1) {
return null;
}
// retrieve subdocument at index using slice operator
Query query2 = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query2.fields().include(collectionName).slice(collectionName, index, 1);
D obj2 = mongo.findOne(query2, docClass);
if (obj2 == null) {
return null;
}
return collectionGetter.apply(obj2).get(0);
}
public void removeNestedDocument(UUID outerId, UUID innerId, String collectionName, Class<?> outerClass) {
Update update = new Update();
update.pull(collectionName, new Query(Criteria.where("_id").is(innerId)));
mongo.updateFirst(new Query(Criteria.where("_id").is(outerId)), update, outerClass);
}
}
This could for example be called using
mongoUtils.findNestedDocument(Shop.class, "items", shopId, itemId, Shop::getItems);
mongoUtils.removeNestedDocument(shopId, itemId, "items", Shop.class);
The Domain interface looks like this:
public interface Domain {
UUID getId();
}
Notice: If the nested document's constructor contains elements with primitive datatype, it is important for the nested document to have a default (empty) constructor, which may be protected, in order for the class to be instantiatable with null arguments.
Solution
Thats my solution for this problem:
The object should be updated
#Getter
#Setter
#Document(collection = "projectchild")
public class ProjectChild {
#Id
private String _id;
private String name;
private String code;
#Field("desc")
private String description;
private String startDate;
private String endDate;
#Field("cost")
private long estimatedCost;
private List<String> countryList;
private List<Task> tasks;
#Version
private Long version;
}
Coding the Solution
public Mono<ProjectChild> UpdateCritTemplChild(
String id, String idch, String ownername) {
Query query = new Query();
query.addCriteria(Criteria.where("_id")
.is(id)); // find the parent
query.addCriteria(Criteria.where("tasks._id")
.is(idch)); // find the child which will be changed
Update update = new Update();
update.set("tasks.$.ownername", ownername); // change the field inside the child that must be updated
return template
// findAndModify:
// Find/modify/get the "new object" from a single operation.
.findAndModify(
query, update,
new FindAndModifyOptions().returnNew(true), ProjectChild.class
)
;
}

Resources