I've run into some code which behaviour is not clear for me.
We have first entity:
#Data
#Entity
#Table(name = "Info")
public class Info {
#OneToOne
#JoinColumn(name = "data_id")
private Data data;
}
and second one:
#Data
#Entity
#Table(name = "Data")
public class Data {
#Id
private Long dataId
}
And we have next method for retrieving data:
#Query(value = "SELECT * FROM Info i inner join Data d on d.data_id=i.data_id",
nativeQuery = true)
List<Info> getInfo() {}
As nativeQuery = true is present I expect this method make just one SQL select and retrieve me data. But If I take a look at logs actually there are 2 selects:
SELECT * FROM Info i inner join Data d on d.data_id=i.data_id;
SELECT * FROM Data d where d.data_id = 123;
Why this is happening ? How to fix it to make only one select and retrieve all data ?
It's not possible to specify native query fetches without Hibernate specific APIs. I would suggest you to use a normal JPQL/HQL query:
#Query(value = "FROM Info i join fetch i.data")
List<Info> getInfo();
This will do the same as your native query but at the same time just run only a single query.
Related
Usually during my work hours i spend a lot of time querying the db(oracle) and parsing blob from various table where the streams that we receive are stored.
There are various type of stream so i was trying to made a simple webapp where i write the select statement and it returns all the stream parsed accordingly.
My problem is that using jpa and executing the simple native query:
select B_BODY from TABLE_B where TRANSACTION_ID = 'GG-148c-01502790743907855009';
the statement doesn't return anything but querying directly the database return the record.
this is my java code:
#Transactional(readOnly = true)
public List<Object[]> retrieveBlobs(String squery) {
squery = squery + " and rownum <= "+maxResults;
Query query = em.createNativeQuery(squery);
List<Object[]> resultList = query.getResultList();
return resultList;
}
this is the sql generated:
Hibernate:
select
B_BODY
from
TABLE_B
where
TRANSACTION_ID ='GG-148c-01502790743907855009'
and rownum <= 100
i know that this way might seems weird but our team spend a lot of time trying to tokenize the stored streams(the code that identify how to parse the stream is also stored in the tables).Useless to say this application is going to be used only internally.there is a way to just execute the query as it is and retrieve the correct output?
Well, I tried to reproduce your problem on MariaDB (with mysql-connector-java + hibernate) but selecting a lob with native query was working properly.
You can try to create entities which will be holding your blob and check if this would help. Just make a standard entity with #Lob annotation over your lob column.
#Entity
#NamedQueries(
#NamedQuery(name = FIND_ALL, query = "SELECT m FROM LobEntity m")
)
public class LobEntity {
public static final String FIND_ALL = "PhpEntity.findAll";
#Id
#Column(name = "id")
private String id;
#Lob
#Column(name = "lob")
private byte[] lob;
//Use Blob class if you want to use streams.
//#Column(name = "lob")
//#Lob
//private Blob lob;
}
I have Spring Data method like this:
List<RegionBasics> findTop10ByRegionMappingsActiveTrue();
What I expect is that it will find top 10 records from db in one query but What I see in logs is (I didn't paste whole logs in order to keep this readable but this select query is invoke 10 times):
select regionmapp0_.id as id1_2_1_, regionmapp0_.is_active as is_activ2_2_1_, regionmapp0_.region_basic_id as region_b3_2_1_, regionbasi1_.id as id1_1_0_, regionbasi1_.hotel_count as hotel_co2_1_0_, regionbasi1_.name_long as name_lon3_1_0_, regionbasi1_.name as name4_1_0_, regionbasi1_.type as type5_1_0_ from region_mappings regionmapp0_ left outer join region_basics regionbasi1_ on regionmapp0_.region_basic_id=regionbasi1_.id where regionmapp0_.region_basic_id=?
How can I ensure that this method will hit db only once (instead of 10)?
My Model:
#Data
#NoArgsConstructor
#Entity
#Table(name = "region_basics")
public class RegionBasics {
#Id
Integer id;
#Column
String type;
#Column
String name;
#Column(name = "name_long")
String longName;
#Column(name = "hotel_count")
Integer hotelCount;
#OneToOne(mappedBy="regionBasics")
RegionMappings regionMappings;
}
I think you should join fetching the RegionMappings:
Like this: #Query("SELECT rb FROM RegionBasics r JOIN FETCH r.regionMappings rm WHERE rm.active=true")
With pageable parameter new PageRequest(0,10)
I thought I understood hibernate's fetching strategies, but it seems I was wrong.
So, I have an namedNativeQuery:
#NamedNativeQueries({
#NamedNativeQuery(
name = "getTest",
resultClass = ArticleOnDate.class,
query = "SELECT `a`.`id` AS `article_id`, `a`.`name` AS `name`, `b`.`price` AS `price` FROM article a LEFT JOIN price b ON (a.id = b.article_id) WHERE a.date <= :date"
)
})
#Entity()
#Immutable
public class ArtikelOnDate implements Serializable {
#Id
#OneToOne
#JoinColumn(name = "article_id")
private Article article;
...
}
Then I call it:
Query query = session.getNamedQuery("getTest").setDate("date", date);
List<ArticleOnDate> list = (List<ArticleOnDate>) query.list();
The query returns thousand of entities... Well, ok, but after that query hibernate queries thousand other queries:
Hibernate:
select
article0_.id as id1_0_0_,
article0_.bereich as name2_0_0_,
price1_.price as price1_14_1_
from
article artikel0_
where
artikel0_.id=?
Ok, that's logic, because the #OneToOne relation is fetched eagerly. I don't want to fetch it lazy, so I want a batch fetching strategy.
I tried to annotate the Article property but it didn't work:
#Id
#OneToOne
#JoinColumn(name = "article_id")
#BatchSize(size=100)
private Article article;
So what can I do to fetch the relation in a batch?
I have write a materialized view for full text search against memberstable:
CREATE MATERIALIZED VIEW member_search_index AS
SELECT member.memberid,
member.firstname,
member.lastname,
...
to_tsvector((COALESCE(member.lastname, ' '::character varying))::text) AS document
FROM member
GROUP BY member.memberid;
CREATE INDEX idx_member_search ON member_search_index USING gin(document);
I have created entity and mapped it like this:
#Entity
#Table(name = "MemberSearchIndex")
public class MemberSearch {
#Id
private long memberId;
private String firstName;
private String lastName;
...
}
And here's SQL call I am trying to make:
Query sqlQuery = entityManager.createNativeQuery("SELECT * FROM member_search_index WHERE document ## to_tsquery('doe:*')", MemberSearch.class);
In this example I have hard coded value that I am passing to query doe and the query is returning me 0 elements (it should return 1)
So:
Am I doing mapping correctly?
How to pass parameter to a native query like this? I tried like this .setParameter(1, searchQuery) but I got exception where [1] couldn't be found.
You can use JPQL query instead of native SQL. It will look like this:
Query query = entityManager.createQuery("
SELECT ms
FROM MemberSearch ms
WHERE SQL('to_tsvector(''english'', ?) ## to_tsquery(''english'', ?)', ms.document, ?1)
");
query.setParameter(1, "'doe:*'");
query.getResultList();
I am currently working on a project to transfer some legacy jdbc select statements over to using Hibernate and it's criteria api.
The two relevant table columns and the SQL query looks like:
-QUERIES-
primaryId
-QUERYDETAILS-
primaryId
linkedQueryId -> Foreign key references queries.primaryId
value1
value2
select *
from queries q
where q.primaryId not in (SELECT qd.linkedQueryId
FROM querydetails qd
WHERE (qd.value1 LIKE 'PROMPT%'
OR qd.value2 LIKE 'PROMPT%'));
My entity relationships look like:
#Table("queries")
public class QueryEntity{
#Id
#Column
private Long primaryId;
#OneToMany(targetEntity = QueryDetailEntity.class, mappedBy = "query", fetch = FetchType.EAGER)
private Set<QueryDetailEntities> queryDetails;
//..getters/setters..
}
#Entity
#Table(name = "queryDetails")
public class QueryDetailEntity {
#Id
#Column
private Long primaryId;
#ManyToOne(targetEntity = QueryEntity.class)
private QueryEntity query;
#Column(name="value1")
private String value1;
#Column(name="value2")
private String value2;
//..getters/setters..
}
I am attempting to utilize the criteria api in this way:
Criteria crit = sessionFactory.getCurrentSession().createCriteria(QueryEntity.class);
DetachedCriteria subQuery = DetachedCriteria.forClass(QueryDetailEntity.class);
LogicalExpression hasPrompt = Restrictions.or(Restrictions.ilike("value1", "PROMPT%"),
Restrictions.ilike("value2", "PROMPT%"));
subQuery.add(hasPrompt);
Criterion subQueryCrit = Subqueries.notIn("queryDetails", subQuery);
crit.add(subQueryCrit);
List<QueryMainEntity> entities = (List<QueryMainEntity>) crit.list();
System.out.println("# of results = " + entities.size());
I am getting a NullPointerException on the crit.list() line that looks like
Exception in thread "main" java.lang.NullPointerException
at org.hibernate.loader.criteria.CriteriaQueryTranslator.getProjectedTypes(CriteriaQueryTranslator.java:362)
at org.hibernate.criterion.SubqueryExpression.createAndSetInnerQuery(SubqueryExpression.java:153)
at org.hibernate.criterion.SubqueryExpression.toSqlString(SubqueryExpression.java:69)
at org.hibernate.loader.criteria.CriteriaQueryTranslator.getWhereCondition(CriteriaQueryTranslator.java:380)
at org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:114)
at org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:83)
at org.hibernate.loader.criteria.CriteriaLoader.<init>(CriteriaLoader.java:92)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1687)
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:347)
Now, I think its pretty safe to say I'm using the Criteria Api/Detached Query Api incorrectly, but I'm not sure what the 'correct' way to do it is since the Hibernate Docs only briefly cover criteria api subqueries.
I realize this is a pretty long question, but I figure its appear to put it all the relevant aspects of the question (query I'm attempting to represent via Criteria API, tables, entities).
Give this a shot:
DetachedCriteria d = DetachedCriteria.forClass(QueryDetailEntity.class, "qd");
d.setProjection(Projections.projectionList().add(Projections.property("qd.query")));
d.add(Restrictions.or(Restrictions.like("qd.value1", "PROMPT%"), Restrictions.like("qd.value2", "PROMPT%")));
criteria = session.createCriteria(QueryEntity.class, "q");
criteria.add(Subqueries.propertyNotIn("q.primaryId", d));
criteria.list();
The use of the following are property names, not column names:
qd.query
qd.value1
qd.value2
q.primaryId
As a side note, if this is not a dynamically generated query, have you given thought to using HQL instead?