Hibernate criteria, distinct association property - java

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?

Related

How can I get the same result in JPA hibernate as querying "select * from table where ..."?

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.

Select only specific columns from joined tables (Many-to-Many) in Spring Data JPA

The purpose is to select columns from joined tables (Many-to-Many).
The problem i have is to select two columns from a joined Many-to-Many table.
I'am using Springboot 2.3 and Spring data Jpa.
I have this data model, and what i want to fetch are the blue boxed fields
So the native query could look like this (if i am right ...)
SELECT bg.id, bg.name, p.name, c.name, c.short_desc FROM boardgame as bg
JOIN boardgame_category bgc on bg.id = bgc.fk_game
JOIN publisher p on bg.fk_publisher = p.id
JOIN category c on bgc.fk_category = c.id
WHERE bg.id = :id
I first tried to work with dto in JPQL statment
public class BoardgameDto {
private long id;
private String name;
private String publisherName;
private Set<CatregoryDto> categoryDto;
// setter, getter etc...
}
public class CategoryDto {
private String name;
private String shortDesc;
// setter, getter etc...
}
The JQPL query could look like this , but it doesn't work (IDE shows errors on CategoryDto)
/* THIS DOESN'T WORK */
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name,
new org.moto.tryingstuff.dto.CategoryDto(c.name, c.short_desc)) FROM Boardgame as bg, Publisher as p, Category as c
Well, I think the problem I have with this way of doing is that the dto's contructor can't receive a collection as written here, and i think neither another contructor in parameter.
Then i started looking at Criteria Queries, especialy multiselect, Tuple, Dto, but it look like i had same kind of problems so i didn't dive deeper into it.
Finally i used a JpaRepository and it's findById() method like this
public interface BoardgameRepository extends JpaRepository<Boardgame, Long> {
}
// In a test or service method
Boardgame game = repository.findById(long id);
Then i filter the fields i need to keep through mappings in Service or Controller layer. So the front only received need datas.
But it feel a bit overkill,
Am I missing something, any part of the framework that would allow me to select only specific columns?
As you wrote, you can't use a collection as the parameter of a constructor expression. That's because the expression gets applied to each record in the result set. These records are a flat data structure. They don't contain any collections. Your database returns a new record for each element in that collection instead.
But your constructor expression fails for a different reason. You're trying to combine 2 constructor expressions, and that's not supported. You need to remove the 2nd expression and perform that operation within the constructor of your DTO.
So, your query should look like this:
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name, c.name, c.short_desc) FROM Boardgame as bg <Your JOIN CLAUSES HERE>
And the constructor of your BoardgameDto like this:
public class BoardgameDto {
public BoardgameDto(Long id, String gameName, String publisherName, String categoryName, String description) {
this.id = id;
this.name = gameName;
this.publisherName = publisherName;
this.category = new Category(categoryName, description);
}
...
}

Parameters not being set in native query

I am working on a SpringBoot Application and I am trying to do a simple delete using: #Query(value="", nativeQuery = true)
My query has dynamic parameters but the parameters are not getting substituted and I am also not getting any error on console.
I tried the NativeQuery with hardcoded parameters and it worked perfectly fine.
Below is the code:
#Repository
public interface TypGbPccRepo extends JpaRepository<TypGbPcc, String>{
#Transactional
#Modifying(ClearAutomatically = true)
#Query(value = "delete from T_ST_KFR__TYP_GB_PCC typ where Trunc(typ.GUELTIG_AB) IN (?1) and typ.GB_PCC IN (?2)" , nativeQuery=true)
void deleteRecords(String datumStr , String gbPccStr);
}
I even tried the below query:
#Query(value = "delete from T_ST_KFR__TYP_GB_PCC typ where Trunc(typ.GUELTIG_AB) IN (:datumStr ) and typ.GB_PCC IN (:gbPccStr)" , nativeQuery=true)
void deleteRecords(#Param("datumStr ") String datumStr , #Param("gbPccStr") String gbPccStr);
Parameters I am trying to substitute:
datumStr - TO_DATE('12-Sep-2012', 'dd-MM-yy'), TO_DATE('14-Sep-2012', 'dd-MM-yy')
gbPccStr - 'P0','P1'
I am implementing the above code to delete records based on a Composite Primary key which is a combination of GUELTIG_AB & GB_PCC. If possible suggest me how to achieve this with a query QueryMethod?
I have been able to delete records based on Primary which is not composite by using QueryMethod but I am unable to do this for a composite Primary key. I have to delete Multiple records in one go.
Below is my POJO
#Entity
#IdClass(TypGbPccId.class)
#Table(name="T_ST_KFR_TYP_GB_PCC")
public class TypGbPcc implements Serializable{
private static final long serialVersionUID = .....
#Id
#Column(name="GB_PCC")
private String gbPcc = null ;
#Column(name="GB_PCC_desc")
priavte String gbPccDesc = null ;
#Column(name="GB_PCC_SQL")
priavte String gbPccSql = null ;
#Id
#Column(name="GUELTIG_AB")
#JsonFormat(pattern = "dd-MM-yyyy")
private String gueltigAb = null ;
}
setter & getters here
These are bind parameters, they get passed to the database separately from the SQL statement. They are not used to create a new SQL statement with the parameters concatenated into it.
So what your Trunc(typ.GUELTIG_AB) IN (?1) with the given parameter really boils down to is:
Throw away the time part from GUELTIG_AB, convert it to a String using the systems default format, whatever that happens to be and check if the resulting string is contained in the list consisting of a single string: "TO_DATE('12-Sep-2012' , 'dd-MM-yy'),TO_DATE('14-Sep-2012' , 'dd-MM-yy')"
What you probably want is closer to:
Is GUELTIG_AB element in the list of dates with the two elements 12th of September 2014 and 14th of September 2014
Therefore your method should look like:
#Modifying(ClearAutomatically = true)
#Query(value = "delete from T_ST_KFR__TYP_GB_PCC typ " +
"where Trunc(typ.GUELTIG_AB) IN (?1) " +
"and typ.GB_PCC IN (?2)"
, nativeQuery=true)
void deleteRecords(List<java.sql.Date> dates , List<String> gbPccStr);
Note: you don't need the #Transactional annotation on the method, nor the #Repository annotation on the interface.
I am implementing the above code to delete records based on a Composite Primary key which is a combination of GUELTIG_AB & GB_PCC. If possible suggest me how to achieve this with a query QueryMethod?
That is not at all what the current query is doing.
If you have rows with the following keys:
(d1,p1)
(d1,p2)
(d2,p1)
(d2,p2)
You can't delete for example (d1,p1) and (d2,p2) with this approach, because it will also delete the other two elements.
In order to fix that declare an #IdClass on your entity. Specify that in the type parameters for your Repository and use a normal query method.
First of all there is a space after datumStr in #Param("datumStr "). Next, change its type to List of String and pass the date strings in your default DB date format.
(You should change the type of gbPccStr to List of Sting as well)
You can do something like this.
#Query(value = "delete from TypGbPcc where func('TRUNC',gueltigAb) IN func('TO_DATE',:dateOne,'dd/mm/yyyy') and
func('TRUNC',gueltigAb) IN func('TO_DATE',:dateTwo,'dd/mm/yyyy') and gbPcc IN (:gbPccStr)" , nativeQuery=true)
void deleteRecords(#Param("dateOne") String dateOne ,#Param("dateTwo") String dateTwo, #Param("gbPccStr") String gbPccStr);
in dateOne give first date in dateTwo give second date

Problems mapping Hibernate entities - native query containing left join with condition

This should be straight-forward though can't get my Hibernate entities to play nice for the following scenario with a simple two table structure:
I'm attempting to get all config names and matching config values for a given currency code (and null's where not matching).. so have written a native query to retrieve the following like so:
SELECT * FROM CONFIG_NAME LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID =
CONFIG_VALUE.CONFIG_ID AND CONFIG_VALUE.CURRENCY_CODE = '<CURRENCY_CODE>'
ORDER BY CONFIG_NAME.ID
This query doesn't seem to play nice with my Hibernate mapping as it appears to be essentially ignoring the CURRENCY_CODE clause in the join.
Essentially, for the following subset of data:
CONFIG_NAME:
CONFIG_VALUE:
There is no value defined for 'FREE_SHIPPING_ENABLED' for 'USD' so running the query above for both currency code returns as expected:
QUERY RESULTS FOR 'CAD':
QUERY RESULTS FOR 'USD':
I'm running the above query as a native query in a JpaRepository for the ConfigName entity. But what I appear to be getting is that it seems to ignore the currency_code clause in the JOIN condition. As the list of config values defined has both values for USD and CAD where they're populated. Is there an Hibernate annotation to factor this in that I'm unaware of?
It's worth bearing in mind there will only ever be ONE value defined for each config for a given currency - there's a unique constraint across CONFIG_VALUE.CONFIG_ID/CONFIG_VALUE.CURRENCY_CODE so potentially ConfigValue on the ConfigName entity would not need to be a map.
Mappings as are follows:
ConfigName - Entity
#OneToMany(mappedBy = "config")
private Set<ConfigValue> configValue;
ConfigValue - Entity
#ManyToOne(optional = false)
#JoinColumn(name="CONFIG_ID")
#Property(policy=PojomaticPolicy.NONE)
private ConfigName config;
Doesn't need to be strictly unidirectional either.. as I'm only concerned with the values from the ConfigName entity either being populated or null.
Think I'm missing something simple, so hope someone can help.
EDIT: Am querying using JpaRepository:
Am using JpaRepository to query:
#Repository
public interface ConfigNameRepository extends JpaRepository<ConfigName, Long>
{
static final String SQL_QUERY = "SELECT * FROM CONFIG_NAME "
+ "LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID = CONFIG_VALUE.CONFIG_ID "
+ "AND CONFIG_VALUE.CURRENCY_CODE = ?1 ORDER BY CONFIG_NAME.ID";
#Query(value = SQL_QUERY, nativeQuery = true)
List<ConfigName> findConfigValuesByCurrencyCode(final String currencyCode);
}
As mentioned by #Ouney, your JPA relations are not taken in account if you use a native query.
You declared a SELECT * and List<ConfigName> (the real sql result contains ConfigName+ConfigValue). So with this query, Hibernate fetchs all the ConfigName. Then, when you try to access to the set of configValue, it fetchs all the related ConfigValue.
I think this should be better/easier to use a JPQL query instead (but you need Hibernate 5.1+) :
SELECT n, v
FROM ConfigName n
LEFT JOIN ConfigValue v
ON v.config = n AND v.currencyCode = :currencyCode
ORDER BY n.id
With this method signature :
List<Object[]> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
Where the result will be :
o[0] // ConfigName
o[1] // ConfigValue (nullable)
You may want to do this prettier with a wrapper :
SELECT new my.package.MyWrapper(n, v)
...
MyWrapper constructor :
public MyWrapper(ConfigName configName, ConfigValue configValue) {
...
}
Method signature with the wrapper :
List<MyWrapper> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
(update)
I think in this case, your query can be :
SELECT n, v // or new my.package.MyWrapper(n, v)
FROM ConfigName n
LEFT JOIN n.configValue v
WITH v.currencyCode = :currencyCode
ORDER BY n.id

List items per sales using JPA

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)

Categories

Resources