Array contains into postgres jsonb with Spring data JDBC - java

Let's make a journey...
What I want to achieve is
SELECT * FROM people WHERE interest->'interests' ?| ARRAY['sport','cars'];
into my Repository defined as
public interface PeopleRepository extends CrudRepository<People, UUID> {
#Query("SELECT * FROM people where interest->'interests' ??| :array::text[] ")
List<People> findByInterest(#Param("array") String interest);
and finally select interests, using the method in this way
findByInterest("{foo, beer}")
This is what I achieved after a day of tests and
IMHO is really MEH
I think out of there, a nicest solution is possible
(without sql cast and string concatenation in runtime)
Could you help with a more "clean code" solution?
(Sorry, for the long post)
Update
I will elaborate a little bit better my question.
I'm searching for something like
#Query("SELECT * FROM people where interest->'interests' ??| :array::text[] ")
List<People> findByInterest(#Param("array") List<String> interest);
is this possible ?

JSON array elements strores data in the following way
{
"interests" : ["sport", "cars"]
}
So if you directly java list it does not work, so one option is convert java array into a string which can be looked at as json array like ["sport", "cars"] or
use jsonObject used to create json array like below
JSONArray interests = new JSONArray();
a.add(0, "sport");
a.add(0, "car");
findByInterest(interests.toString())
#Query("SELECT * FROM people where interest->'interests' ??| :array::text[] ")
List<People> findByInterest(#Param("array") String interest);

Related

Is there a way to use Regex to execute indexOps on Mongo?

I've some collections on my database that have a 'Dynamic Name',
Let's say that I've some collections that store Animals data from an specific country,
So in my database Animals I would have the following collections:
Australia-Animals
Japan-Animals
Brazil-Animals
And from time to time I need to add new collections to my Database, currently I've an Script that add an Index to the country like:
public class CreateIndexForAnimalSpecies {
#ChangeSet(order = "1", id = "CreateIndexForAnimalSpecies", author = "Foo Bar")
public void createIndexForAnimalSpecies(final MongockTemplate template)
IndexOperations idx = mongockTemplate.indexOps("Australia-Animals");
Index index = new Index().named("specieIndex").on("specie", Direction.ASC).background();
idx.ensureIndex(index);
}
Basically this class does the job of creating the index for the Collection named Australia-Animals but in the future if I add a new collection with a new Country Prefix I would have to copy this whole piece of code and change the Collection's name String.
I tried using *, $, %, as Regex but none of them worked. There's any alternative method along fetching the collections name and add to a list with contains("name")?
I tough of something like:
Set<String> collections = mongockTemplate.getCollectionNames();
// Convert to a List<String>
for(String c: collections){
if(c.contains("-Animals"){
otherList.add(c);
}
}
otherList.forEach(item -> scriptToCreateIndex(item));
It works, but seems quite inefficient having to iterate over the collections to do the job.

Spring JPA repository JPQL query using a collection of objects

let's say I have the next entity or object:
class Person {
BigInteger cardNumber;
String name;
int age;
Address address;
}
Then, I have a List<Person> and I want to find the youngest person based on the cardNumber and name. So, I'd like to write a query similar to this:
#Query("SELECT p FROM Person p WHERE p.cardNumber = :myPerson.cardNumber AND p.name LIKE :myPerson.name ORDER BY p.age ASC")
public List<Person> find(#param("myPerson") List<Person> personList);
My problem is that I'm not so sure how to tackle this, and what approach to use. I've tried passing two collections, List<String> names and List<BigInteger> cardNumbers and the query was something like this:
#Query("SELECT p FROM Person p WHERE p.cardNumber IN (:cardNumbers) AND p.name IN (:names) ORDER BY p.age ASC")
public List<Person> find(#param("cardNumbers") List<BigInteger> cardNumbers, #param("names") List<String> names);
The problem, I need to use the name and the cardNumber as a single value to compare because, they both work as a primary key. I tried to CONCAT(name, cardNumber) and pass to the method a list of strings with the name.concat(cardNumber.toString(()) value but, I found that the JPQL turns the BigInteger in a number like this 1.00 so, to make it work I need to do something like this name.concat(cardNumber.toString(() + ".00"). That way, it works but, I can't hardcode that mapping from BigInteger to String.
Any advice is appreciated. I just want to find a way to pass the collection of the object rather than concatenate those strings in my code and then pass them to the query method.
Thank you.

Hibernate ResultTransformer unable to cast a single column

I have some very complicated SQL (does some aggregation, some counts based on max value etc) so I want to use SQLQuery rather than Query. I created a very simple Pojo:
public class SqlCount {
private String name;
private Double count;
// getters, setters, constructors etc
Then when I run my SQLQuery, I want hibernate to populate a List for me, so I do this:
Query hQuery = sqlQuery.setResultTransformer(Transformers.aliasToBean(SqlCount.class));
Now I had a problem where depending on what the values are for 'count', Hibernate will variably retrieve it as a Long, Double, BigDecimal or BigInteger. So I use the addScalar function:
sqlQuery.addScalar("count", StandardBasicTypes.DOUBLE);
Now my problem. It seems that if you don't use the addScalar function, Hibernate will populate all of your fields with all of your columns in your SQL result (ie it will try to populate both 'name' and 'count'). However if you use the addScalar function, it only maps the columns that you listed, and all other columns seem to be discarded and the fields are left as null. In this instance, it wouldn't be too bad to just list both "name" and "count", but I have some other scenarios where I need a dozen or so fields - do I really have to list them all?? Is there some way in hibernate to say "map all fields automatically, like you used to, but by the way map this field as a Double"?
Is there some way in hibernate to say "map all fields automatically.
No, check the document here, find 16.1.1. Scalar queries section
The most basic SQL query is to get a list of scalars (values).
sess.createSQLQuery("SELECT * FROM CATS").list();
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();
These will return a List of Object arrays (Object[]) with scalar values for each column in the CATS table. Hibernate will use ResultSetMetadata to deduce the actual order and types of the returned scalar values.
To avoid the overhead of using ResultSetMetadata, or simply to be more explicit in what is returned, one can use addScalar():
sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME", Hibernate.STRING)
.addScalar("BIRTHDATE", Hibernate.DATE)
i use this solution, I hope it will work with you.
with this solution you can populate what you select from the SQL, and return it as Map, and cast the values directly.
since hibernate 5.2 the method setResultTransformer() is deprecated but its work fine to me and works perfect.
if you hate to write extra code addScalar() for each column from the SQL, you can implement ResultTransformer interface and do the casting as you wish.
ex:
lets say we have this Query:
/*ORACLE SQL*/
SELECT
SEQ AS "code",
CARD_SERIAL AS "cardSerial",
INV_DATE AS "date",
PK.GET_SUM_INV(SEQ) AS "sumSfterDisc"
FROM INVOICE
ORDER BY "code";
note: i use double cote for case-sensitive column alias, check This
after create hibernate session you can create the Query like this:
/*Java*/
List<Map<String, Object>> list = session.createNativeQuery("SELECT\n" +
" SEQ AS \"code\",\n" +
" CARD_SERIAL AS \"cardSerial\",\n" +
" INV_DATE AS \"date\",\n" +
" PK.GET_SUM_INV(SEQ) AS \"sumSfterDisc\"\n" +
"FROM INVOICE\n" +
"ORDER BY \"code\"")
.setResultTransformer(new Trans())
.list();
now the point with Trans Class:
/*Java*/
public class Trans implements ResultTransformer {
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
#Override
public Object transformTuple(Object[] objects, String[] strings) {
Map<String, Object> map = new LinkedHashMap<>();
for (int i = 0; i < strings.length; i++) {
if (objects[i] == null) {
continue;
}
if (objects[i] instanceof BigDecimal) {
map.put(strings[i], ((BigDecimal) objects[i]).longValue());
} else if (objects[i] instanceof Timestamp) {
map.put(strings[i], dateFormat.format(((Timestamp) objects[i])));
} else {
map.put(strings[i], objects[i]);
}
}
return map;
}
#Override
public List transformList(List list) {
return list;
}
}
here you should override the two method transformTuple and transformList, in transformTuple you have two parameters the Object[] objects its the columns values of the row and String[] strings the names of the columns the hibernate Guaranteed the same order of of the columns as you order it in the query.
now the fun begin, for each row returned from the query the method transformTuple will be invoke, so you can build the row as Map or create new object with fields.

Using group-by type of operation on JSON objects

I have JSON data in the following format.
[{
"id": 16966,
"post": "This is about road!",
"category": "road",
},
.
.
.]
I want to group JSON entries according to their categories. So, I will get all road related entries in one datastructure, (say list). I know that I can put the data into Mongo DB or even a relational database and do querying. Without doing that, is there some easy method to do this?
If you gonna read the entire JSON file, an easy way is to first read all the entries into a List<Data> and then group them in a Map<String, List<Data>>.
class Data {
private int id;
private String post;
private String category;
//getter, equals, hashcode, toString, etc.
}
and then:
public class Test {
public static void main(String args[] ) throws Exception {
Gson gson = new Gson();
List<Data> list = gson.fromJson(new BufferedReader(new FileReader("myJson.json")), new TypeToken<List<Data>>(){}.getType());
Map<String, List<Data>> groupedMap = list.stream().collect(Collectors.groupingBy(Data::getCategory));
groupedMap.forEach((k, v) -> System.out.println(k + " => " + v));
}
}
which outputs:
road => [Data [id=16966, post=This is about road!, category=road], Data [id=16965, post=This is about road!, category=road]]
land => [Data [id=16961, post=This is about land!, category=land]]
I added some entries to the file. I guess you could write your own deserializer too to get rid of the step when you have a temporary list and store directly in the map, but you asked for an easy way :-). Also note that I'm using java-8 and Gson, but you can also achieve this without it (but you'll write more code).
Take a look on JXPath. This library allows running XPath queries on collections of java objects. So, you can map JSON to your java model using one of popular JSON parser (e.g. Jackson or Gson) and then JXPath to run XPath queries on your collection.
For more information refer here: http://commons.apache.org/proper/commons-jxpath/

spring-data #Query mapping result issue

I've created a Repository that extends CrudRepository,
this repository has a method with an #Query notation:
Code:
#Query("select itemType, count(*) as count from Item where User_id = :userId group by itemType")
List<Map<String, Long>> countItemsForUser(#Param("userId") Long userId);
The issue I'm having is that this return a ArrayList of Object(s) and not a List of Map.
I've read somewhere that JPA can't return a Map so that's why I stuff the result in a List>.
I don't know what's the best way to work around this issue or to quickly access the result data.
I've tried casting but that didn't work out either:
for(Object item: items) {
Map<String,Long> castedItem = (HashMap<String,Long>)item;
}
See this example in official documentation of Hibernate.Here
for (Object item:items) {
Object[] tuple = (Object[]) item;
String itemType = (String)tuple[0];
Long count = (Long) tuple[1];
}
Most simple way is to use interface. To let Spring wire query alias
to the interface getter. Example can be found here: https://www.baeldung.com/jpa-queries-custom-result-with-aggregation-functions
also there is #SqlResultSetMapping. See:
JPA- Joining two tables in non-entity class

Categories

Resources