Theres a way to query on hibernate without generate SQLs? - java

I need to query on my database (postgres) like this:
Entity:
class Cat{
int id;
String name;
}
main class:
int [] idCats = {1,2,7,5,8,4,9,10,12,14};
for(int id : idCats){
Cat cat = session.load(Cat,id);
(do something with cat, according your name)
}
But, this approach generates to many sqls. Considering i'll search almost all ids, there's a
way to bring all objects e search on it using criteria. Without implement by myself.

You can use the second level cache feature of hibernate. But if your application is simple and you just want to fetch a cat with its id, then store the result of the query in a hashmap like
Map<Integer, Cat> mapCats = new HashMap<Integer, Cat>();
You can use the for loop to iterate over the list from DB.
Map<Integer, Cat> mapCats = new HashMap<Integer, Cat>();
for(Cat oneCat: listCats) {
mapCats.put(oneCat.id, oneCat);
}
Then retrieve using
mapCats.get(catid);

Related

Fastest way to find out in hibernate diff between database entries and entries stored within app

#Entity
Student {
Long id;
String name;
}
Let's say I have a list of 10 Students List<Student> studentList = new ArrayList<>(); stored in a list somewhere in my app.
And I have a list of 20 entities I have stored in DB previously:
List<Student> studentsDbList = StudentRepo.findAll()
I would like to find out which of these 20 are not in the studentList, and delete them from database in the most efficient way without clearing whole db.
I can't find nothing similar on hibernate which allows me to do this with one method call, but I assume this is a trivial problem which has been solved numerous times, and I simply doesn't comprehend hibernate enough to solve it with some oneliner(
Any suggestions here?
You can filter database entities by which are not in App and get the id of those Student those you want to delete using Stream API
List<Long> ids = studentsDbList.stream()
.filter(e -> !studentList.contains(e))
.map(e -> e.getId())
.collect(Collectors.toList());
then delete by ids from the database by using this on the repository
void deleteByIdIn(List<Long> ids);
And call with those student ids you want to delete
studentRepo.deleteByIdIn(ids);
Note: Override equals in Student class
Another better way is
List<Long> ids = studentList.stream()
.map(e -> e.getId())
.collect(Collectors.toList());
#Query(value = "DELETE FROM Student s WHERE s.id NOT IN (:ids)")
void deleteByIdIn(#Param("ids") List<Long> ids);
findAll() doesn't seem like optimal way and it's a code smell. I would only fetch id's from db. Add this methods to student repository:
#Query("select s.id from Student s")
List<Long> getAllIds();
void deleteByIdIn(List<Long> ids);
Then make a diff on id's list and remove by ids:
ArrayList<Long> studentIdsToRemove = new ArrayList<>(studentIdsFromDB);
studentIdsToRemove.removeAll(studentIds);
studentRepo.deleteByIdIn(studentIdsToRemove);

Sqlite relative complement on combined key

First some background about my Problem:
I am building a crawler and I want to monitor some highscore lists.
The highscore lists are defined by two parameters: a category and a collection (together unique).
After a successful download I create a new stats entry (category, collection, createdAt, ...)
Problem: I want to query the highscore list only once per day. So I need a query that will return category and collection that haven't been downloaded in 24h.
The stats Table should be used for this.
I have a List of all possible categories and of all possible collections. They work like a cross join.
So basically i need the relative complement of the cross join with the entries from the last 24h
My Idea: Cross join categories and collections and 'substract' all Pair(category, collection) of stats entries that has been created during last 24 h
Question 1: Is it possible to define categories and collections inside the query and cross join them or do I have to create a table for them?
Question 2: Is my Idea the correct approach? How would you do this in Sqlite?
Ok i realise that this might sound confusing so I drew an image of what I actually want.
I am interested in C.
Here is my current code in java, maybe it helps to understand the problem:
public List<Pair<String, String>> getCollectionsToDownload() throws SQLException {
long threshold = System.currentTimeMillis() - DAY;
QueryBuilder<TopAppStatistics, Long> query = queryBuilder();
List<TopAppStatistics> collectionsNotToQuery = query.where().ge(TopAppStatistics.CREATED_AT, threshold).query();
List<Pair<String, String>> toDownload = crossJoin();
for (TopAppStatistics stat : collectionsNotToQuery) {
toDownload.remove(new Pair<>(stat.getCategory(), stat.getCollection()));
}
return toDownload;
}
private List<Pair<String, String>> crossJoin() {
String[] categories = PlayUrls.CATEGORIES;
String[] collections = PlayUrls.COLLECTIONS;
List<Pair<String, String>> toDownload = new ArrayList<>();
for (String ca : categories) {
for (String co : collections) {
toDownload.add(new Pair<>(ca, co));
}
}
return toDownload;
}
The easiest solution to your problem is an EXCEPT. Say you have a subquery
that computes A and another one that computes B. These queries
can be very complex. The key is that both should return the same number of columns and comparable data types.
In SQLite you can then do:
<your subquery 1> EXCEPT <your subquery 2>
As simple as that.
For example:
SELECT a, b FROM T where a > 10
EXCEPT
SELECT a,b FROM T where b < 5;
Remember, both subqueries must return the same number of columns.

Group objects in list by multiple fields

I have a simple object like this
public class Person {
private int id;
private int age;
private String hobby;
//getters, setters
}
I want to group a list of Person by attributes
Output should be like this
Person count/Age/Hobby
2/18/Basket
5/20/football
With a chart for more understanding
X axis : hobby repartition
Y axis : count of person distribution
Colors represents age
I managed to group by one attribute using map, but I can't figure how to group by multiples attributes
//group only by age . I want to group by hobby too
personMapGroupped = new LinkedHashMap<String, List<Person>>();
for (Person person : listPerson) {
String key = person.getAge();
if (personMapGroupped.get(key) == null) {
personMapGroupped.put(key, new ArrayList<Person>());
}
personMapGroupped.get(key).add(person);
}
Then I retrieve the groupable object like this
for (Map.Entry<String, List<Person>> entry : personMapGroupped .entrySet()) {
String key = entry.getKey();// group by age
String value = entry.getValue(); // person count
// I want to retrieve the group by hobby here too...
}
Any advice would be appreciated.
Thank you very much
Implement methods for comparing people according to the different fields. For instance, if you want to group by age, add this method to Person:
public static Comparator<Person> getAgeComparator(){
return new Comparator<Person>() {
#Override
public int compare(Person o1, Person o2) {
return o1.age-o2.age;
}
};
}
Then you can simply call: Arrays.sort(people,Person.getAgeComparator()) or use the following code to sort a Collection:
List<Person> people = new ArrayList<>();
people.sort(Person.getAgeComparator());
To sort using more than one Comparator simultaneously, you first define a Comparator for each field (e.g. one for age and one for names). Then you can combine them using a ComparatorChain. You would use the ComparatorChain as follows:
ComparatorChain chain = new ComparatorChain();
chain.addComparator(Person.getNameComparator());
chain.addComparator(Person.getAgeComparator());
You could simply combine the attributes to a key.
for (Person person : listPerson) {
String key = person.getAge() + ";" + person.getHobby();
if (!personMapGrouped.contains(key)) {
personMapGrouped.put(key, new ArrayList<Person>());
}
personMapGrouped.get(key).add(person);
}
The count of entries is easy to determine by using personMapGrouped.get("18;Football").getSize().
I'm not sure about your requirements, but I'd probably use multiple maps (Google Guava's Multimap would make that easier btw) and sets, e.g. something like this:
//I'm using a HashMultimap since order of persons doesn't seem to be relevant and I want to prevent duplicates
Multimap<Integer, Person> personsByAge = HashMultimap.create();
//I'm using the hobby name here for simplicity, it's probably better to use some enum or Hobby object
Multimap<String, Person> personsByHobby = HashMultimap.create();
//fill the maps here by looping over the persons and adding them (no need to create the value sets manually
Since I use value sets Person needs a reasonable implementation of equals() and hashCode() which might make use of the id field. This also will help in querying.
Building subsets would be quite easy:
Set<Person> age18 = personsByAge.get(18);
Set<Person> basketballers = personsByHobby.get( "basketball" );
//making use of Guava again
Set<Person> basketballersAged18 = Sets.intersection( age18, basketballers );
Note that I made use of Google Guava here but you can achieve the same with some additional manual code (e.g. using Map<String, Set<Person>> and manually creating the value sets as well as using the Set.retainAll() method).

Return HashMap in mybatis and use it as ModelAttribute in spring MVC

I want to display list of categories in my Jsp page using spring mvc #modelAttribute.
In my mapper.xml file is
<select id="selectAllCategories" resultMap="BaseResultMap">
select id, name from categories
</select>
In my Mapper.java class I have method
List<Map<String, String>> selectAllCategories();
I want to have a method like this:
Map<Integer, String>`selectAllCategories();
instead of List<Map<>>, is that possible?
You want to get a Map<Integer,String> where the Integer is the id and the String is the name. If there were 200 categories in your table, you would want 200 entries in your map, rather than a list of 200 maps.
MyBatis can't quite do that out of the box, but you can use its facilities to do that. I see two options.
Option 1:
The first isn't quite what you asked for but is worth showing. It gives you a Map<Integer,Category> where Category is a domain object for the categories table that has id, name (and possibly other fields from the categories table). After you've created the Category domain object, this is quite easy to do in MyBatis using the #MapKey annotation:
#Select("SELECT id, name FROM categories")
#MapKey("id")
Map<Integer,Category> getAllCategories();
In your code you would then do:
MyMapper mapper = session.getMapper(MyMapper.class);
Map<Integer,Category> m = mapper.getAllCategories();
That may or may not work for your use case depending on whether whether you can extract the name as a property of the Category object.
Option 2:
To get the Map<Integer,String> you asked for, the easiest way I know is to create a class that implements the MyBatis ResultHandler interface.
Your ResultHandler will use the default hashmap of column-name => column-value that MyBatis creates and create a single master Map. Here's the code:
public class CategoryResultHandler implements ResultHandler {
Map<Integer,String> inMap = new HashMap<Integer,String>();
public Map<Integer, String> getIdNameMap() {
return inMap;
}
#Override
public void handleResult(ResultContext rc) {
#SuppressWarnings("unchecked")
Map<String,Object> m = (Map<String,Object>)rc.getResultObject();
inMap.put((Integer)getFromMap(m, "id"),
(String)getFromMap(m, "name"));
}
// see note at bottom of answer as to why I include this method
private Object getFromMap(Map<String, Object> map, String key) {
if (map.containsKey(key.toLowerCase())) {
return map.get(key.toLowerCase());
} else {
return map.get(key.toUpperCase());
}
}
}
The handleResult method gets called once per row in the category table. You tell MyBatis to use the ResultHandler and then extract your master map like this:
CategoryResultHandler rh = new CategoryResultHandler();
session.select("getAllCategories", rh);
Map<Integer,String> m = rh.getIdNameMap();
One of those two should work for you.
A few final notes:
Why did I include the getFromMap() helper method? Because you can't always control the case of the column name in the hashmap that MyBatis returns. More details here: mybatis- 3.1.1. how to override the resultmap returned from mybatis
I have working examples of these solutions in Koan26 of the mybatis-koans (which I added based on your question): https://github.com/midpeter444/mybatis-koans

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