Adding parameters optionally to Spring Data JPA Native query - spring

I am using Spring Data JPA with native queries like below
public interface ItemRepository extends JpaRepository<ItemEntity, Long> {
#Query(value = "select * from items i where i.category = :itemCategory and i.item_code = :itemCode", nativeQuery = true)
Page<ItemEntity> search(#Param("itemCategory") String itemCategory, #Param("itemCode") String itemCode, Pageable pageable);
}
Now, my use case is
It itemCode is available, only the item with that code from that category should be returned.
But if itemCode is not available then all items in that category should be returned.
So the problem with the above category is when itemCode is passed as NULL no data is returned since it does not match anything. whereas the requirement is it should be ignored.
So is there a way to optionally add a clause to Spring Data JPA native query. I know it is possible with CriteriaQuery but can we do something similar for native queries?
Thanks

Yes, it's feasible with native query too. Very Good explanation here read this
#Approach1
#NamedQuery(name = "getUser", query = "select u from User u"
+ " where (:id is null or u.id = :id)"
+ " And :username"
:itemCode is null or i.item_code = :itemCode
#Approach2
# UserDao.java
public User getUser(Long id, String usename) {
String getUser = "select u from user u where u.id " + Dao.isNull(id)
+ " And u.username " + Dao.isNull(username);
Query query = Dao.entityManager.createQuery(getUser);
}
# Dao.java
public static String isNull(Object field) {
if (field != null) {
if (field instanceof String) {
return " = " + "'" + field + "'";
} else {
return " = " + field;
}
} else {
return " is NULL ";
}
}
How to handle null value of number type in JPA named query

Just modify the where condition
i.item_code = :itemCode
to
:itemCode is null or i.item_code = :itemCode

Related

Spring Data JPA Query by Example with access to nested Objects Attributes

I use Query by Example and want to know how I can find objects with certain properties in the nested objects.
A plan anyone?
Here is my example Code:
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("offer2product.id.productId", match -> match.exact()
);
Offer2ProductId id = new Offer2ProductId();
id.setProductId(1337L);
Offer2Product offer2Product = new Offer2Product();
offer2Product.setId(id);
Set<Offer2Product> offer2productSet = new HashSet<>();
offer2productSet.add(offer2Product);
Offer probe = new Offer();
probe.setOffer2productSet(offer2productSet);
Example<Offer> example = Example.of(probe, matcher);
List<Offer> offerList = offerRepository.findAll(example);
Quoting Spring data documentation: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example
Currently, only SingularAttribute properties can be used for property matching.
In your example, you want to search by a property that is a Set<> (offer2productSet), which is a PluralAttribute - it is not possible to search by this field. It will be ignored when building a query, as can be seen here:
https://github.com/spring-projects/spring-data-jpa/blob/master/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java#L112
man actually can do it, follow the example below, where demand has a list of labels, I don't have time to explain everything but soon I'll update this post.
#Repository
#RequiredArgsConstructor
public class DemandaFilterRepository {
private final EntityManager entityManager;
public Page<Demanda> findAll(DemandaFilterDTO demandaFilter, Pageable pageable) {
String query = "select d from Demanda d where 1=1";
Map<String, Object> parameters = new HashMap<>();
if (demandaFilter.getId() != null) {
query += " and d.id = :id";
parameters.put("id", demandaFilter.getId());
}
if (!StringUtils.isEmpty(demandaFilter.getTenant())) {
query += " and upper(d.tenant) = upper(:tenant)";
parameters.put("tenant", demandaFilter.getTenant());
}
if (!StringUtils.isEmpty(demandaFilter.getAssunto())) {
query += " and upper(d.assunto) like upper(:assunto)";
parameters.put("assunto", "%" + demandaFilter.getAssunto() + "%");
}
if (!StringUtils.isEmpty(demandaFilter.getDescricao())) {
query += " and upper(d.descricao) like upper(:descricao)";
parameters.put("descricao", "%" + demandaFilter.getDescricao() + "%");
}
if (!StringUtils.isEmpty(demandaFilter.getEtiqueta())) {
query = query.replace("Demanda d", "Demanda d inner join d.etiquetas etiqueta");
query += " and upper(etiqueta.descricao) = upper(:etiqueta)";
parameters.put("etiqueta", demandaFilter.getEtiqueta());
}
if (!StringUtils.isEmpty(demandaFilter.getNomeDemandante())) {
query += " and upper(d.demandante.nome) like upper(:nomeDemandante)";
parameters.put("nomeDemandante", "%" + demandaFilter.getNomeDemandante() + "%" );
}
if (!StringUtils.isEmpty(demandaFilter.getDtInclusao())) {
query += " d.dtInclusao like :dtInclusao";
parameters.put("dtInclusao", "%" + demandaFilter.getDtInclusao() + "%");
}
query += " ORDER BY d.id DESC ";
TypedQuery<Demanda> typedQuery = entityManager.createQuery(query, Demanda.class)
.setMaxResults(pageable.getPageSize())
.setFirstResult(pageable.getPageNumber() * pageable.getPageSize());
parameters.forEach(typedQuery::setParameter);
return new PageImpl<>(typedQuery.getResultList());
}
}
this is a class for one of a small project that I'm working on ...

Database column data having linebreaks are ignored when retrieved from Hibernate?

A CLOB database column data has linebreaks :
When I retrieve it and display the data inside a html table cell then the linebreaks are ignored :
#Override
#Transactional
public String getPrevisionRessourceAutreForProduit(Objectif produit) {
String hql = "select to_char(r.ress_comment_1) " +
"from ressource r join type_ressource t on r.type_ress_code = t.type_ress_code " +
"left join objectif o on r.obj_code = o.obj_code " +
"where o.obj_code = " + produit.getCode().toString() + " and upper(t.type_ress_code) = 'AUT'";
Session sessionDynamic = Utils.createDynamicSession(env);
Query query = sessionDynamic.createSQLQuery(hql);
#SuppressWarnings("unchecked")
List<String> list = (List<String>) query.list();
sessionDynamic.close();
if (list.isEmpty())
return "";
else
return list.get(0) == null ? "" : list.get(0);
}
So how to fix it ?
I found the solution by enclosing the data inside the tags <pre>...</pre>

How can I make a HQL using Instant from java.time

I am developing a small project using spring-jpa. I decided to use java.time to deal with date problems. Everything was working fine until I had to deal with HQL.
So I am trying to make this query:
#Query("SELECT sensor "
+ "FROM TrashSensor sensor "
+ "join sensor.measurementList measurement "
+ "WHERE sensor.id = 100 AND measurement.instantOfMeasure > '2017-01-01'")
public TrashSensor findTrashSensorByIdCustom();
But the type of measurement.instantOfMeasure is a Instant from java.time.
So this last AND of my WHERE clause is always returning true so I don't get the filter I need.
How can I make this comparison using HQL and java.time? Does Hibernate support this??
You can develop it with
1 way -> (in TrashSensorRepository interface )
#Query("SELECT sensor "
+ "FROM TrashSensor sensor "
+ "join sensor.measurementList measurement "
+ "WHERE sensor.id =:sensorId AND measurement.instantOfMeasure > :instantOfMeasure")
public TrashSensor findTrashSensorByIdCustom(#Param("sensorId") sensorId, #Param("instantOfMeasure") Instant instantOfMeasure);
The types from the java.time package are directly mapped to corresponding SQL types(Since Java 8)
LocalDate is mapped to DATE
LocalTime and OffsetTime are mapped to TIME
Instant, LocalDateTime, OffsetDateTime and ZonedDateTime are mapped to TIMESTAMP
2 way -> (if you have created custom utility class for run HQL dynamically)
// Utility for run custom dynamic HQL query
#Service
#Transactional
public class HibernateQueryService {
private final Logger log = LoggerFactory.getLogger(HibernateQueryService.class);
private JpaContext jpaContext;
public List executeHibernateQuery(String sql, Class entity){
log.debug("HibernateQuery executing: "+sql);
Session session = jpaContext.getEntityManagerByManagedType(Article.class).unwrap(Session.class);
return session.createQuery(sql, entity).getResultList();
}
}
// Custom converter
public class CustomConverter{
public static Timestamp toTimestamp(Instant time){
return time != null ? Timestamp.from(time) : null;
}
public static Instant toInstant(Timestamp timestamp){
return timestamp != null ? timestamp.toInstant() : null;
}
}
// Solution
public class Solution2{
public static makeIt(){
boolean isFirst = true;
String query = "SELECT sensor "
+ "FROM TrashSensor sensor "
+ "join sensor.measurementList measurement ";
if(sensor.id != null){
query += isFirst ? "WHERE " : "AND ";
query += "sensor.id="+sensor.id+" ";
isFirst = false;
}
if(measurement.instantOfMeasure != null){
query += isFirst ? "WHERE " : "AND ";
query += "measurement.instantOfMeasure > "+toTimestamp(measurement.instantOfMeasure)+" ";
}
List<TrashSensor> hibernateQueryService.executeHibernateQuery(query, TrashSensor.class);
}
}

Query works fine in MySql but returns no records with JdbcTemplate

I have the following service method:
public Page<ProductSalesReportLineDto> getReport(
Unit unit, Organization customer, Organization supplier, Date startDate, Date endDate, Pageable pageable
){
List<ProductSalesReportLineDto> lines = new ArrayList<ProductSalesReportLineDto>();
String sql = "SELECT product.id, product.code as code, product.barcode as barcode, product_name.name__text AS name, " +
"SUM(order_line.quantity) as quantity, SUM(order_line.commissioned_price_price * order_line.quantity) as price " +
"FROM product LEFT JOIN order_line ON order_line.product_id = product.id " +
"JOIN `order` ON order_line.order_id = order.id " +
"JOIN product_name ON product_name.product_id = product.id " +
"WHERE product_name.name__locale = \"en-US\" " +
"AND (:customer is null OR :customer='' OR `order`.customer_id = :customer) " +
"AND (:supplier is null OR :supplier='' OR `order`.supplier_id = :supplier) " +
"AND (:startDate is null OR :startDate='' OR `order`.date >= :startDate) " +
"AND (:endDate is null OR :endDate='' OR `order`.date <= :endDate) " +
"AND (:unit is null OR :unit='' OR product.unit = :unit) " +
"GROUP BY product.id, product_name.name__text " +
"LIMIT :offset, :pageSize";
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("offset", pageable.getOffset());
parameters.put("pageSize", pageable.getPageSize());
parameters.put("customer", customer != null ? customer.id : null);
parameters.put("supplier", supplier != null ? supplier.id : null);
parameters.put("startDate", startDate);
parameters.put("endDate", endDate);
parameters.put("unit", unit);
List<Map<String, Object>> rows = namedParameterJdbcTemplate.queryForList(sql, parameters);
for (Map<String, Object> row : rows) {
ProductSalesReportLineDto line = new ProductSalesReportLineDto();
line.productName = row.get("name") != null ? row.get("name").toString() : "";
line.productCode = row.get("code") != null ? row.get("code").toString() : "";
line.productBarcode = row.get("barcode") != null ? row.get("barcode").toString() : "";
line.quantity = row.get("quantity") != null ? Double.parseDouble(row.get("quantity").toString()) : 0;
line.price = row.get("price") != null ? Double.parseDouble(row.get("price").toString()) : 0;
lines.add(line);
}
Page<ProductSalesReportLineDto> page = new PageImpl<ProductSalesReportLineDto>(lines);
return page;
}
The parameters come from a #RestController method:
public #ResponseBody
ResponseEntity<?> get(
#RequestParam(value = "unit", required = false) Unit unit,
#RequestParam(value = "customer", required = false) Organization customer,
#RequestParam(value = "supplier", required = false) Organization supplier,
#DateTimeFormat(pattern = "yyyyy-MM-dd") #RequestParam(value = "startDate", required = false) Date startDate,
#DateTimeFormat(pattern = "yyyyy-MM-dd") #RequestParam(value = "endDate", required = false) Date endDate,
Pageable pageable
)
And Unit is an enum type field on Product:
#Enumerated(EnumType.STRING)
public Unit unit;
public enum Unit {
ROLL("ROLL", "ROLL", AbstractUnit.ONE),
METRE("METRE", Units.METRE.getSymbol(), Units.METRE),
KILOGRAM("KILOGRAM", Units.KILOGRAM.getSymbol(), Units.KILOGRAM),
PIECE("PIECE", "PIECE", AbstractUnit.ONE),
SAMPLE("SAMPLE", "SAMPLE", AbstractUnit.ONE);
I cannot retrieve products by unit using this query. With MySql the query works fine and it returns records. For example:
SELECT product.id, product.code as code, product.barcode as barcode, product_name.name__text AS name, SUM(order_line.quantity) as quantity, SUM(order_line.commissioned_price_price) as price FROM product LEFT JOIN order_line ON order_line.product_id = product.id JOIN `order` ON order_line.order_id = `order`.id JOIN product_name ON product_name.product_id = product.id WHERE product_name.name__locale = "en-US" AND (null is null OR null='' OR `order`.customer_id = null) AND (null is null OR null='' OR `order`.supplier_id = null) AND (null is null OR null='' OR `order`.date >= null) AND (null is null OR null='' OR `order`.date <= null) AND ("ROLL" is null OR "ROLL"='' OR product.unit = "ROLL") GROUP BY product.id, product_name.name__text
This query returns one row. When I run the same query via the rest interface and NamedParameterJdbcTemplate it returns no records. Can anybody tell what I am missing?
Following code also returns one row:
String sql2 = "SELECT * FROM product WHERE product.unit = :unit";
Map<String, Object> parameters2 = new HashMap<String, Object>();
parameters2.put("unit", "ROLL");
List<Map<String, Object>> rows2 = namedParameterJdbcTemplate.queryForList(sql2, parameters2);

Converting from dataset to list in linq

I have dataset function which is as below,
public DataSet GetUpodBrandList(string criteria, string locationId)
{
using (SqlConnection conn = new SqlConnection(this.ConnectionString))
{
string query;
if (criteria == "")
query = "select distinct brandDesc " +
"from arabia_upod_item_master " +
"where locationId = '" + locationId +
"' order by brandDesc";
else
query = "select distinct brandDesc " +
"from arabia_upod_item_master " +
"where brandDesc like '%" + criteria + "%' " +
"and locationId = '" + locationId + "'
order by brandDesc";
conn.Open();
SqlCommand command = new SqlCommand(query, conn);
return this.ExecuteDatasetStoredProc(command);
}
}
I am trying to convert it into linq as follow,
public static List<DataContext.arabia_upod_item_master> GetUpodBrandList(
string criteria,
string locationId)
{
List<DataContext.arabia_upod_item_master> m =
new List<DataContext.arabia_upod_item_master>();
using (var db = UpodDatabaseHelper.GetUpodDataContext())
{
db.ObjectTrackingEnabled = false;
if (criteria == "")
m = db.arabia_upod_item_masters.Where(
i => i.locationId == Convert.ToInt32(locationId))
.OrderBy(i => i.brandDesc)
.Distinct()
.ToList();
else
m = db.arabia_upod_item_masters.Where(
i => i.brandDesc.Contains(criteria) &&
i.locationId == Convert.ToInt32(locationId))
.OrderBy(i => i.brandDesc)
.Distinct()
.ToList();
return m;
}
}
But I don't know how to select distinct brandDesc in the above function (as in the previous function). I am simply using Distinct(). Is it right? or is there any other way to achieve it? Also, if there was 'case' in query in my old function (i.e the first one above) then how will i convert it to linq in the second function. Any other things to worry about while converting to linq?
Put a .Select(i => i.brandDesc) just before each Distinct call. You'll also need to change your List<x> so x is whatever the type of brandDesc is.
If I were to refactor you whole method, I'd do something like the following. I've pulled out code that is common to both forms of the query, and tweaked in a couple of other places.
public static IList<string/* I assume*/>GetUpodBrandList(
string criteria, string locationId)
{
// only do this once, not once per item.
int locatId = Convert.ToInt32(locationId);
using (var db = UpodDatabaseHelper.GetUpodDataContext())
{
db.ObjectTrackingEnabled = false;
// This is a common start to both versions of the query.
var query = db.arabia_upod_item_masters
.Where(i => i.locationId == locatId);
// This only applies to one version of the query.
if (criteria != "")
{
query = query.Where(i => i.brandDesc.Contains(criteria));
}
// This is a common end to both version of the query.
return query
.Select(i => i.brandDesc)
.Distinct()
// Do the ordering after the distinct as it will
// presumably take less effort?
.OrderBy(i => i.brandDesc)
.ToList();
}
}

Resources