Querying multiple records using composite key in the mybatis - java

I have a table with composite key. I want to query it in bulk in mybatis. Is it possible? if yes, how?
Table structure of table Foo with foo_id_1 and foo_id_2 as composite primary key.
{
foo_id_1 uniqueIdentifier,
foo_id_2 uniqueIdentifier,
foo_name nvarchar
}
I will have a list of foo POJOs and I want to query them to check if they exist or not.
Below is for getting a single object
<select id="getFooList" resultMap="foo">
SELECT
f.foo_id_1,
f.foo_id_2,
f.foo_name
FROM foo f
WHERE f.foo_id_1 = #{foo_id_1} and f.foo_id_2 = #{foo_id_2}
</select>
<resultMap id="foo" type="Foo">
<id property="fooId1" column="foo_id_1"/>
<id property="fooId2" column="foo_id_2"/>
<result property="fooName" column="foo_name"/>
</resultMap>
But I want to fetch in bulk.
Corresponding SQL query would be
Select *
From foo
Where (foo_id_1 = '1a' and foo_id_2 = '1b') or
(foo_id_1 = '2a' and foo_id_2 = '2b') or
(foo_id_1 = '3a' and foo_id_2 = '3b');

If your calling application can combine the keys into a single delimited string, the query can split the values back out into individual search values. Since your use case involves multiple key columns, you will need two different delimiters - one to separate tuples, and one to separate keys within a tuple.
For example, if you can build a parameter like:
DECLARE #SearchIDs VARCHAR(MAX) = '1a,1b;2a,2b;3a,3b'
The following will separate the keys and perform an efficient key lookup:
SELECT *
FROM (
SELECT
MAX(CASE WHEN S2.ordinal = 1 THEN S2.value END) AS search_id_1,
MAX(CASE WHEN S2.ordinal = 2 THEN S2.value END) AS search_id_2
FROM STRING_SPLIT(#SearchIDs, ';', 1) S1
CROSS APPLY STRING_SPLIT(S1.value, ',', 1) S2
GROUP BY S1.ordinal
) P
JOIN foo f
ON f.foo_id_1 = P.search_id_1 and f.foo_id_2 = P.search_id_2
Sample results:
search_id_1
search_id_2
foo_id_1
foo_id_2
foo_name
1a
1b
1a
1b
A
2a
2b
2a
2b
CCC
3a
3b
3a
3b
EEEEE
In you actual code, you should cast the search_id_1 and search_id_2 values to the proper data type.
CAST(MAX(...) AS UNIQUEIDENTIFIER) AS search_id_1,
CAST(MAX(...) AS UNIQUEIDENTIFIER) AS search_id_2
See this db<>fiddle.
Other more formal ways to pass multiple repeating values in a parameter involve constructing XML or JSON, but sometimes a plain string is the simplest as long as you can guarantee that the values cannot themselves contain any delimiters.

Related

Return List element pairs based on given criteria

I've got a class Category with properties and constructors like so:
Double categoryRange;
String categoryName;
Double categoryPercent;
Category(categoryRange, categoryName, categoryPercent)
getters(), setters()...
I've also got a linkedList of Category objects like so:
[C1,C2,C3,C4]
where
C1 = new Category(10,"Music",20);
C2 = new Category(20,"Short Films",40)
C3 = new Category(30,"Films",75)
C4 = new Category(40,"MNM",3)
Now let's assume the inputRange is 15. I'm trying to return those 2 sequential Category objects between whose categoryRange the inputRange falls. In this case, 15 comes between categoryRange 10 and 20. So C1 & C2 will be returned.
Similarly if inputRange = 39, C3 & C4 needs to be returned.
Now this can be done by iterating over every pair of Category objects in the list(i.e., (C1,C2),(C2,C3),(),..) and check if the inputRange falls between the categoryRange of the pairs.
But I was wondering, if there's a better way to do it.
If so, do let me know as to how it is possible.
Thanks and Regards.
Use a NavigableSet, which keeps the entires ordered and you can directly access the elements above and below any value.
Assuming you have a getter for categoryRange:
// Declare the set and how to order its elements
NavigableSet<Category> set = new TreeSet<>(Comparator.comparing(Category::getCategoryRange));
Category C1 = new Category(10D, "Music", 20D);
Category C2 = new Category(20D, "Short Films", 40D);
Category C3 = new Category(30D, "Films", 75D);
Category C4 = new Category(40D, "MNM", 3D);
set.add(C1);
set.add(C2);
set.add(C3);
set.add(C4);
// Create a category object with the targe range value
Category search = new Category(15D, null, null);
// Voila!
Category below = set.floor(search);
Category above = set.ceiling(search);
There is a fancier approach, assuming categoryRange is unique, that could use a NavigableMap (TreeMap as the impl) with categoryRange as the key. Code is similar, and you wouldn't have to create the search object.

Hibernate or SQL Query M-N member of with collections?

Given a class which has an #ElementCollection of Strings, and given an input collection of Strings:
public class FooBar {
#ElementCollection
private Set<String> tags
}
Set<String> queryTags;
How can I query the two following scenarios:
Return all FooBar fb where queryTags has at least one match in fb.tags
Return all FooBar fb where all Strings in queryTag match an element in fb.tags (queryTag is a subcollection of fb.tags)
Note: I'm not asking if the two collections are equal.
Obviously, I can query all and perform the collection comparison in code, or I can do:
for(String tag : queryTags){
createQuery("from FooBar fb where :t member of fb.tags").setString("t", tag)
}
Both ways seem incredibly inefficient, but I don't know if vanilla SQL or HQL support any query support for collections
select fb
from FooBar fb
left join fb.tags t
where t in ( :queryTags )
select fb
from FooBar fb
where fb.id not in (
select id
from FooBar
left join tags t
where t not in ( :queryTags )
)
Where the queryTags is set with:
session.createQuery(queryString).setParameterList( "queryTags ", queryTags ).list();

Hibernate result list cast to get rows

Hello I had problem with iterate Hibernate ResultList
I had followed query that I got from external class:
queryContent = "select distinct c.identity, c.number, c.status, ctr.name, aab.paymentConditions.currency from AgreementStateBean ast join ast.currentAgreement aab join conagr.contract c where c.agreementStateId = ? and con.mainContractor = true ? "
And I must sum whole aab.paymentConditions.currency, check numbers of statutes and names.
I want to do this by iterate list of results:
Query q = session.createQuery(queryContent);
List result = q.list();
Long wholeCurrency, numberOfStatutes;
for(Object res : result){
//wholeCurrency += res.getColumnName?
}
My question is how to cast res Object to have possibility to get concrete column values? I had read about create map inside hibernate query but I don't know it is good practice to modyfied query string by adding
"new map(" prefix and then ")"
before from sql statement
Solution:
After All I decided to use map in my query. I modified my external query by adding hibernate map statement by replacing select by 'select new map(' and from by ') from'.
Additional thing is to add 'as' statement with name of key because without them column keys are integer.
So after all my query looks like follow:
"select new map( distinct c.identity, c.number, c.status as status, ctr.name as name, aab.paymentConditions.currency as currency ) from AgreementStateBean ast join ast.currentAgreement aab join conagr.contract c where c.agreementStateId = ? and con.mainContractor = true ? "
That was the most siutable solution for me, I tried with 'NEW com.example.MyClass' as Kostja suggested but in my case I didn't have control for incoming query so I can not rely on pernament constructor.
new List( select...
Is also interest but it also didn't tell me on with position I have my field that give me information.
If I understand correctly, you want to have a typed representation of your result without it being an entity itself. For this, you can use constructor queries:
"SELECT NEW com.example.MyClass( e.name, e.data) FROM Entity e"
MyClass has to have a matching constructor. Full qualification (com.example) is not mandatory AFAIK.
If you are using this query often, creating a view in the DB may be a good idea. You can map a view to an entity just as if it were a regular table, but please note that you cannot store changes to you data over a mapped view.
EDIT: Turns out, mapping to an unspecified Map is alright with Hibernate:
select new map( mother as mother, offspr as offspr, mate as mate )
As per http://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch11.html#ql-select-clause
you can use
queryContent = "select new list(distinct c.identity, c.number, c.status, ctr.name, aab.paymentConditions.currency) from AgreementStateBean ast join ast.currentAgreement aab join conagr.contract c where c.agreementStateId = ? and con.mainContractor = true ? "
And you get List<List> as result.

CriteriaQuery with One-to-Many and Many-to-One Relationships

I have 3 tables in question: PrblFldr --> PrblFldrAtrbtVal --> PrblTmpltAtrbt. The relationships between these are "one-to-many" and "many-to-one", respectively.
I am using CriteriaBuilder to perform a search on PrblFldr objects. I need to search by the values of each PrblFldrAtrbtVal that is associated with the PrblFldr. The query parameters' keys are the unique PKs that associate each PrblFldrAtrbtVal with a PrblTmpltAtrbt; the value of the parameter is what to search for in the PrblFldrAtrbtVal's value.
Here's my code so far (edited):
#GET
#Path("/folders/search")
public Response searchFolders(#Context UriInfo uriInfo) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<PrblFldr> cq = cb.createQuery(PrblFldr.class);
Root<PrblFldr> folder = cq.from(PrblFldr.class);
Join<PrblFldr, PrblFldrAtrbtVal> attributes = folder.join("prblFldrAtrbtVals");
Join<PrblFldrAtrbtVal, PrblTmpltAtrbt> attributeTemplates = attributes.join("prblTmpltAtrbt");
List<Predicate> predicates = new ArrayList<Predicate>();
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
for (String key: queryParams.keySet()) {
String value = queryParams.getFirst(key).replaceAll("_", "\\\\_");
predicates.add(cb.and(cb.equal(attributeTemplates.<String>get("tmpltAtrbtSeqId"), key),
cb.like(attributes.<String>get("fldrAtrbtVal"), "%" + value + "%", '\\')));
}
cq.distinct(true).select(folder).where(cb.and(predicates.toArray(new Predicate[]{})));
List<PrblFldr> results = em.createQuery(cq).getResultList();
return Response.ok(gson.toJson(results), MediaType.APPLICATION_JSON).build();
}
EDITED: It is currently working if I only pass in one key/value pair to search for. If I pass in more than one PrblFldrAtrbtVal to search for, a blank result set is returned, despite the fact that one or more PrblFldr objects should be matched by the specified PrblFldrAtrbtVal objects.
I think it has something to do with the cb.and() statement in the cq.where() clause. I do want an 'AND', but why is returning back no results?
The query is returning an empty list because the predicates are added with an 'AND'.
i.e., the query being generated is something like;
tmpltAtrbtSeqId = '1' AND fldrAtrbtVal like '%a%'
AND tmpltAtrbtSeqId = '2' AND fldrAtrbtVal like '%b%'
AND tmpltAtrbtSeqId = '3' AND fldrAtrbtVal like '%c%'
when you pass in multiple key/value pairs.
You need to add a 'OR' clause in the for loop.

How do I select the nth element in a Collection by the number in an #IndexColumn?

I have an ItemEntity class, which has a collection of ImageEntity's. I want to be able to fetch a specific ImageEntity, given its index in the collection. For example, for an ItemEntity with 10 ImageEntity's, I want the 4th ImageEntity in as few database hits as possible.
I've read Hibernate In Action and googled, but every piece of documentation I can find is very terse in describing #IndexColumn, with no detailed examples of how to use it.
I can use item.getImages() to fetch a List<ImageEntity>, and then call .get(4) on that.. but that involves loading the whole collection into memory to be able to call get() on it.
What I'd like to do is something like:
int itemId = 111; // id of the item I want to fetch the image for
int wantImageNum = 4; // index of the image I want, in the item's list
imageId = .. somehow get the id of the 4th ImageEntity ..;
ImageEntity img = session.load(ImageEntity.class, imageId);
I have used #IndexColumn to let Hibernate manage the ordering of ImageEntity's in the collection, i.e.:
public class ItemEntity {
...
#ManyToMany(cascade={CascadeType.MERGE,
CascadeType.PERSIST,
CascadeType.REFRESH}, fetch=FetchType.LAZY)
#JoinTable(name = "ItemImages",
joinColumns = {
#JoinColumn(name="item_id", referencedColumnName="id") // id in this class
},
inverseJoinColumns = {
#JoinColumn(name="image_id") // id in ImageEntity
}
)
#org.hibernate.annotations.IndexColumn(name="idx", base=1)
private List images = new ArrayList();
Thus, there is a 'join table' that looks like this:
table ItemImages (
item_id
image_id
idx
)
I could do something very dirty with plain SQL, i.e.
select image_id from ItemImages where item_id = :itemId and idx = :wantImageNum;
That is clearly horrible. I can't pull a similar trick using either HQL or Criteria queries because ItemImages is not a mapped entity, it's a join table being managed by Hibernate.
The index() HQL function is what you're looking for:
select image
from ItemEntity item
join item.images image
where index(image) = 4
and item.id = 111
It has been standardized in JPA 2.0 also as INDEX. From the specification:
4.6.17.2.2 Arithmetic Functions
functions_returning_numerics::=
ABS(simple_arithmetic_expression) |
SQRT(simple_arithmetic_expression) |
MOD(simple_arithmetic_expression, simple_arithmetic_expression) |
SIZE(collection_valued_path_expression) |
INDEX(identification_variable)
(...)
The INDEX function returns an
integer value corresponding to the
position of its argument in an ordered
list. The INDEX function can only be
applied to identification variables
denoting types for which an order
column has been specified.
In the following example,
studentWaitlist is a list of
students for which an order column has
been specified:
SELECT w.name
FROM Course c JOIN c.studentWaitlist w
WHERE c.name = ‘Calculus’
AND INDEX(w) = 0
References
Hibernate Core Reference Guide
14.10. Expressions
JPA 2.0 Specification
Section 4.6.17.2.2 Arithmetic Functions

Categories

Resources