In a JavaEE JPA web application, Feature entity has bidirectional ManyToOne relationship with Patient Entity. I want to write a query to count the number of Patients who have one or more matching criteria features. I use EclipseLink as the Persistence Provider.
For example, I want to count the number of patients who have a feature with 'variableName' = 'Sex' and 'variableData' = 'Female' and another feature with 'variableName' = 'smoking' and 'variableData' = 'yes'.
How can I write a JPQL query to get the count of patients?
After the first answer, I tried this Query does not give any results as expected.
public void querySmokingFemales(){
String j = "select count(f.patient) from Feature f "
+ "where ((f.variableName=:name1 and f.variableData=:data1)"
+ " and "
+ " (f.variableName=:name2 and f.variableData=:data2))";
Map m = new HashMap();
m.put("name1", "sex");
m.put("data1", "female");
m.put("name2", "smoking");
m.put("data2", "yes");
count = getFacade().countByJpql(j, m);
}
The Patient entity is as follows.
#Entity
public class Patient implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy = "patient")
private List<Feature> features;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Patient)) {
return false;
}
Patient other = (Patient) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "entity.Patient[ id=" + id + " ]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Feature> getFeatures() {
return features;
}
public void setFeatures(List<Feature> features) {
this.features = features;
}
}
This is the Feature Entity.
#Entity
public class Feature implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String variableName;
private String variableData;
#ManyToOne
private Patient patient;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Feature)) {
return false;
}
Feature other = (Feature) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "entity.Feature[ id=" + id + " ]";
}
public String getVariableName() {
return variableName;
}
public void setVariableName(String variableName) {
this.variableName = variableName;
}
public String getVariableData() {
return variableData;
}
public void setVariableData(String variableData) {
this.variableData = variableData;
}
public Patient getPatient() {
return patient;
}
public void setPatient(Patient patient) {
this.patient = patient;
}
}
For single feature counts you can use this
select count(f.patient) from Feature f where f.variableName=:name and f.variableData:=data
Two feature counts
select count(distinct p) from Patient p, Feature f1, Feature f2
where
p.id=f1.patient.id and p.id=f2.patient.id and
f1.variableName=:name1 and f1.variableData:=data1 and
f2.variableName=:name2 and f2.variableData:=data2
Multiple feature counts solution is a bit tricky. org.springframework.data.jpa.domain.Specification can be used
public class PatientSpecifications {
public static Specification<Patient> hasVariable(String name, String data) {
return (root, query, builder) -> {
Subquery<Fearure> subquery = query.subquery(Fearure.class);
Root<Fearure> feature = subquery.from(Fearure.class);
Predicate predicate1 = builder.equal(feature.get("patient").get("id"), root.get("id"));
Predicate predicate2 = builder.equal(feature.get("variableName"), name);
Predicate predicate3 = builder.equal(feature.get("variableData"), data);
subquery.select(operation).where(predicate1, predicate2, predicate3);
return builder.exists(subquery);
}
}
}
Then your PatientRepository have to extend org.springframework.data.jpa.repository.JpaSpecificationExecutor<Patient>
#Repository
public interface PatientRepository
extends JpaRepository<Patient, Long>, JpaSpecificationExecutor<Patient> {
}
Your service method:
#Service
public class PatientService {
#Autowired
PatientRepository patientRepository;
//The larger map is, the more subqueries query would involve. Try to avoid large map
public long countPatiens(Map<String, String> nameDataMap) {
Specification<Patient> spec = null;
for(Map.Entry<String, String> entry : nameDataMap.entrySet()) {
Specification<Patient> tempSpec = PatientSpecifications.hasVariable(entry.getKey(), entry.getValue());
if(spec != null)
spec = Specifications.where(spec).and(tempSpec);
else spec = tempSpec;
}
Objects.requireNonNull(spec);
return patientRepository.count(spec);
}
}
We also handled same situation for two feature and after extracting the IDs, we used a nested loops after and counting the number of common count. It was resource intensive and this two feature query in the answer helped a lot.
May need to redesign the Class Structure so that querying is easier.
Related
I'm trying to create my own REST API and I'm having trouble trying to order my data by name. currently, I am able to display all the data from the styles table, however, I wish to sort them alphabetically.
I was able to do a filter by extracting the year from the date and checking if that was in the database, this is shown in
public List<Beers> getAllBeersByYear(int year) {
EntityManager em = DBUtil.getEMF().createEntityManager();
List<Beers> list = null;
List<Beers> beersToRemove = new ArrayList<>();
try {
list = em.createNamedQuery("Beers.findAll", Beers.class)
.getResultList();
if (list == null || list.isEmpty()) {
list = null;
}
} finally {
em.close();
}
Calendar cal = Calendar.getInstance();
for (Beers beer : list) {
cal.setTime(beer.getLastMod());
if (cal.get(Calendar.YEAR) != year) {
beersToRemove.add(beer);
}
}
list.removeAll(beersToRemove);
return list;
}
the controller is
#GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
public List<Styles> GetAllStyles() {
return service.getAllStyles();
}
would it be possible to do something similar to the service and controller where instead of filtering the data, it can sort by the name of a column
the service is
public List<Styles> getAllStyles() {
EntityManager em = DBUtil.getEMF().createEntityManager();
List<Styles> list = null;
try {
list = em.createNamedQuery("Styles.findAll", Styles.class)
.getResultList();
if (list == null || list.isEmpty()) {
list = null;
}
} finally {
em.close();
}
return list;
}
the JPA I am using is
#Entity
#Table(name = "styles")
#NamedQueries({
#NamedQuery(name = "Styles.findAll", query = "SELECT s FROM Styles s"),
#NamedQuery(name = "Styles.findById", query = "SELECT s FROM Styles s WHERE s.id = :id"),
#NamedQuery(name = "Styles.findByCatId", query = "SELECT s FROM Styles s WHERE s.catId = :catId"),
#NamedQuery(name = "Styles.findByStyleName", query = "SELECT s FROM Styles s WHERE s.styleName = :styleName"),
#NamedQuery(name = "Styles.findByLastMod", query = "SELECT s FROM Styles s WHERE s.lastMod = :lastMod")})
public class Styles implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#NotNull
#Column(name = "cat_id")
private int catId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 255)
#Column(name = "style_name")
private String styleName;
#Basic(optional = false)
#NotNull
#Column(name = "last_mod")
#Temporal(TemporalType.TIMESTAMP)
private Date lastMod;
public Styles() {
}
public Styles(Integer id) {
this.id = id;
}
public Styles(Integer id, int catId, String styleName, Date lastMod) {
this.id = id;
this.catId = catId;
this.styleName = styleName;
this.lastMod = lastMod;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getCatId() {
return catId;
}
public void setCatId(int catId) {
this.catId = catId;
}
public String getStyleName() {
return styleName;
}
public void setStyleName(String styleName) {
this.styleName = styleName;
}
public Date getLastMod() {
return lastMod;
}
public void setLastMod(Date lastMod) {
this.lastMod = lastMod;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Styles)) {
return false;
}
Styles other = (Styles) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "Service.Styles[ id=" + id + " ]";
}
}
You can use #OrderBy annotation
https://www.logicbig.com/tutorials/java-ee-tutorial/jpa/order-by-annotation.html
Just create another query:
#NamedQuery(name = "Styles.findAll", query = "SELECT s FROM Styles s ORDER BY s.name")
And why are you filtering in the code when you can add a query with a where condition?
Not sure why I have an issue here, but when I save with a CrudRepository with these objects, I get the SerializationException (with no further information). Can someone take a look at my objects and offer me some insight into why they can't serialize? My pom.xml is attached last as well in case that helps somehow. I'm using a Postgres database.
EDIT: The database and now - tables are created, but objects are not creating rows.
The actual CrudRepository interface:
public interface AccountRepository extends CrudRepository<ZanyDishAccount, String> {}
ZanyDishAccount entity:
#Entity
public class ZanyDishAccount {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id; // internal id of the customer account for a Zany Dish subscription
private String status;
#OneToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "company_id")
private Company company;
#OneToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "order_id")
private Order order;
public ZanyDishAccount() {}
public ZanyDishAccount(Company company, Order order) {
this.company = company;
this.order = order;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
#Override
public String toString()
{
return "ClassPojo [id = "+id+ ", company = " + company + ", status = " + status + "]";
}
}
Company entity:
#Entity
public class Company {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
Long id;
private String phoneNumber;
private String website;
private String name;
private String uuid;
private String country;
public Company() {}
public Company(String phoneNumber, String website, String name, String uuid, String country) {
this.phoneNumber = phoneNumber;
this.website = website;
this.uuid = uuid;
this.country = country;
}
public String getPhoneNumber ()
{
return phoneNumber;
}
public void setPhoneNumber (String phoneNumber)
{
this.phoneNumber = phoneNumber;
}
public String getWebsite ()
{
return website;
}
public void setWebsite (String website)
{
this.website = website;
}
public String getName ()
{
return name;
}
public void setName (String name)
{
this.name = name;
}
public String getUuid ()
{
return uuid;
}
public void setUuid (String uuid)
{
this.uuid = uuid;
}
public String getCountry ()
{
return country;
}
public void setCountry (String country)
{
this.country = country;
}
#Override
public String toString()
{
return "ClassPojo [phoneNumber = "+phoneNumber+", website = "+website+", name = "+name+", uuid = "+uuid+", country = "+country+"]";
}
}
Order entity:
#Entity
#Table(name = "_order")
public class Order {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
Long id;
private String pricingDuration;
private Items[] items;
private String editionCode;
public Order() {}
public Order(String pricingDuration, Items[] items, String editionCode) {
this.pricingDuration = pricingDuration;
this.items = items;
this.editionCode = editionCode;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPricingDuration ()
{
return pricingDuration;
}
public void setPricingDuration (String pricingDuration)
{
this.pricingDuration = pricingDuration;
}
public Items[] getItems ()
{
return items;
}
public void setItems (Items[] items)
{
this.items = items;
}
public String getEditionCode ()
{
return editionCode;
}
public void setEditionCode (String editionCode)
{
this.editionCode = editionCode;
}
#Override
public String toString()
{
return "ClassPojo [pricingDuration = "+pricingDuration+", items = "+items+", editionCode = "+editionCode+"]";
}
}
Thanks for your help!
Mike
Hm, this seems multi-faceted. Let's see if I can help at all. Last thing first...
No tables being created automatically.
I would take a look at this section in Spring's docs for the most basic approach: Initialize a database using Hibernate. For example, spring.jpa.hibernate.ddl-auto: create-drop will drop and re-create tables each time the application runs. Simple and easy for initial dev work. More robust would be leveraging something like Flyway or Liquibase.
Serialization issue
So without logs, and the fact that you have no tables created, the lack of a persistence layer would be the assumed culprit. That said, when you have tables and data, if you do not have a repository for all of the related tables, you'll end up with a StackOverflow error (the serialization becomes circular). For that, you can use #JsonBackReference (child) and #JsonManagedReference (parent). I have been successful using only #JsonBackReference for the child.
Items[]
I'm not sure what Item.class looks like, but that looks like an offensive configuration that I missed the first round.
Change private Items[] items; to private List<Item> items = new ArrayList<Item>();. Annotate with #ElementCollection.
Annotate Item.class with #Embeddable.
Given a MongoDB collection described by the following classes:
#Document(collection = "persons")
#TypeAlias("person")
public class Person {
#Id
private ObjectId _id = null;
private String personName = null;
private List<Account> accountsList = null;
public ObjectId getId()
{
return _id;
}
public void setId(ObjectId id)
{
this._id = id;
}
public String getPersonName()
{
return personName;
}
public void setPersonName(String personName)
{
this.personName = personName;
}
public List<Account> getAccountsList()
{
return accountsList;
}
public void setAccountsList(List<Account> accountsList)
{
this.accountsList = accountsList;
}
}
public class Account
{
#Indexed(unique = false)
private double amount = 0.0;
private String description = null;
public double getAmount()
{
return amount;
}
public void setAmount(double amount)
{
this.amount = amount;
}
public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
}
Using Spring Data Mongodb, how do I:
1) count the number of accounts only where the amount > x
2) retrieve only the accounts where the amount > x
3) sum the total amount of accounts only where the amount > x
Any help will be appreciated
I want Drool to fire rule in all elements of list
Here is my Order class
package com.sample;
import java.util.ArrayList;
import java.util.List;
public class Order {
public static final int ORDER_AMOUNT_LIMIT = 10;
public static final int DEFAULT_VALUE=0;
public int id ;
public String name;
public OrderItem item;
public String code;
public List<String> message=new ArrayList<String>();
public void addmessage(String m) {
message.add(m);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public List<OrderItem> orderItems = new ArrayList<OrderItem>();
public OrderItem getItem() {
return item;
}
public void setItem(OrderItem item) {
this.item = item;
}
Order(int id,String name){
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void addOrderItem(OrderItem item) {
orderItems.add(item);
}
}
OrderItem.java
package com.sample;
import java.util.ArrayList;
import java.util.List;
public class OrderItem {
public static final int TEMP_PRICE = 0;
public Order order;
public int price;
public String code;
public List<String> message=new ArrayList();
public void addMessage(String msg){
message.add(msg);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
OrderItem(Order order,int price){
this.order = order;
this.price = price;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
order.drl
package com.sample
import com.sample.Order;
rule "order"
when
$order : Order()
$total : Double() from accumulate( OrderItem( order == $order, $price : price,code == $order.code,),
sum( $price ) )
eval($total>Order.ORDER_AMOUNT_LIMIT)
then
System.out.println($total);
$order.orderItems.get(0).price=0;
System.out.println("price is "+ $order.orderItems.get(0).getPrice());
end
DroolTest.java
public class DroolsTest {
public static final void main(String[] args) {
try {
// load up the knowledge base
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("ksession-rules");
Order order = new Order(1,"bob");
order.setCode("test1");
OrderItem item1 = new OrderItem(order, 11);
item1.setCode("test1");
OrderItem item2 = new OrderItem(order, 7);
item2.setCode("test1");
order.addOrderItem(item1);
order.addOrderItem( item2 );
kSession.insert(order);
kSession.insert(item1);
kSession.fireAllRules();
kSession.insert(item2);
kSession.fireAllRules();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Output
11.0
price is 0
As you can see the then condition is executed only by once because
when drools check 11 > 10 which is working fine and set the value to
0 but when drools process second list item now sum will return 0+7 >
10 which is false but i want to fire then condition on second list
item because 11+7 = 19 is greater than 10 Is there any way to fire
then condition on both list item ? Is there any way we get the
original value of the object not from working memory?
You add a pattern OrderItem so the rule will fire for each item of an order exceeding the limit.
rule "order"
when
$order: Order( $code: code )
accumulate( OrderItem( order == $order, $price: price,
code == $code );
$total: sum( $price );
$total > Order.ORDER_AMOUNT_LIMIT )
$item: OrderItem( order == $order, $price: price, code == $code )
then
System.out.println( $item ); // an order item of a "big" order
end
I have been trying to create a bidirectional #OneToMany relation with composite keys but some pieces are missing or wrong.
I have a draft entity which holds a list of sub draft entities.
Here is what I got:
#Entity
#Table(name = "draft")
#IdClass(Pk.class)
public class Draft {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
protected Long id;
#Id
protected Integer rev;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "draft")
List<SubDraft> subDrafts = new ArrayList<SubDraft>();
// getters / setters omitted
}
#Entity
#Table(name = "sub_draft")
#IdClass(PK.class)
public class DraftToDoDAO {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
protected Long id;
#Id
protected Integer rev;
#ManyToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name = "draft_id", referencedColumnName = "id"),
#PrimaryKeyJoinColumn(name = "draft_rev", referencedColumnName = "rev")
})
protected DraftDAO draft;
// getters / setters omitted
}
public class PK implements Serializable {
protected Long id;
protected Integer rev;
public PK() {
}
public PK(Long id, Integer rev) {
this.id = id;
this.rev = rev;
}
public Long getId() {
return id;
}
public Integer getRev() {
return rev;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((rev == null) ? 0 : rev.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MetaDataDAO other = (MetaDataDAO) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (rev == null) {
if (other.rev != null)
return false;
} else if (!rev.equals(other.rev))
return false;
return true;
}
}
I have no problems saving a draft with a list of sub drafts but the relation is not created both ways.
In the sub draft table the SchemaTool (DataNucleus) creates a column named draft_id but it is empty. And I wonder why it is not creating the columns I specified (draft_id, draft_rev) and settles the relation there.
I have search a lot for an answer but just can not get thing to work.
Your help is appreciated!
Thanks.
EDIT!
Here is the actual persistence code:
public Draft create(Draft draft, SubDraft subDraft) {
EntityManager em = PersistenceHelper.getEntityManager();
draft.setCreated(Calendar.getInstance());
// This should do it
draft.setSubDraft(subDraft);
subDraft.setDraft(draft);
em.persist(draft);
em.close();
return draft;
}
Some where along the line of code this subDraft attribute was nilled and therefore is did not work.