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
Related
I am writing an application with Spring 4 and Hibernate 5.1. I need to be able to interoperate with a legacy system that saves SQL queries using the native table and column names. I need to be able to add a Selection object to my Tuple query which uses the original column name rather then the entity field name.
I tried doing this using Hibernate with Projections.sqlProjection(column_name... and that sort of worked, but other issues are preventing me from continuing in this direction.
#Transactional(propagation = Propagation.MANDATORY)
public List<Object[]> testQuery() {
Class<?> rootClass = getEntityClassByTable("pm_project");
List<String> columnList = new ArrayList<>();
columnList.add("pm_project.pm_project_id");
columnList.add("pm_project.pm_project_name");
columnList.add("pm_project.pm_project_title");
columnList.add("pm_project.parent_id");
columnList.add("pm_project.pm_project_from_date");
CriteriaQuery<Tuple> query = criteriaBuilder.createTupleQuery();
Root<?> root = query.from(rootClass);
List<Selection> selectionList = new ArrayList<>();
for (String column : columnList) {
Selection s = nativeSelection(column);
selectionList.add(s);
}
query.multiselect(selectionList.toArray());
List<Tuple> resultList = em.createQuery(query).getResultList();
return resultList;
}
I need 'NativeSelection', something that produces a Selection for a native column name, not an entity attribute name. It would be really good if it took any sql that could be a field, because some JSON_VALUE fields might pop up.
The values of the native sql are dynamic, and I am not receiving a full SQL, only column and table names, with possible JSON_VALUE calls. I would very much like not to have to generate native sql, as I want to leverage other JPA features in my query.
If you are using native query then no need to specify entity attribute names. All you have to do is to set the value of the nativeQuery attribute to true and define the native SQL query in the value attribute of the annotation
#Query(
value = "SELECT * FROM pm_project p WHERE p.pm_project_id= 1",
nativeQuery = true)
Collection<Project> findProjectById();
You can also do it without using native query by simple JPA
List<Project> findAllByProjectId(Integer id);
I have two tables in my PostgreSQL database:
CREATE TABLE tableOne (id int, name varchar(10), address varchar(20))
CREATE TABLE tableTwo (id int, info text, addresses varchar(20)[])
now I want to create a join as follows:
SELECT * FROM tableOne JOIN tableTwo ON address = ANY(addresses)
I tried to achieve this using Hibernate - class TableOne:
#Entity
#Table(name = "tableOne")
class TableOne {
private int id;
private TableTwo tableTwo;
private String address;
#Id
#Column(name = "id")
public getId() { return id; }
#ManyToOne
#JoinFormula(value = "address = any(tableTwo.addresses)",
referencedColumnName = "addresses")
public TableTwo getTableTwo(){
return tableTwo;
}
// Setters follow here
}
But Hibernate keeps generating queries with non-sense JOIN clauses, like:
... JOIN tableTwo ON _this.address = any(tableTwo.addresses) = tableTwo.addresses
How do I tell Hibernate using annotations to format my join query correctly? Unfortunately, our project must be restricted only to the Criteria API.
EDIT:
After suggestion from ashokhein in the comments below, I annotated the method getTableTwo() with just #ManyToOne - and now I would like to do the join using Criteria API, presumably with createAlias(associationPath,alias,joinType,withClause) method where withClause would be my ON clause in the join.
But Im not sure what to put as associationPath and alias parameters.
Any hints?
To support PostgreSQL array you need a custom Hibernate Type. Having a dedicated user type will allow you to run native SQL queries to make use of the type:
String[] values = ...
Type arrayType = new CustomType(new ArrayUserType());
query.setParameter("value", values, arrayType);
HQL supports ANY/SOME syntax but only for sub-queries. In your case you'll need a native query to use the PostgreSQL specific ANY clause against array values.
You can try Named Query.
#NamedQuery(name="JOINQUERY", query="SELECT one FROM tableOne one JOIN tableTwo two ON one.address = :address" )
#Entity
class TableOne{......
Retrieving part is:
TypedQuery<TableOne> q = em.createNamedQuery("query", TableOne.class);
q.setParameter("address", "Mumbai");
for (TableOne t : q.getResultList())
System.out.println(t.address);
You might need to do some permutations on the query
So after a lot of time searching for the right answer, the only real solution that works for us is creating a view:
CREATE VIEW TableA_view AS SELECT TableOne.*,TableTwo.id FROM TableA JOIN TableTwo ON TableOne.address = ANY(TableTwo.addresses)
and mapping it to an entity TableOne instead of the original table.
This was the only solution for us besides, of course, using a named query, which was a no-go as we needed to stick to the Criteria API.
As #ericbn has mentioned in the comments this is really an example where ORM gets really annoying. I would never expect that custom join clause like this is not possible to do in Hibernate.
#JoinFormula should contain SQL instead of HQL.
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/JoinFormula.html
I've stumbled upon a problem with Hibernate. I've 2 entities - let's say A and B like so (Entity/Table annotations ommited):
class A {
#ManyToOne
#JoinColumn(name = "b_id")
private B b;
}
class B {
#Column(name = "name")
private String name;
}
Now, I'm trying to query all A entities and ordering them by name field of B's entity like so:
SELECT q FROM A AS q ORDER BY q.b.name asc nulls last
The problem is, there are rows in A's table having null foreign-key (b is null) - in result the aforementioned query returns only rows that don't contain null in b field, and I'd like to have them all.
I guess hibernate joins the table without using LEFT JOIN (OUTER JOIN?) resulting in null values being skipped.
Is there any way to change this behaviour? It would be great, if I could solve it by using annotations in entity classes, because the query-generating mechanism is pretty locked up.
You can use CriteriaBuilder and set alias on entityRoot
Root<A> entityRoot = criteriaQuery.from(A);
entityRoot.join("b", JoinType.LEFT).alias("b");
criteriaQuery.select(entityRoot)
.orderBy(criteriaBuilder.asc(entityRoot.get("b").get("name"))
;
you can use criteria query for this but you will have to create session while using that, it is simpler to access database using criteria:
Criteria criteria = session.createCriteria(A.class)
//create alias of your other class to provide ordering according to foriegn key
criteria.createAlias("foreignkey","keyin table A(eg..b)");
criteria.addOrder(Order.asc(b.name));
List list = criteria.getlist();
hope this helps
I would like to map Entity into another Entity (1:1) and specifying condition in sql expression using Hibernate.
I have ERA scheme with tables Position and Subject. Subject has relationship 1:N to positions.
Each Position has date of creation. I would like to map last created position (one from N with same subject_id) as entity into entity Subject via hibernate.
I tried #Formula but wasn't successful.
#Entity
public class Position {
..
}
#Entity
public class Subject {
..
#Basic(fetch = FetchType.LAZY)
#Formula("select p.* from position p inner join (select subject_id, max(position_date) maxdate from position where subject_id = 3743073 group by subject_id ) pmaxgrouped on p.subject_id = pmaxgrouped.subject_id and p.position_date = pmaxgrouped.maxdate")
private Position lastPosition;
}
Hibernate mismatches result sql query.
SELECT *
FROM
(SELECT giposobj0_.POSOBJ_ID AS POSOBJ1_1688_,
giposobj0_.SYS_AGENDA_ID AS SYS2_1688_,
giposobj0_.SYS_INS_DATE AS SYS3_1688_,
giposobj0_.SYS_LOGIN_ID_INS AS SYS4_1688_,
giposobj0_.SYS_LOGIN_ID_UPD AS SYS5_1688_,
giposobj0_.SYS_UPD_DATE AS SYS6_1688_,
giposobj0_.SYS_LOGIN_ID_OWNER AS SYS7_1688_,
giposobj0_.SYS_ORGUNIT_ID AS SYS8_1688_,
giposobj0_.SYS_PUBKIND_LEVEL AS SYS9_1688_,
giposobj0_.POSOBJ_CODE AS POSOBJ10_1688_,
giposobj0_.POSOBJ_IMEI AS POSOBJ11_1688_,
giposobj0_.POSOBJ_LASTSTATUS AS POSOBJ12_1688_,
giposobj0_.POSOBJ_MODEL AS POSOBJ13_1688_,
giposobj0_.POSOBJ_NOTIF AS POSOBJ14_1688_,
SELECT p.*
FROM position p
INNER JOIN
(SELECT giposobj0_.posobj_id,
MAX(giposobj0_.position_date) giposobj0_.maxdate
FROM position
WHERE giposobj0_.posobj_id = 3743073
GROUP BY giposobj0_.posobj_id
) giposobj0_.pmaxgrouped
ON p.posobj_id = pmaxgrouped.posobj_id
AND p.position_date = pmaxgrouped.maxdate AS formula11_
FROM eira_gi.VS_POSOBJ giposobj0_
WHERE giposobj0_.POSOBJ_IMEI=111111111111111
);
Option1: You create a ordered list with #OrderBy. Subsequently, you can return the last object from that collection. Here is a very nice tutorial.
Option2: Save the correlated "lastPosition" every time you saveOrUpdate a Subject.
I have a hibernate call in my DAO that looks like this
List<Associate> associate = (List<Associate>)session.createSQLQuery("SELECT * FROM associates WHERE fk_id = :id AND fk_associate_id = (SELECT id FROM users WHERE fk_user_type = 2)").setParameter("id", id).list();
I am getting an error saying that I cannot cast the resulting list to the model type Associate. I don't understand why this is happening. I am returning only the fields that are in the associates table.
You need to specify the class of entity the result should be converted to using addEntity(), because you are executing SQL query that doesn't know anything about entities:
List<Associate> associate = (List<Associate>) session.createSQLQuery(
"SELECT * FROM associates WHERE fk_id = :id AND fk_associate_id = (SELECT id FROM users WHERE fk_user_type = 2)")
.addEntity(Associate.class)
.setParameter("id", id).list();
See also:
18.1.2. Entity queries
It is possible to apply a ResultTransformer to native SQL queries, allowing it to return non-managed entities.
sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.setResultTransformer(Transformers.aliasToBean(CatDTO.class))
The above query will return a list of CatDTO which has been instantiated and injected the values of NAME and BIRTHNAME into its corresponding properties or fields.
source : Link