I have two entities :
Items(id, user_id, title, price);
Purchases(id, item_id, user_id, date);
Using JPA, I'd like to list all the items that have been purchased more than X times, ordered by their purchased times (the first being the most purchased).
I managed to have the correct SQL request, but how can I do that in JPA (possibly without using createQuery or something equivalent) :
SELECT i.title, COUNT(p.id) as rank FROM items AS i LEFT OUTER JOIN purchases AS p ON p.item_id = i.id WHERE rank > X GROUP BY i.title ORDER BY rank DESC;
// of course, X is an int!
Thank you for your help! :)
Update:
I indicated to avoid createQuery but I didn't explained why.
The thing is, I made a class dedicated to generating the query, it looks like :
public class Arguments {
protected HashSet allowedOrders = new HashSet();
protected Collection args = new ArrayList();
// constructor and some other methods
// this one works great
public void setPriceMin(int price) {
query += " AND price > ?";
args.put(price);
}
// sames method for setPrice (= ?), and setPriceMax (= <)
// this one doesn't :/
public void setSalesMin(int sales) {
// here's my problem
}
}
But It's (really) possible that my methods isn't good. And since you bring up the Criteria, maybe I should take a look at it, even for "setPriceMin" and all the other methods.
How'd you do?
You can't do it without a Query.
Either rewrite the Query as JPQL (very similar, but you will have to replace ids with objects and do joins on properties, not on tables), or use the JPA 2 CriteriaQuery API. (Or use your SQL as a native Query, but this is usually a bad idea)
Related
I'm new to ORM interface, and I'm trying to connect to my databases with Hibernate.
What I've figured out so far is:
With a serializable object, I can get a persistent object with
Person p = session.get(Person.class, serializable);
I can get all the objects by a list with
List people = session.createQuery("FROM Person").list();
What I need is to find a row that meets a certain condition, such as SELECT * FROM person WHERE name="Kim" AND age=30;
However, the above two aren't the ways to achieve this.
#Entity
#Table(name = "person")
public class Person {
#Id
private Integer id; // I can use this variable when using session.get(Person.class, serializable) , but I cannot know the id of my target row.
private String name;
private Integer age;
...
Should I iterate all the objects in people, and check whether all the member variables match what I want?
Is there any simple way to achieve this?
First and most importantly, never put user input in a query like this
SELECT * FROM person WHERE name="Kim" AND age=30;
You have to use Prepared Statements. Learn why from Bobby Tables.
Secondly, you should use the JPA interface EntityManager instead of Hibernate's Session as the second one anchors you to a specific implementation, rather than the wider standard.
With the EntityManager you get an object by id like this:
Person p = em.find(Person.class, id);
To get a list of People you can create a JPQL query like this:
TypedQuery<Person> query = em.createQuery("SELECT p FROM Person p WHERE p.name = :name AND p.age = :age", Person.class);
query.setParameter("name", "Kim"); // :param1 defines a parameter named "param1" in the query
query.setParameter("age", 30);
List<Person> results = query.getResultList();
You could also do this in one chain if you don't need to reuse the query with different parameters on a loop.
List<Person> results = em.createQuery(..., Person.class)
.setParameter("name", "Kim")
.setParameter("age", 30)
.getResultList();
The reason to put every call on a new row is in case an exception occurs it will give you the proper row to look for. If they're all in one row, then that's not very useful.
If your query is a SELECT, and it needs to return exactly one result every time, you can use getSingleResult() instead of getResultList(). If you do that and the query did return more than one result, it will throw a NonUniqueResultException. If the query did not return any results it will throw a NoResultException instead of returning null.
If your query is NOT a SELECT, then you have to use executeUpdate() to invoke it after setting the parameters.
There are many resources to get you started, but generally if its for a Hibernate version before 5.2 you should consider it outdated, and it will likely be more difficult.
Say I have at least these two entities:
class Person {
String firstname, lastname;
Address address;
ManyOtherPropertiesAndEntities ...;
}
class Address {
String street;
Country country;
}
Now, I would like to query the Person table and ONLY Persons that live on different streets.
That is, ignore all Persons that live on same street, and return only one of these Person, any one.
How can I perform such a query?
Is that possibly using Criteria?
Criteria criteria = session.createCriteria(Person.class, "person");
Criteria addressCriteria = criteria.createCriteria("address")
criteria.setProjection(
Projections.distinct(
Projections.projectionList().add(Projections.property("address.street"))
)
);
This doesnt really work.
I've also tried to do:
projectionList.add( Projections.sqlProjection("DISTINCT ON ( address.street ), ... ", columns.toArray(new String[columns.size()]), types.toArray(new Type[types.size()])));
But also fruitless.
>>>>>>>>>>>>>>>>>>>>>>>EDIT<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
I was able to get this to run, and generate an SQL query that actually returns results in a pure sql mode, but seems to return zero in hibernate:
List<String> columns = Lists.lst();
List<Type> types = Lists.lst();
bondCriteria.setProjection(
Projections.sqlProjection ("DISTINCT ON ( tvmux2_.polarization ) * "
, columns.toArray (new String[columns.size()])
, types.toArray (new Type[types.size()])
)
// Projections.projectionList().add(Projections.distinct(Projections.property("polarization")))
);
ResultTransformer resultTransformer = new ResultTransformer() {
#Override
public List transformList( List collection ) {
return null;
}
#Override
public Object transformTuple( Object[] tuple, String[] aliases ) {
return null;
}
};
bondCriteria.setResultTransformer(resultTransformer);
* ITS 2017 and SO still hasn't included a proper editor to easily be able to format code so that indentation and copy and paste is not a complete hell. Feel free to scroll horizontally. *
THis generates teh following query basically
select DISTINCT ON ( tvmux2_.polarization ) * from TvChannelBond this_ inner join TvChannel tvchannel1_ on this_.channel=tvchannel1_.id inner join TvMux tvmux2_ on this_.mux=tvmux2_.id where this_.enabled=true order by tvmux2_.polarization asc limit 100
which does return results in a non hibernate mode.
However, since sqlProjection method requires the supplementation of 3 params, I am not sure what to add to the second and third params. Types can not be other than predefined Hibernate types just as DOUBLE, STRING and so on.
When debugging into the resultTransformer, it gets into transformTuple with zero length tuple[] and aliases[].
Might have to do with the sqlProjection zero length types and columns lists.
In SQL, you could do it like this:
SELECT p.*
FROM Address a
INNER JOIN Person p ON ...
GROUP BY a.Street
HAVING p.id = MIN(p.id)
This statement selects for every distinct Street from Address the Person with the minimum id value. Instead of MIN(p.id) you can of course use any other field and aggregate function which will match exactly one person per street; MAX(p.id) will work, MIN(p.lastname) won't if there can be more than one "Smith" in a street.
Can you transform the above SQL to your Criteria query?
I have these entities:
public class Order_status_sas {
private Order_sas order;
private Date lastModified;
...
}
public class Order_sas {
private long id;
...
}
My CrudRepository:
public interface StatusesWareHouseRepository extends CrudRepository<Order_status_sas, Long> {
Order_status_sas findFirstByOrderIdOrderByLastModifiedDesc(long id);
}
I expect that method findFirstByOrderIdOrderByLastModifiedDesc would return first row from table Order_status_sas, where order.id = <some_id> sorted by field lastModified, but in log I see this query:
Hibernate: select ...
from order_status_sas a
left outer join orders_sas b
on a.order_id=b.id
where b.id=?
order by a.last_modified desc
This query does not return me one row, but returns a list of rows. It seems that Spring Data do not look at word First in my method name. Also, I get an Exception:
org.springframework.dao.IncorrectResultSizeDataAccessException:
result returns more than one elements; nested exception is javax.persistence.NonUniqueResultException: result returns more than one elements
Please, tell me what I am doing wrong and how can I achieve my purpose?
EDITED:
I edited my StatusesWareHouseRepository with custom query:
#Query("select s from Order_status_sas s where s.order.id = ?1 order by s.lastModified desc limit 1")
Order_status_sas findFirstByOrderIdOrderByLastModifiedDesc(long id);
but the query, executed by Hibernate, haven't changed. It looks like this:
select ...
from order_status_sas s
where s.order_id=?
order by s.last_modified desc
OK, I understood #PriduNeemre point. Lets leave the DB model and come back to the JPA question. Here is another example:
#Entity
public class Client {
....
}
public interface ClientRepository extends CrudRepository<Client, Integer> {
Client findFirstByOrderByNameDesc();
}
Hibernate query still looks like this:
select ...
from clients c
order by c.name desc
Have you tried adding a #Query annotation (see here) on top of your findFirstByOrderIdOrderByLastModifiedDesc(..) method to specify the expected behaviour by hand? A (non-related) example on how this could work:
public interface InvoiceRepository extends JpaRepository<Invoice, Long> {
#Query("SELECT I FROM Invoice I JOIN I.customer C JOIN C.user U WHERE
U.username = :username")
public List<Invoice> findInvoicesByUsername(#Param("username")
String username);
}
Note that the query language used in the annotation body is in fact JPQL, not SQL. For more examples on the #Query annotation, see the Spring Data docs here.
PS: I'm also having conflicted feelings about your domain object structure, i.e. whether an instance of Order_sas should really be stored in an instance of Order_status_sas - shouldn't it be the other way around? Normally you would want to store the reference objects in your main domain object, not vice versa. (There's a slight possibility that I'm just not getting it right, though.)
EDIT: I would even go as far as to say that considering your current domain model, Hibernate is doing everything right except missing a LIMIT 1 clause to limit the expected resultset to one single row. The query itself is extremely inefficient, though, and could be improved by fixing your skewed domain model.
I'm trying to create a query using CriteriaBuilder to select all Product with a stock greater than zero. Stock is sum(DeliveryRow.amount) - sum(DispatchRow.amount). Both ofcourse only containing the right Product.
I have tried creating Subquery for both DeliveryRow and DispatchRow though I feel like this should be done using a join().
Classes
Product {
(...)
}
DeliveryRow {
#ManyToOne
private Product product;
private int amount;
}
DispatchRow {
#ManyToOne
private Product product;
private int amount;
}
Query
In this query I'm not sure how to handle the xxx. I've tried making to subqueries but that didn't work out.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Product> query = cb.createQuery(Product.class);
Root product = query.from(Product.class);
query.select(product);
// sum of DeliveryRow.amount where DeliveryRow.product = Product
// minus
// sum of DispatchRow.amount where DispatchRow.product = Product
Expression stock = xxx;
query.where(cb.gt(stock, Integer.parseInt(0)));
return em.createQuery(query).getResultList();
Any suggestions on how to solve this?
I have recently been doing research on JPA/JPQL, studying the three different approaches for retrieving entities: NamedQueries, em.CreateQuery and CriteriaBuilder. The CriteriaBuilder in my opinion is the most awkward of the three to use. I would recommend creating a NamedQuery to handle this situation, it will be a lot easier to implement and read.
Using this JPQL expression you could retrieve all of the products with a stock greater than zero:
SELECT p.name, SUM(delRow.amount) - SUM(disRow.amount)
FROM Product p join p.deliveryRows delRow join p.dispatchRows disRow
HAVING SUM(delRow.amount) - SUM(disRow.amount) > 0
/* This assumes product has a Collection<DispatchRow> named dispatchRows
and a Collection<DeliveryRow> named deliveryRows.
*/
Make this a named query in the `Product' entity
//This should be concatenated or on one line
#NamedQuery(name="Product.hasStock"
query="SELECT p.name, SUM(delRow.amount) - SUM(disRow.amount)
FROM Product p join p.deliveryRows delRow join p.dispatchRows disRow
HAVING SUM(delRow.amount) - SUM(disRow.amount) > 0");
Then execute this query with an EntityManager
#PersistenceContext
EntityManager em;
public void execute(){
List<Object[]> products =
em.createNamedQuery("Product.hasStock").getResultList();
/* Projections return a List<Object[]> where position 1 in the object array
corresponds with the first field in the select statement, position two
corresponds with the second field and so on... These can also be strongly typed
if an object is created and the constructor is specified in JPQL statement
*/
}
I know this is a different approach than using the Criteria API, but in my opinion JPQL queries are vastly superior to the Criteria API. Compared to the JPQL syntax, which is very similar to SQL the API felt less concise and intuitive. If you decide to take this route, I have created a video tutorial that demonstrates #NamedQueries and shows how to strongly type the results of queries containing projections. It can be found here.
I have a table "mytable" with a column "name". I wish to query for all rows that start with a certain string or have the string somewhere in the name, and order them in a way that the rows that start with the name come first. Is it possible to have just one query?
I cannot use "union", I'm using Hibernate and it is not supported. I would prefer not to use views as they are not cached by Hibernate second level cache.
perhaps something like this could work?
select nvl(mytable1.name, '') || nvl(mytable2.name, '') as name,
nvl(mytable1.i, '') || nvl(mytable2.i, '') as i from
(select 1 as i, name from mytable where name like ('string%')) mytable1
full outer join
(select 2 as i, name from mytable where name like ('%string%')) mytable2
where (mytable1.name is null or mytable2.name is null)
and mytable1.name != mytable2.name
order by i, name
but I think I would have used one of the following:
a simple query with union for this query
a sort in Java instead, and do it as 2 queries.
In Oracle you can do:
select *
from myTable mt
where mt.column like '%TargetString%'
order by instr(mt.column, 'TargetString')
If you are using Hibernate + Oracle, it supports instr function in the dialect.
I believe other RDBMSs has similar functions too, which may be supported by Hibernate dialect.
Edit: Since you have an aversion to leveraging more than one Criteria Query - for whatever reason - I would suggest reading up on the Order class. It will not provide you with what you want. That leaves you with few options.
One option would be to leverage Hibernate Search.
Another would be to leverage Hibernate for the query and then Collections.sort(...) for your ordering.
List<MyObject> lstMyObject = ...;
Collections.sort(lstMyObject, new Comparator<MyObject>() {
#Override
public int compare(MyObject o1, MyObject o2) {
// Assuming 'argument' is passed to this method and defined as 'final'
return o1.getName().startsWith(argument) && o2.getName().startsWith(argument) ? 0 :
o1.getName().startsWith(argument) ? -1 : 1;
}
}
This satisfies your requirement of 1) Using Hibernate, 2) Not using multiple queries, 3) Not using native SQL, 4) Not using views, 5) Not using a 'union'.
My advice would be two queries, something like:
public List<MyObject> findMyObjects(...) {
Criteria queryStartsWith = sf.getCurrentSession().createCriteria(MyObject.class)
.add(Restrictions.like("myProperty",nameArgument,MatchMode.START))
.addOrder(Order.desc);
Criteria queryLike = sf.getCurrentSession().createCriteria(MyObject.class)
.add(Restrictions.like("myProperty",nameArgument,MatchMode.ANYWHERE))
.addOrder(Order.desc);
List<MyObject> lstMyObject = new ArrayList<MyObject>();
for (Object curObject : queryStartWith.list())
if (curObject instanceOf MyObject)
lstMyObject.add((MyObject) curObject);
for (Object curObject : queryLike.list())
if (curObject instanceOf MyObject)
lstMyObject.add((MyObject) curObject);
return lstMyObject;
}
I'm not sure Hibernate will accommodate your conditional Ordering. The org.hibernate.criterion.Order class is pretty limited, to be honest.
Other than using native SQL, this is the only option I can think of off the top of my head.