Query by Example Spring Data - spring

I have domain object Person with date fields:
public class Person {
#Id
private Long id;
private Date date
Build example like this:
Person person = new Person();
person.setSomeOtherFields("some fields");
Example<Person> example = Example.of(person);
How i can create example query with date range (search entity contains date greater or equal from some date and less or equal some another date)?

The Spring Data JPA query-by-example technique uses Examples and ExampleMatchers to convert entity instances into the underlying query. The current official documentation clarifies that only exact matching is available for non-string attributes. Since your requirement involves a java.util.Date field, you can only have exact matching with the query-by-example technique.
You could write your own ExampleMatcher that returns query clauses according to your needs.

Related

Spring data query not working properly, because the field is having Or in the name. Field name is - approvedOrRejectedBy

Spring data query not working properly, because the field is having Or in it.
The field is String approvedOrRejectedBy. So while writing the query it is confusing with the default Or.
The query I wrote is:
List<Object> findAllByTimeStampBetweenAndCameraSlugInAndApprovedOrRejectedBy(long startTime, long endTime, List<String> cameraSlugs, String id);
Because of Or present in the field name, it's throwing an error. Any suggestions or workaround on how to solve this issue, other than updating the filed name and DB mapping.
The query by method-name will interpret certain keywords from the name:
Subject Keywords like here findAllBy
Predicate Keywords like here And, Or, Between together with the property names
Since your field/property seems named as approvedOrRejectedBy you could work around by naming the object property differently and annotate it with the mapped DB-column name as given, e.g. #Column(name "ApprovedOrRejectedBy").
#Column(name "approvedOrRejectedBy")
String reviewedBy;
Then your query-method can be rewritten as
List<Object> findAllByTimeStampBetweenAndCameraSlugInAndReviewedBy(long startTime, long endTime, List<String> cameraSlugs, String id);
Alternatively, since the method name becomes hard to read, you could shorten the name and specify the SELECT on #Query annotation like this:
#Query("SELECT * FROM entityOrTable x WHERE x.timeStamp BETWEEN ?1 AND ?2 AND x.cameraSlug IN ?3 AND x.approvedOrRejectedBy = ?4")
List<Object> findByIntervalCameraSlugInAndReviewedBy(long startTime, long endTime, List<String> cameraSlugs, String id);
See also:
Spring-Data-Jpa Repository - Underscore on Entity Column Name
Spring Data JPA repository methods don't recognize property names with underscores
Try writing custom query using JPQL, like #Query annotation. Please see
https://www.baeldung.com/spring-data-jpa-query

Spring Data / Hibernate save entity with Postgres using Insert on Conflict Update Some fields

I have a domain object in Spring which I am saving using JpaRepository.save method and using Sequence generator from Postgres to generate id automatically.
#SequenceGenerator(initialValue = 1, name = "device_metric_gen", sequenceName = "device_metric_seq")
public class DeviceMetric extends BaseTimeModel {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "device_metric_gen")
#Column(nullable = false, updatable = false)
private Long id;
///// extra fields
My use-case requires to do an upsert instead of normal save operation (which I am aware will update if the id is present). I want to update an existing row if a combination of three columns (assume a composite unique) is present or else create a new row.
This is something similar to this:
INSERT INTO customers (name, email)
VALUES
(
'Microsoft',
'hotline#microsoft.com'
)
ON CONFLICT (name)
DO
UPDATE
SET email = EXCLUDED.email || ';' || customers.email;
One way of achieving the same in Spring-data that I can think of is:
Write a custom save operation in the service layer that
Does a get for the three-column and if a row is present
Set the same id in current object and do a repository.save
If no row present, do a normal repository.save
Problem with the above approach is that every insert now does a select and then save which makes two database calls whereas the same can be achieved by postgres insert on conflict feature with just one db call.
Any pointers on how to implement this in Spring Data?
One way is to write a native query insert into values (all fields here). The object in question has around 25 fields so I am looking for an another better way to achieve the same.
As #JBNizet mentioned, you answered your own question by suggesting reading for the data and then updating if found and inserting otherwise. Here's how you could do it using spring data and Optional.
Define a findByField1AndField2AndField3 method on your DeviceMetricRepository.
public interface DeviceMetricRepository extends JpaRepository<DeviceMetric, UUID> {
Optional<DeviceMetric> findByField1AndField2AndField3(String field1, String field2, String field3);
}
Use the repository in a service method.
#RequiredArgsConstructor
public class DeviceMetricService {
private final DeviceMetricRepository repo;
DeviceMetric save(String email, String phoneNumber) {
DeviceMetric deviceMetric = repo.findByField1AndField2AndField3("field1", "field", "field3")
.orElse(new DeviceMetric()); // create new object in a way that makes sense for you
deviceMetric.setEmail(email);
deviceMetric.setPhoneNumber(phoneNumber);
return repo.save(deviceMetric);
}
}
A word of advice on observability:
You mentioned that this is a high throughput use case in your system. Regardless of the approach taken, consider instrumenting timers around this save. This way you can measure the initial performance against any tunings you make in an objective way. Look at this an experiment and be prepared to pivot to other solutions as needed. If you are always reading these three columns together, ensure they are indexed. With these things in place, you may find that reading to determine update/insert is acceptable.
I would recommend using a named query to fetch a row based on your candidate keys. If a row is present, update it, otherwise create a new row. Both of these operations can be done using the save method.
#NamedQuery(name="getCustomerByNameAndEmail", query="select a from Customers a where a.name = :name and a.email = :email");
You can also use the #UniqueColumns() annotation on the entity to make sure that these columns always maintain uniqueness when grouped together.
Optional<Customers> customer = customerRepo.getCustomersByNameAndEmail(name, email);
Implement the above method in your repository. All it will do it call the query and pass the name and email as parameters. Make sure to return an Optional.empty() if there is no row present.
Customers c;
if (customer.isPresent()) {
c = customer.get();
c.setEmail("newemail#gmail.com");
c.setPhone("9420420420");
customerRepo.save(c);
} else {
c = new Customer(0, "name", "email", "5451515478");
customerRepo.save(c);
}
Pass the ID as 0 and JPA will insert a new row with the ID generated according to the sequence generator.
Although I never recommend using a number as an ID, if possible use a randomly generated UUID for the primary key, it will qurantee uniqueness and avoid any unexpected behaviour that may come with sequence generators.
With spring JPA it's pretty simple to implement this with clean java code.
Using Spring Data JPA's method T getOne(ID id), you're not querying the DB itself but you are using a reference to the DB object (proxy). Therefore when updating/saving the entity you are performing a one time operation.
To be able to modify the object Spring provides the #Transactional annotation which is a method level annotation that declares that the method starts a transaction and closes it only when the method itself ends its runtime.
You'd have to:
Start a jpa transaction
get the Db reference through getOne
modify the DB reference
save it on the database
close the transaction
Not having much visibility of your actual code I'm gonna abstract it as much as possible:
#Transactional
public void saveOrUpdate(DeviceMetric metric) {
DeviceMetric deviceMetric = metricRepository.getOne(metric.getId());
//modify it
deviceMetric.setName("Hello World!");
metricRepository.save(metric);
}
The tricky part is to not think the getOne as a SELECT from the DB. The database never gets called until the 'save' method.

How to use java.util.Date as #Id in mongo documents

Ok i found myself in a simple but annoying problem. My mongo documents are using java.util.Date as id, and as you might guess the id gets converted (spring converters) to ObjectId, I can't update these documents because every time a new ObjectId(Date) is created get a completely different id even though the date is the same...
how do i force mongo to just use java.util.Date as an id?
providing the sample code:
public void updateNode(...node..) {
final MongoTemplate mongoTemplate = ...
final String collectionName = ...
final Query query = (new Query()).addCriteria(Criteria.where("time").is(node.getTime()));
final Update update = Update.update("time", node.getTime()).set("top", node.getTop())
.set("bottom", node.getBottom()).set("mid", node.getMid())
.set("startTime", node.getStartTime()).set("potential", node.isPotential());
mongoTemplate.upsert(query, update, MyClassNode.class, collectionName);
}
if I ran this code for the first time the objects are inserted into the database but with ObjectId... if the node.getTime() is a java.sql.Date then everything is fine.
if the node.getTime() is not a java.sql.Date I cannot update the document if it exists: why? because everytime the document is prepared it creates a new ObjectId the update and query will have two different _id field values and update fails.
On checking the documentation , i found the following details :
In MongoDB, each document stored in a collection requires a unique _id
field that acts as a primary key. If an inserted document omits the
_id field, the MongoDB driver automatically generates an ObjectId for the _id field.
This also applies to documents inserted through update operations with
upsert: true.
The following are common options for storing values for _id:
Use an ObjectId.
Use a natural unique identifier, if available. This saves space and
avoids an additional index.
Generate an auto-incrementing number.
What i understood from the documentation was that to avoid inserting the same document more than once, only use upsert: true if the query field is uniquely indexed.So, if this flag is set , you will find your id converted using ObjectId() to make it unique.

Spring data - Order by multiplication of columns

I came to a problem where I need to put ordering by multiplication of two columns of entity, for the sake of imagination entity is:
#Entity
public class Entity {
#Column(name="amount")
private BigDecimal amount;
#Column(name="unitPprice")
private BigDecimal unitPrice;
.
.
.
many more columns
}
My repo interface implements JpaRepository and QuerydslPredicateExecutor,
but I am struggling to find a way to order my data by "amount*unitPrice",
as I can't find a way to put it into
PageRequest (new Sort.Order(ASC, "amount * unitPrice"))
without having PropertyReferenceException: No property amount * unitPrice... thrown.
I can't user named query, as my query takes quite massive filter based on user inputs (can't put where clause into query, because if user hasn't selected any value, where clause can't just be in query).
To make it simple. I need something like findAll(Predicate, Pageable), but I need to force that query to order itself by "amount * unitPrice", but also have my Preditate (filter) and Pageable (offset, limit, other sortings) untouched.
Spring Sort can be used only for sorting by properties, not by expressions.
But you can create a unique sort in a Predicate, so you can add this sort-predicate to your other one before you call the findAll method.

spring crud repository find top n Items by field A and field B in list order by field C

I have in a Spring Repo something like this:
findTop10ItemsByCategIdInOrderByInsertDateDesc(List ids)
I want the first 10 items where category id in list of ids ordered by insert date.
Another similar query:
findTop10ItemsByDomainIdAndCategIdInOrderByInsertDateDesc(List ids, #Param Integer domainId)
Here I want that the domain id is equal to the given param and the categId to be in given list.
I managed to resolve it using #Query but I wonder if there is an one liner for the above queries.
thanks
EDIT
The top works fine. Initially I had findTop10ItemsByDomainIdAndCategIdOrderByInsertDateDesc. Now I want the results from a list of category ids. That's the new requirement.
SECOND EDIT
My query works for find the set o results where domain id is equal to a given param and categ id is contained in a given list. BUT I found out that HQL doesn't support a setMaxResult kind of thing as top or limit.
#Query("select i from Items i where i.domainId = :domainId and i.categId in :categoryIds order by i.insertDate desc")
The params for this method were (#Param("domainid") Integer domainid,List<Integer> categoryIds) but it seams that I'm alowed to use either #Param annotation to each parameter or no #Param at all ( except for Pageable return; not my case )
I still don't know how to achieve this think:
extract top n elements where field a eq to param, field b in set of param, ordered by another field.
ps: sorry for tags but there is no spring-crudrepository :)
The method to resolve your problem is:
List<MyClass> findTop10ByDomainIdAndCategIdInOrderByInsertDateDesc(Long domainId, List<Long> ids);
Top10 limits the results to first 10.
ByDomainId limits results to those that have passed domainId.
And adds another constraint.
CategIdIn limits results to those entries that have categId in the passed List.
OrderByInsertDateDesc orders results descending by insert date before limiting to TOP10.
I have tested this query on the following example:
List<User> findTop10ByEmailAndPropInOrderByIdDesc(String email, List<Long> props);
Where User is:
private Long id;
private String username;
private String password;
private String email;
private Long prop;
Currently I would recommend using LocalDate or LocalDateTime for storing dates using Spring Data JPA.

Resources