I need to execute several different queries and I want to use the same POJO to get the results. What I get with those queries are combinations of same columns, can I reuse the same sqlResultSetMapping?
I am working with JPA-2.1 and Java 8
My queries are something like:
select tableA.a, tableB.b, tableA.c
from tableA
inner join tableB
select tableA.a, tableB.b
from tableA
inner join tableB
select tableB.b, tableA.c
from tableA
inner join tableB
My POJO is something like:
public class Result {
String a;
String b;
String c;
}
and finally, my SqlResultSetMapping is:
#SqlResultSetMapping(
name="GeneralResult",
classes = {
#ConstructorResult(
targetClass = Result.class,
columns = {
#ColumnResult(name="a", type=String.class), #ColumnResult(name="b", type=String.class), #ColumnResult(name="c", type=String.class)
}
)
}
)
When I execute the first query, with fields a,b, the call works fine. The problem is when I execute one of the other two queries.
Can I use the same SqlResultSetMapping for those queries?
I tried with several ConstructorResult but the problem is all fields are String (VARCHAR in DB).
Thanks in advance.
Related
I'm trying to find the best way to map my data on ORM. I have a query which gets me data from MySQL database which look like
SELECT d.ID AS Id,
equipment.EQUIP_ID,
equipment.EQUIP_REFERENCE
FROM
tbl_devices d
INNER JOIN
tbl_equipment equipment ON equipment.EQUIP_ID = d.DEV_ID
What would be the most optimal way to get these data with Spring boot and Spring data??
Should I use #Query annotation and execute this or somehow create entities for Equipment and Devices tables and then use JPQL/HQL to join tables in a query, but then how should I map the result??
Thanks in advance.
You can use JdbcTemplate (import from org.springframework.jdbc.core.JdbcTemplate) to run the SQL statement above you wrote. After you can create a pojo to map result into it via BeanPropertyRowMapper. For example:
final String sql = "SELECT d.ID AS Id, equipment.EQUIP_ID, equipment.EQUIP_REFERENCE FROM tbl_devices d INNER JOIN tbl_equipment equipment ON equipment.EQUIP_ID = d.DEV_ID";
YourPojo result = jdbcTemplate.query(
sql,
new BeanPropertyRowMapper<>(YourPojo.class)
);
Pojo class maybe like following:
#Data
public class YourPojo {
private Long id;
private Long equipId;
private Your_type equip_reference_name;
}
A quick and dirty solution would be to use projections.
First, you create a projection interface:
public interface DeviceDetails {
Long getId();
Long getEquipId();
String getEquipReference();
}
You then modify the query to match column aliases with the projection properties:
SELECT d.ID AS id,
equipment.EQUIP_ID as equipId
equipment.EQUIP_REFERENCE As equipReference
...
Finally, you put the query method in a repository of your choice:
#Query(value =..., nativeQuery = true)
List<DeviceDetails> findDeviceDetails();
I am using Spring Boot, Groovy and JPA/Hibernate to migrate an old application. The only restriction is that the database model must not be changed and I found myself with a weird situation in which a OneOnOne relationship:
Please look at the following model setup:
#Entity
#Table(name='table1')
class Table1 {
#Id
Table1Id id
#Column(name='sequence_num')
Integer seqNum
#Column(name='item_source')
String itemSource
#Column(name='source_type')
String sourceType
#OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumn(name='key_field_2', insertable=false, updatable=false)
#NotFound(action=NotFoundAction.IGNORE)
// #Fetch(FetchMode.JOIN)
Table2 table2
}
#Embeddable
class Table1Id implements Serializable {
#Column(name='key_field_1')
String key1
#Column(name='key_field_2')
String key2
}
#Entity
#Table(name='table2')
class Table2 {
#Id
#Column(name='key_id')
String keyId
#Column(name='field1')
String field1
#Column(name='field2')
String field2
#Column(name='field3')
String field3
}
My Spock test looks as follows:
def "Try out the JOIN select with Criteria API"() {
given:
CriteriaBuilder cb = entityManager.getCriteriaBuilder()
CriteriaQuery<Object[]> cQuery = cb.createQuery(Object[].class)
Root<Table1> t1 = cQuery.from(Table1.class)
Path<Table2> t2 = t1.get('table2')
Join<Table1, Table2> lanyonLeftJoin = t1.join('table2', JoinType.INNER)
Predicate where = cb.equal(t1.get('itemSource'), 'ABC')
cQuery.multiselect(t1, t2)
cQuery.where(where)
when:
List<Object[]> result = entityManager.createQuery(cQuery).getResultList()
then:
result.each{ aRow ->
println "${aRow[0]}, ${aRow[1]}"
}
}
This configuration successfully generates an INNER JOIN between Table1 and Table2, NOTE that even the constant on the "where" clause is correctly interpreted.
However for some strange reason Table2 gets re-queried for every row returned in the first query.
The output that I see is:
Hibernate:
select
table10_.key_field_1 as key_field_11_3_0_,
table10_.key_field_2 as key_field_22_3_0_,
table21_.key_id as key_id1_5_1_,
table10_.item_source as item_source3_3_0_,
table10_.sequence_num as sequence_num4_3_0_,
table10_.source_type as source_type5_3_0_,
table21_.field2 as field23_5_1_,
table21_.field3 as field34_5_1_,
table21_.field1 as field15_5_1_
from
table1 table10_
inner join
table2 table21_
on table10_.key_field_2=table21_.key_id
where
table10_.item_source=?
Hibernate:
select
table20_.key_id as key_id1_5_0_,
table20_.field2 as field23_5_0_,
table20_.field3 as field34_5_0_,
table20_.field1 as field15_5_0_
from
table2 table20_
where
table20_.key_id=?
Hibernate:
select
table20_.key_id as key_id1_5_0_,
table20_.field2 as field23_5_0_,
table20_.field3 as field34_5_0_,
table20_.field1 as field15_5_0_
from
table2 table20_
where
table20_.key_id=?
// 500+ more of these
As we can see the first query successfully returns all rows from both tables and it is actually the exact query I am looking for. However there is all those unnecessary extra queries being performed.
Is there any reason why JPA would do such thing and is there a way to prevent it??
I have the impression I am missing something very obvious here.
Thanks in advance for your help
Update 1
If I replace
cQuery.multiselect(t1, t2)
for
cQuery.multiselect(t1.get('id').get('key1'), t1.get('id').get('key2'),
t1.get('fieldX'), t1.get('fieldY'), t1.get('fieldZ'),
t2.get('fieldA'), t2.get('fieldB'), t2.get('fieldC') ...)
It generates the exact same inner join query and DOES NOT re-queries Table2 again.
In other words, looks like (at least for this case) I need to explicitly list all the fields from both tables. Not a nice workaround as it can get very ugly very quickly for tables with a lot of fields. I wonder if there is a way to retrieve all the #Column annotated fields/getters without resourcing to a bunch of reflection stuff?
I think I've got it!
#JoinFormula:
Primary Key in Table2 is INT and the field in Table1 that is used as FK is String (I completely missed that! duh!). So, the solution for that was to apply a #JoinFormula instead of a #JoinColumn in the form of:
#OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumnsOrFormulas([
#JoinColumnOrFormula(formula=#JoinFormula(value='CAST(key_field_2 AS INT)'))
])
#NotFound(action=NotFoundAction.IGNORE)
Table2 table2
This strangely returns a List<Object[]> each item of the List contains an array of 2 elements: one instance of Table1 and one instance of Table2.
Join Fetch:
Following your suggestion I added "join fetch" to the query, so it looks like:
select t1, t2 from Table1 t1 **join fetch** t1.table2 t2 where t1.itemSource = 'ABC'
This causes Hibernate to correctly return a List<Table1>
Either with the #JoinFormula alone or the #JoinFormula + "join fetch" hibernate stopped generating the n+1 queries.
Debugging Hibernate code I've found that it correctly retrieves and stores in Session both entities the first time it queries the DB with the join query, however the difference between PK and FK data types causes Hibernate to re-query the DB again, once per each row retrieved in the first query.
I have two entities, A and B. Entity A has the following annotations.
#OneToOne(mappedBy="entityA", optional=true)
public EntityB getEntityB() {
return b;
}
public void setEntityB(EntityB b) {
this.b = b;
}
Entity B has the following annotations
#OneToOne
public A getEntityA() {
return a;
}
public void setEntityA(EntityA a) {
this.a = a;
}
When I query the following, everything works fine.
String hql = "from EntityB eb where eb.entityA is null";
When I try to query with this, it doesn't work.
String hql = "from EntityA ea where ea.entityB is null";
If I follow this other StackOverflow answer, the query works. Why do I need to do a join to find out if the relationship is null?
In your query you are using HQL implicit join syntax. According to Hibernate docs 15.4. Forms of join syntax this form of join results in SQL inner join, which by its nature filters only those items where a match exists in the other table. Using explicit left join in hql will make your query more transparent. Turn on SQL query logging via persistence.xml or via your server's logging config to examine the actual SQL commands generated by hibernate.
I wrote a hsql:
String queryString = "select t1.a, t1.b, t2.c from table1 t1, table2 t2 where t1.id = t2.id";
and then I have a class:
class test{
String a;
String b;
String c
....//other getter and setter
}
I tried:
List = getHibernateTemplate().find(queryString);
this doesn't work, when I use test object in jsp page, it will throw out exception.
I have to manually create a test object:
List<Object[]> list = getHibernateTemplate().find(queryString);
test.seta(list.get(0)[0]);
is it possible for hibernate to automatically map the class for me in hsql ?
If you have a mapping for both table1 and table2 (see Prashant question above) you can do something like:
String queryString = "select t1 from table1 t1
inner join t1.table2 t2";
After you run the query you should have a list of t1 objects.
for(Table1 t1:listOfTable1Objects) {
t1.getA(); //for example or whatever you want to do with your object.
}
The Problem is that you do not write a HQL query. You just write a normal SQL query. In HQL, because the hibernate make the mapping from table to class, you cannot make a projection. So, if you write something like
String query = "FROM Class1 WHERE ome_condition;
without the SELECT clause, the Hibernate will be able to convert the result in the proper object.
You can see more about this here: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/queryhql.html
If you dont have a mapping, you may create a auxiliary class for this. Say ResultClass. Then you add #NamedNativeQuery and #SqlResultSetMapping annotations to the class:
#NamedNativeQuery(name="queryHehehe", query="select t1.field1 f1, t2.field2 f2 from table1 t1, table2 t2", resultSetMapping="mappingHehehe")
#SqlResultSetMapping(name="mappingHehehe", entities={
#EntityResult(entityClass=my.clazz.AuxiliaryClass.class, fields = {
#FieldResult(name="id", column="f1"),
#FieldResult(name="other_property", column="f2")
}),
})
public class AuxiliaryClass {
public Long id;
public String other_property;
}
I have never used this, but can work. Good luck.
If you need a query to return values from multiple tables and create an object of an unmapped class, then you need to either do what you're doing here, or use a ResultTransformer.
In order to do this with HibernateTemplate, you'll need to change the way you use the template, possibly using execute(HibernateCallback action), as you'll need to convert the sql query to a Criteria as described in Hibernate Reference Native SQL Chapter.
If you do want to try this, you'll probably want to use an AliasToBeanResultTransformer or AliasToBeanConstructorResultTransformer rather than writing your own transformer.
My entity is defined like this:
public class Entity implements Serializeable {
#ManyToMany(mappedBy="entities")
private List<OtherEntity> otherEntities;
}
How can I select in HQL all Entities which have more than one OtherEntity?
I think this should work. This will generate a SQL with a subquery - not fetch it and filter it in memory.
from Entity e where e.otherEntities.size > 1