Advanced Query mongodb morphia List in List in Object - java

How to get a list of Object, where the tla (three letter acronym) of an LoanboardEmployee equals the providedTla inside the list of borrowers inside the list of ActionStamps (preferably the last stamp)?
I have the following classes:
Object:
#Id
private String objectCode;
#Embedded
private List<ActionStamp> borrowingHistory;
...
ActionStamp (used in Object)
#Reference
private LoanboardEmployee responsible;
#Reference
private List<LoanboardEmployee> borrowers;
...
LoanboardEmployee (used in ActionStamp)
#Id
private short id;
private String tla;
...
Now I want to find all Object, where 1 of the tla's of the borrowers in the last ActionStamp equals a given tla.
So far i have this:
// Select only Object with type Object.class
Query<Object> q = ds.createQuery(Object.class);
// Query the Object only that have a borrowingHistory
q.field("borrowingHistory").exists();
// Query the Object only that have borrowers in the borrowingHistory
q.field("borrowingHistory.borrowers").exists();
// This is where I'm stuck
q.field("borrowingHistory.borrowers.tla").hasThisOne(providedTla);
I get the following Error:
com.google.code.morphia.query.ValidationException: Can not use dot-notation past 'borrowers' could not be found in 'nl.technolution.loanboard.datamodel.entity.Object' while validating - borrowingHistory.borrowers.tla
How should I solve this query?

Related

Google Cloud Objectify - Error saving entity

I want to save an entity and seems to be falling because I am trying to index a HashMap which has a list.
Here are my classes:
**IndicadorEntity **
#Entity
public final class IndicadorEntity {
private #Index Map<String, List<ObjetivoEntity>> objetivos;
}
ObjetivoEntity
package com.eulen.google.efqm.datastore.entities;
import java.util.Date;
public final class ObjetivoEntity {
private double objetivo;
private boolean variable;
private Date fechaCreacion;
}
When trying to save IndicadorEntity I get the following error:
com.googlecode.objectify.SaveException: Error saving com.eulen.google.efqm.datastore.entities.IndicadorEntity#694e7f0b: objetivos.2: java.util.ArrayList is not a supported property type.
If I remove #Index it works, but I need to know which IndicadorEntity has null objetivos.
Thanks.
There are limits to what the datastore will index. However, you can almost always work around this by creating a synthetic index.
For example, you can make a field private List<String> myCustomIndex and populate it with an onSave method, filling it with all the things you want to be able to search for. You can extract information from objects at any depth in your object hierarchy.
Then query on your custom index: filter("myCustomIndex", somevalue)

How to sort on a field in indexedEmbedded

I have an indexedEmbedded object with #OneToMany relation, inside a class, and want to sort with a field containded in that object.
When i call my method for search i got this exception:
"Unexpected docvalues type NONE for field 'employees.id_forSort' (expected=NUMERIC). Use UninvertingReader or index with docvalues"
Thanks in advance!
Here is my code:
public Company {
....
#IndexedEmbedded(prefix = "students.", includeEmbeddedObjectId = true)
#OneToMany(mappedBy = "company")
private Set<Employee> employees= new HashSet<>();
}
public Employee{
....
#SortableField(forField = "id_forSort")
#Field(name = "id_forSort", analyze = Analyze.NO)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
public class CompanySearchRepository{
....
public List<Company> searchCompany(
DataTablePagination pagination, Long id, SearchCriteria searchCriteria) {
FilterField[] filterFields = validateFilterFields(searchCriteria.getFilterFields());
// Sorting
String sortField = "employees.id";
Sort sort = getSort(sortField, pagination.getSortDirection());
Query query;
if (filterFields.length > 0) {
query = getFilterableColumnsQuery(filterFields);
} else {
// global search
query = getGlobalSearchQuery(searchableFields, searchCriteria.getSearchTerm());
}
FullTextQuery fullTextQuery = getFullTextQuery(query);
initPagination(
fullTextQuery,
sort,
pagination.getPage(),
pagination.getPageSize());
List<Company> data = fullTextQuery.getResultList();
}
Sort getSort(String sortField, SortDirection sortDirection) {
SortFieldContext sortFieldContext =
getQueryBuilder().sort().byField(sortField.concat("_forSort"));
return sortDirection.equals(SortDirection.ASC)
? sortFieldContext.asc().createSort()
: sortFieldContext.desc().createSort();
}
}
You named your sortable field employees.id_forSort, but when searching, you're using another field for sorts: employees.id. Use the field that is intended for sorts. Replace String sortField = "employees.id"; with String sortField = "employees.id_forSort"; My bad, I didn't see the weird code that adds a suffix to the field name in the getSort method. Then the message is strange. Would your index be empty, by any chance?
Sorts on multi-valued fields are not supported. You will likely get different results from one execution to the other, since the search engine has to select a value to use for sorts, and which value is selected is undefined.
Regardless of the technical aspect, I'm not sure what you're trying to achieve. You're getting Company instances as a result, and want them to be sorted by Employee ID, of which there are many for each Company. What does it mean? You want the company with the oldest employee first? Something else? If you're just trying to stabilize the order of hits, I'd recommend using the company ID instead.

How to efficiently compare two objects of same Class and check which are the fields that differ?

I want to write a generic function that accepts two objects of same entity class and compares the fields that are different and returns List of all the changes made to particular fields along with time.
One among the many entity classes would be say Member as follows
public class Member {
String firstName;
String lastName;
String driverLicenseNumber;
Integer age;
LocalDateTime timestamp;
}
In the DB, I have a table called member_audit that gets populated with old data whenever there is a change in member table using triggers (Similarly for other entities).
The List of resource for each of the entity I would be returning is something like
public class MemberAuditsResource {
private String field;
private LocalDateTime on;
private String changeType;
private String oldValue;
private String newValue;
}
I can only think of writing a function for each entity separately like this
private List<MembeAuditsResource> memberCompare(Member obj1, Member obj2) {
//Compare every field in both the objects using if else and populate the resource.
}
And then calling the above function to compare every pair of record in the entity_audit table.
The code would be very large to compare every field and multiplied by different entities.
Is there a better and efficient way?
If you extend the ideas to compare the object graph , it is not a trivial problem. So, the efficient way is not to re-inventing the wheel but use an existing library such as JaVers :
Member oldMember = new Member("foo" ,"chan" ,"AB12" , 21 ,LocalDateTime.now());
Member newMember = new Member("bar" ,"chan" ,"AB12" , 22 ,LocalDateTime.now());
Diff diff = javers.compare(oldMember, newMember);
for(Change change: diff.getChanges()) {
System.out.println(change);
}
Then , you can get something like:
ValueChange{ 'firstName' changed from 'foo' to 'bar' }
ValueChange{ 'age' changed from '21' to '22' }
Convert both object to a Map using JSON objectMapper.convertValue method. Then you can easily compare the keys/values of the two maps and create a list of differences.

How to persist an enum with JPA?

I'm using MySQL and JPA.
I have an enum that has it's own table.
I have a field in an entity (entity1) that uses this enum. This field is annotated with: #Enumeration(EnumType.STRING).
1 - is it correct to persist this field in entity1 as a column in the db when it has it's own table?
2 - if I am using #JsonProperty on my other fields and answer to 1 is "yes", must I use #JsonProperty on the enum field too?
3 - what's the point in having the enum in a separate table?
Currently, with just the #Enumeration annotation and a column for the enum for entity1 I get error: was annotated as enumerated, but its java type is not an enum
1- How I addressed similar problem was as follows :
I defined the enum in a separate entity :
#Entity
#Table(name="CALC_METHOD")
public class CalculationMethod {
public CalculationMethod() {
super();
// TODO Auto-generated constructor stub
}
#Id
#Enumerated(EnumType.STRING)
#Column(name="METHOD_NAME")
private CalculationMethodId calcMethodID;
#Column(name="DISPLAY_TEXT")
private String displayName;
.
.
.
.
then I refered to it in another entity as follows :
#OneToOne
#JoinColumn(name="CALCULATION_METHOD",referencedColumnName="METHOD_NAME")
private CalculationMethod calculationMethod;
that way it's stored in a seprate table, yet referenced from another entity with no duplication ... the point here is that you can't map enum scoped variables so when I needed to store a display name for the enum value, I needed to make it a separate attribute as you see
3- why to store it in table? because from the java POV it was really an enum , and I want to apply some calculation methods polymorpically (like calculate some value in the refering entity using the calculation method, so I defined calculate() method for each calculation method , each with a different implementation then call it while calculating a whole) the I wanted it to be always read with the same value and display name from many places in the code , and If I want to modify the display name, it's done only # one place -thus consistency and maintainability-
2- it depends on the requirement and your json model
For your situation I normaly use an entity on BBDD for the ENUM like:
AuthenticationType
id, name, value : (0, CERT, Certificate)
Where name is the real ENUM and value is the text I want to represent on the views.
For that you need the following:
public enum AuthenticationTypeEnum{
CERT, PASS;
}
#Entity
#Table(name = "AuthenticationType")
public class AuthenticationType{
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
#Enumerated(EnumType.STRING)
private AuthenticationTypeEnum name; // REAL ENUM TYPE
#Column(name = "VALUE")
private String value;
....
}
#Entity...
class Authentication{
private String login;
...
#ManyToOne
private AuthenticationType type; // ENUM USE
...
}
In that way you can edit the value of your ENUM on BBDD without changing your code, for me this is one of the best options.
Hope this helps.
When you persist the entity use the cascade all on JPA to persist also the enum entity.
NOTE: On normal situations, the enums not change, so you set them only ones. They are a prerequisite to the application so they change on rare circumstances.

Map hibernate projections result to java POJO model

I've been using spring and hibernate for this past few weeks and I've always been learning something new there.
Right now I've got a problem that I want to solve with Projections in Hibernate.
Suppose there is a model Person and that model has many Car. The following are how the class definitions roughly gonna look like:
public class Person implements java.io.Serializable {
private Integer id;
private String name;
private List<Car> cars;
private Integer minYear; // Transient
private Integer maxYear; // Transient
}
public class Car implements java.io.Serializable {
private Integer id;
private Integer year;
}
The problem here is I want to get the minYear (maxYear) of each Person to be filled by the earliest year (latest year) of the cars they have.
Later I found a solution to use Projections but I stumbled upon org.hibernate.QueryException: could not resolve property: minYear of: model.Person and here is the code of the db operation:
Criteria criteria = sessionFactory.getCurrentSession().createCriteria("model.Person");
criteria.add(create(personInstance));
criteria.createAlias("minYear", "minYear");
criteria.setProjection(Projections.min("cars.year").as("minYear"));
Is there anyway to store the aggregation value in transient method using Projections because I just want to avoid using plain SQL and HQL as much as possible.
Never mind, I've found the solution.
First we need to create alias of the associated object like so
Criteria criteria = sessionFactory.getCurrentSession().createCriteria("model.Person");
criteria.createAlias("cars", "cars");
Select the needed using Hibernate Projections
ProjectionList projections = Projections.projectionList();
projections.add(Projections.property("id").as("id"));
projections.add(Projections.property("name").as("name"));
projections.add(Projections.property("cars").as("cars"));
Group the result based on the root entity (in this case using its id, Person.id), this is needed especially when used with aggregation to group the aggregation
projections.add(Projections.groupProperty("id"));
Use the aggregate function
projections.add(Projections.min("cars.year").as("minYear"));
projections.add(Projections.max("cars.year").as("maxYear"));
Set the projection
criteria.setProjection(projections);
Use result transformer AliasToBeanResultTransformer to map the result fields (as specified in step 2 & 4) to the POJO
criteria.setResultTransformer(new AliasToBeanResultTransformer(Person.class));
Get the result
List<Person> results = (List<Person>) criteria.list();

Categories

Resources