Multiple clause query in spring boot - java

package com.mobile.model;
import javax.persistence.*;
#Entity
#Table(name = "mobile_table")
public class Mobile {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
public int mobileId;
#Column(name = "mobile_name",nullable = false,length = 20)
public String mobileName;
#Column(name = "mobile_brand",nullable = false,length = 15)
public String mobileBrand;
#Column(name="mobile_networkType")
public String mobileNetworkType;
#Column(name = "mobile_core")
public Integer mobileAvailableCore;
#Column(name = "mobile_ram")
public Integer mobileRAMSize;
#Column(name = "mobile_os")
public String mobileOS;
#Column(name = "mobile_wifif")
public Boolean mobileWifiAvailability;
#Column(name = "mobile_bluetooth")
public Boolean mobileBluethoothAvailablity;
public Mobile() {
}
public Mobile(String mobileName, String mobileBrand, String mobileNetworkType, Integer mobileAvailableCore, Integer mobileRAMSize, String mobileOS, Boolean mobileWifiAvailability, Boolean mobileBluethoothAvailablity) {
this.mobileName = mobileName;
this.mobileBrand = mobileBrand;
this.mobileNetworkType = mobileNetworkType;
this.mobileAvailableCore = mobileAvailableCore;
this.mobileRAMSize = mobileRAMSize;
this.mobileOS = mobileOS;
this.mobileWifiAvailability = mobileWifiAvailability;
this.mobileBluethoothAvailablity = mobileBluethoothAvailablity;
}
}
//Query for multiple selection
#SuppressWarnings("unchecked")
public List<Mobile> getMobileSearch(String mobileBrand, Integer mobileRAMSize, String mobileOS){
return mobileDao.findAll(Specifications.where((Specification<Mobile>) getAllMobilesByBrand(mobileBrand))
.and((Specification<Mobile>) getAllMobilesByOS(mobileOS))
.and((Specification<Mobile>) getAllMobilesByRam(mobileRAMSize)));
}
// calling function for multiple attribute Search in controller class
#RequestMapping(value="/searchMobile", method=RequestMethod.GET)
public ResponseEntity<List<Mobile>> searchMobile(#RequestParam(value="brand") String mobileBrand, #RequestParam(value="ramSize") Integer ramSize, #RequestParam(value="mobileOs") String MobileOs)
{
List<Mobile> mobileList=mobileService.getMobileSearch(mobileBrand,ramSize,MobileOs);
return new ResponseEntity<List<Mobile>>(mobileList,HttpStatus.OK);
}
// here is how my database is calling database query.
#Repository
public interface MobileDao extends CrudRepository<Mobile, Integer>, JpaSpecificationExecutor<Mobile> {
public List<Mobile> findByMobileBrand(String mobileBrand);
#Query("select distinct m.mobileBrand from Mobile m")
public List<String > getAllDistinctBrand();
public List<Mobile> findByMobileRAMSize(Integer mobileRAMSize);
#Query("select distinct m.mobileRAMSize from Mobile m")
public List<Integer> getAllDistinctRam();
public List<Mobile> findByMobileOS(String mobileOS);
#Query("select distinct m.mobileOS from Mobile m")
public List<String > getAllDistinctMobileOS();
I am trying to implement the search query for products like used in different e-commerce Websites(like Flipkart, Amazon) where user inserts different attributes for the product. Based on the product Attribute, results are shown. For the implementation i am using Spring Boot and JPA as a database.
How can i search multiple clause in single query dynamically. For Example, in case of SQL query we search multiple clause like this.
How can i implement multiple clause in JPA.
Select * from table_name where attribute1="Attb" and attribute2="Attb2" and attribute3="Attb3";
Attb, Attb2 and Attb3 are dynamically changing based on client input.

Your question was not clear enought. If you need to use variable number of conditions, you will have to use Criteria API. It allows you to conditionally create list of predicates and use that for data fetch.
http://www.objectdb.com/java/jpa/query/criteria
It will look like this:
myQueryMethod(UserChoices choices){
if(choices.someChoice1){
predicates.add(somePredicate)
}
.....

You should implement a JPA repository and use method queries :
public interface MyRepository implements JpaRepository<MODEL_CLASS, Long> {
MODEL_CLASS findAllByAtttibute1AndAttribute2AndAttribute3(String Attribute1, String Attribute2, String Attribute3);
}

Related

java.lang.NullPointerException due to 'Map<String, Integer>' may not contain keys of type 'Path<String>'

I am facing an issue in a springboot project,
I am trying to retrieve statistics of "Tickets" that are handled ontime using jpa specifications.
Ticket are given a number of days to handle based on the purpose.
Here is the error:
java.lang.NullPointerException: null
at com.ticketcorp.ticket.repository.TicketSpecification.lambda$isOntime$c9c337fb$1(TicketSpecification.java:208) ~[classes/:na]
Which i Believe is to be expected since i got this warning on the same line:
'Map<String, Integer>' may not contain keys of type 'Path<String>'
Here is my Ticket Entity:
#Table(name = "tickets")
public class Ticket {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String code;
private String purpose;
#Lob
#Column(columnDefinition = "TEXT")
private String content;
#Lob
#Column(columnDefinition = "TEXT")
private String solution;
#Lob
#Column(columnDefinition = "TEXT")
private String comment;
private int status;
private LocalDateTime createdAt= LocalDateTime.now();
private LocalDateTime handledAt= LocalDateTime.now();
}
Here is my Ticket Specification:
public class TicketSpecification {
public static Specification<Ticket> isOntime(ArrayList<Purpose> purposes) {
return (root, query, builder) -> {
/*Example of content for nameAndDurationMap: {Suggestion=25, Bug report=1}*/
Map<String, Integer> nameAndDurationMap = PurposeUtils.PurposeArrayToNameDurationMap(purposes);
return builder.le(
builder.diff(
builder.function("unix_timestamp", Long.class, root.get(Ticket_.handledAt)),
builder.function("unix_timestamp", Long.class, root.get(Ticket_.createdAt))
)
, nameAndDurationMap.get(root.get(Ticket_.purpose)) * 86400);/*Line 208*/
};
}
}
Here is my Ticket Service:
#Service
public class TicketService {
#Autowired
private TicketRepository ticketRepository;
public String countTicketsHandledOnTime(){
int handledStatus=2;
Specification<Ticket> allTicketHandledOnTimeQuery =where(TicketSpecification.isHandled(handledStatus)).and(TicketSpecification.isOntime(purposes));
return String.valueOf(ticketRepository.count(allTicketHandledOntimeQuery));
}
}
Here is Purpose POJO Model:
public class Purpose{
private String id;
private String name;
private String description;
private int level;
private int duration;
}
Here is PurposeUtils :
It takes a list of purposes and generate a hashmap of purpose and number of days it should take to handle a ticket of that purpose.
public class PurposeUtils {
public static Map<String, Integer> PurposeArrayToNameDurationMap(ArrayList<Purpose> purposes) {
Map<String, Integer> purposeMap = new HashMap<String, Integer>();
for(Purpose purpose: purposes) {
purposeMap.put(purpose.getName(), purpose.getDuration());
}
return purposeMap;
}
}
I assume you use javax.persistence.criteria.Root in this line:
nameAndDurationMap.get(root.get(Ticket_.purpose)) * 86400);/*Line 208*/
note documentation of Root:
Path< Y > get(String attributeName) Create a path corresponding to the
referenced attribute.
so you ask the map to get the value that indeed is not there
Note that root is not a value holder, it is a for the prdicat creation, so in your predict you will say I want value X(root) to met Y condition
this will become SQL query, so it has to be values that SQL can handle, your code will not be called on every ticket... if you want to do it either makes it iterate every purpose
(
if purpose_x == Ticket_.purpose and le(...)
or purpose_y == Ticket_.purpose and le(...)
)
or move the logic to DB function you can call
code that will give the idea but probably will not run since it dry:
public class TicketSpecification {
public static Specification<Ticket> isOntime(ArrayList<Purpose> purposes){
return (root, query, builder) -> {
List<Predicate> predicates = new ArrayList<>();
for(Purpose purpose: purposes) {
predicates.add(isOntime(purpose.getName(), purpose.getDuration(),root, query, builder);
}
return builder.or(predicates.toArray(new Predicate[0]));
}
}
public static Predicate isOntime(String purposes_name,int purposes_time,Root<Ticket> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.and(
builder.equal(root.get(Ticket_.purpose),purposes_name)
,
builder.le(
builder.diff(
builder.function("unix_timestamp", Long.class, root.get(Ticket_.handledAt)),
builder.function("unix_timestamp", Long.class, root.get(Ticket_.createdAt))
)
,(purposes_time * 86400);/*Line 208*/
)
);
}
}

SQL aggregation GROUP BY and COUNT in Spring JPA

I have an SQL table:
#Table(name = "population_table")
public class Population {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String country;
private String state;
private String area;
private String population;
}
I want to get a count, grouping by country and state with the output class being List of Count:
private static class Count {
private String country;
private String state;
private long count;
}
I know the query is
SELECT country, state, Count(*)
FROM population_table
GROUP BY country, state
But I want to do this using JPA Specification. How can I achieve this using JPA Specification in spring boot?
You could achieve this by using Spring Data JPA Projections in Spring Data JPA.
Create a custom Repository method like
#Repository
public interface PopulationRepository extends JpaRepository<Population, Long> {
#Query("select new com.example.Count(country, state, count(p) )
from Population p
group by p.country, p.state")
public List<Count> getCountByCountryAndState();
}
Also you must define the specific constructor in Count class which will handle this projection
private static class Count {
private String country;
private String state;
private long count;
//This constructor will be used by Spring Data JPA
//for creating this class instances as per result set
public Count(String country,String state, long count){
this.country = country;
this.state = state;
this.count = count;
}
}
You can use JpaRepository interface.
Example:
#Repository
public interface PopulationRepository extends JpaRepository<Population, Long> {
public int countAllByCountryAndState(String countryName, String stateName);
}
And in your service:
#Service
#Transactional
public class PopulationService {
#Autowired
private PopulationRepository populationRepository;
public int countPopulationByCountryAndState(String countryName, String stateName) {
return populationRepository.countAllByCountryAndState(countryName, stateName);
}
}
Sorry, I made mistake it can be simpler. I edited my code.

Using SQL-IN-clause in custom #Query in JPA-/CrudRepository with the list or set as passed parameter?

Hi Spring and Hibernate experts!
Can any one say if it is possible to use SQL IN-clause in custom #Query in CrudRepository while the Arraylist or set of strings is passed as parameter?
I am relatively new to Spring and do not quite figure out why I get the following Spring error:
"java.lang.IllegalArgumentException: Parameter value [d9a873ed-3f15-4af5-ab1b-9486017e5611] did not match expected type [IoTlite.model.Device (n/a)]"
In this post (JPQL IN clause: Java-Arrays (or Lists, Sets...)?) the subject is discussed pretty closely but I cannot make the suggested solution to work in my case with custom #Query.
My demo repository as part of the spring boot restful application is the following:
#Repository
public interface DeviceRepository extends JpaRepository<Device, Long> {
#Query("SELECT d FROM Device d WHERE d IN (:uuid)")
List<Device> fetchUuids(#Param("uuid") Set<String> uuid);
}
And the model-class is the following:
#Entity
#SequenceGenerator(sequenceName = "device_seq", name = "device_seq_gen", allocationSize = 1)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Device implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "device_seq_gen")
#JsonIgnore
private Integer id;
#Column(unique=true, length=36)
#NotNull
private String uuid = UUID.randomUUID().toString();
#Column(name="name")
private String name;
#JsonInclude(JsonInclude.Include.NON_NULL)
private String description;
#OneToMany(
mappedBy="device",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Sensor> sensors = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#JsonIgnore
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDeviceUuid() {
return uuid;
}
public void setDeviceUuid(String deviceUuid) {
this.uuid = deviceUuid;
}
public List<Sensor> getSensors() {
return sensors;
}
public void addSensor(Sensor sensor){
sensor.setDevice(this);
sensors.add(sensor);
}
}
An here is the relevant part of the service calling the fetchUuids-custom-method with set-list of strings as parameter (service naturally being called by the relevant restcontroller):
#Service
public class DeviceService implements IDeviceService {
#Autowired
private DeviceRepository deviceRepository;
...
#Override
public List<Device> listDevices(Set<String> clientIds) {
return deviceRepository.fetchUuids(clientIds);
}
...
}
Quick fix
You have WHERE d IN (:uuid) in the custom query. You cannot match d, which is an alias for Device entity with :uuid parameter, which is a collection of Strings.
WHERE d.uuid IN (:uuid) would fix the query - it matches a String with Strings.
What you should do instead
It's rather misleading to name the method fetchUuids and return a list of Device instances. It's also unnecessary to write a custom query to do that. You can benefor from repository method name conventions and let Spring Data Jpa framework generate the query for you:
List<Device> findByUuidIn(Set<String> uuids);
You can write in this way
#Query(value = "select name from teams where name in :names", nativeQuery = true)
List<String> getNames(#Param("names") String[] names);
and call the function in service and pass an array of String as arguments.like this
String[] names = {"testing team","development team"};
List<String> teamtest = teamRepository.getNames(names);
Yes is possible to using collection in JPA query parameters.
Your query is wrong, it should be like this:
#Query("SELECT d FROM Device d WHERE d.uuid IN :uuid")

Getting entity from table without having primary key in Hibernate

I'm currently working on a project where I'm trying to get a list of enities from table which does not have a primary key (dk_systemtherapie_merkmale). This table is 1:n related to another table (dk_systemtherapie). See the screenshot for the table structure.
When getting an entry for dk_systemtherapie, the program fetches the Collection "dkSystemtherapieMerkmalesById". However, the first table entry is fetched as often as the number of actual entries in the table is. It never fetches the other entries from dk_systemtherapie_merkmale. I assume it has something to do with the fact that hibernate can't differ between the entries, but I don't know how to fix it.
Table schema
I've created two corresponding entity classes, dk_systemtherapie:
#Entity
#Table(name = "dk_systemtherapie", schema = "***", catalog = "")
public class DkSystemtherapieEntity {
private int id;
private Collection<DkSystemtherapieMerkmaleEntity> dkSystemtherapieMerkmalesById;
#Id
#Column(name = "id")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#OneToMany(mappedBy = "dkSystemtherapieByEintragId")
public Collection<DkSystemtherapieMerkmaleEntity> getDkSystemtherapieMerkmalesById() {
return dkSystemtherapieMerkmalesById;
}
public void setDkSystemtherapieMerkmalesById(Collection<DkSystemtherapieMerkmaleEntity> dkSystemtherapieMerkmalesById) {
this.dkSystemtherapieMerkmalesById = dkSystemtherapieMerkmalesById;
}
}
Here the second one, which is accessing the table without a primary key, dk_systhemtherapie_merkmale:
#Entity #IdClass(DkSystemtherapieMerkmaleEntity.class)
#Table(name = "dk_systemtherapie_merkmale", schema = "***", catalog = "")
public class DkSystemtherapieMerkmaleEntity implements Serializable {
#Id private Integer eintragId;
#Id private String feldname;
#Id private String feldwert;
private DkSystemtherapieEntity dkSystemtherapieByEintragId;
#Basic
#Column(name = "eintrag_id")
public Integer getEintragId() {
return eintragId;
}
public void setEintragId(Integer eintragId) {
this.eintragId = eintragId;
}
#Basic
#Column(name = "feldname")
public String getFeldname() {
return feldname;
}
public void setFeldname(String feldname) {
this.feldname = feldname;
}
#Basic
#Column(name = "feldwert")
public String getFeldwert() {
return feldwert;
}
public void setFeldwert(String feldwert) {
this.feldwert = feldwert;
}
#Id
#ManyToOne
#JoinColumn(name = "eintrag_id", referencedColumnName = "id")
public DkSystemtherapieEntity getDkSystemtherapieByEintragId() {
return dkSystemtherapieByEintragId;
}
public void setDkSystemtherapieByEintragId(DkSystemtherapieEntity dkSystemtherapieByEintragId) {
this.dkSystemtherapieByEintragId = dkSystemtherapieByEintragId;
}
}
I assume the problem is releated to the fact that Hibernate is using the following annotation as the one and only id for fetching data from database.
#Id
#ManyToOne
#JoinColumn(name = "eintrag_id", referencedColumnName = "id")
public DkSystemtherapieEntity getDkSystemtherapieByEintragId() {
return dkSystemtherapieByEintragId;
}
This leads to the problem that when getting more than one entry with the same id (as the id is not unique), you will get the number of entries you would like to but hibernate is always fetching the first entry for this id. So in fact you are getting dublicate entries.
So how to fix this?
According to this question: Hibernate and no PK, there are two workarounds which are actually only working when you don't have NULL entries in your table (otherwise the returning object will be NULL as well) and no 1:n relationship. For my understanding, hibernate is not supporting entities on tables without primary key (documentation). To make sure getting the correct results, I would suggest using NativeQuery.
Remove the Annotations and private DkSystemtherapieEntity dkSystemtherapieByEintragId; (incl. beans) from DkSystemtherapieMerkmaleEntity.java und add a constructor.
public class DkSystemtherapieMerkmaleEntity {
private Integer eintragId;
private String feldname;
private String feldwert;
public DkSystemtherapieMerkmaleEntity(Integer eintragId, String feldname, String feldwert) {
this.eintragId = eintragId;
this.feldname = feldname;
this.feldwert = feldwert;
}
public Integer getEintragId() {
return eintragId;
}
public void setEintragId(Integer eintragId) {
this.eintragId = eintragId;
}
public String getFeldname() {
return feldname;
}
public void setFeldname(String feldname) {
this.feldname = feldname;
}
public String getFeldwert() {
return feldwert;
}
public void setFeldwert(String feldwert) {
this.feldwert = feldwert;
}
}
Remove private Collection<DkSystemtherapieMerkmaleEntity> dkSystemtherapieMerkmalesById; (incl. beans) from DkSystemtherapieEntity.java.
Always when you need to get entries for a particular eintrag_id, use the following method instead of the Collection in DkSystemtherapieEntity.java.
public List<DkSystemtherapieMerkmaleEntity> getDkSystemtherapieMerkmaleEntities(int id) {
Transaction tx = session.beginTransaction();
String sql = "SELECT * FROM dk_systemtherapie_merkmale WHERE eintrag_id =:id";
List<Object[]> resultList;
resultList = session.createNativeQuery(sql)
.addScalar("eintrag_id", IntegerType.INSTANCE)
.addScalar("feldname", StringType.INSTANCE)
.addScalar("feldwert", StringType.INSTANCE)
.setParameter("id", id).getResultList();
tx.commit();
List<DkSystemtherapieMerkmaleEntity> merkmale = new ArrayList<>();
for (Object[] o : resultList) {
merkmale.add(new DkSystemtherapieMerkmaleEntity((Integer) o[0], (String) o[1], (String) o[2]));
}
return merkmale;
}
Call getDkSystemtherapieMerkmaleEntities(dkSystemtherapieEntityObject.getid()) instead of getDkSystemtherapieMerkmalesById().

Java JPA "Error compiling the query" when it uses an enum

The following JPA query doesn't compile:
#NamedQuery(name = "PSA.findBySourceSystem",
query = "SELECT p FROM PSA p WHERE p.sourceSystem.id = :sourceSystemId")
p.sourceSystem is the following enum:
public enum SourceSystem {
FIRST(3, "ABC"), SECOND(9, "DEF"), THIRD(17, "GHI");
private int id;
private String code;
...
}
and is mapped in PSA's base class:
public class PsaBase implements Serializable {
#Column(name = "sourceSystemId")
#Enumerated(EnumType.ORDINAL)
protected SourceSystem sourceSystem;
...
}
The query compiles and runs fine if I replace p.sourceSystem.id in the query with something more benign.
Thank you in advance for any help.
It shouldn't compile.
You have to resolve the required enum value manually before passing it as a query parameter:
#NamedQuery(name = "PSA.findBySourceSystem",
query = "SELECT p FROM PSA p WHERE p.sourceSystem = :sourceSystem")
.
public enum SourceSystem {
...
private static Map<Integer, SourceSystem> valuesById = new HashMap<Integer, SourceSystem>();
static {
for (SourceSystem s: values())
valuesById.put(s.id, s);
}
public static SourceSystem findById(int id) {
return valuesById.get(id);
}
}
.
em.createNamedQuery("PSA.findBySourceSystem")
.setParameter("sourceSystem", SourceSystem.findById(sourceSystemId));
EDIT:
Since sourceSystem is annotated as #Enumerated(EnumType.ORDINAL), it's stored in the database as the ordinal numbers of the corresponding enum values, therefore FIRST is stored as 0. JPA doesn't directly support using arbitrary field of the enum value to identify it in the database. If your database schema assumes so, you can do the following trick to decouple state of your object from the database schema:
public class PsaBase implements Serializable {
protected SourceSystem sourceSystem;
#Column(name = "sourceSystemId")
public Integer getSourceSystemId() {
return sourceSystem.getId();
}
public void setSourceSystemId(Integer id) {
this.sourceSystem = SourceSystem.findById(id);
}
... getter and setter of sourceSystem with #Transient ...
}

Categories

Resources