JPA dynamically EntityGraph and Subgraphs - java

I created an util method:
public <T> Optional<T> fetch(Class<T> clazz, Object id, String... relations) {
EntityGraph<T> graph = entityManager.createEntityGraph(clazz);
Stream.of(relations).forEach(graph::addSubgraph);
return Optional.ofNullable(entityManager.find(clazz, id, Collections.singletonMap("javax.persistence.loadgraph", graph)));
}
So if for example if User has lazy orders and wallets, I can do this:
Optional<User> user = fetch(User.class, 1, "orders", "wallets");
But I don't know how to take orders's or wallets lazy collections. It would be greate if I would call method like this:
Optional<User> user = fetch(User.class, 1, "orders", "orders.products", "wallet");
How can I extend the method to achieve this?

I decided to use the next method:
public <T> Optional<T> fetch(Class<T> clazz, Object id, String... relations) {
EntityGraph<T> graph = entityManager.createEntityGraph(clazz);
Stream.of(relations).forEach(path -> {
String[] splitted = path.split("\\.");
Subgraph<T> root = graph.addSubgraph(splitted[0]);
for (int i = 1; i < splitted.length; i++)
root = root.addSubgraph(splitted[i]);
});
return Optional.ofNullable(entityManager.find(clazz, id, Collections.singletonMap("javax.persistence.loadgraph", graph)));
}
It has only one defect. The next two will work:
Optional<User> user = fetch(User.class, 1, "orders", "orders.products", "wallet");
Optional<User> user = fetch(User.class, 1, "orders.products", "wallet");
The next one will not:
Optional<User> user = fetch(User.class, 1, "orders.products", "orders", "wallet");
That's because orders overrides orders.products. I think it's enough, because logically if you want to load orders.products, you have to load orders anyway.

Related

Spliting string, flatMapping it and creating objects using Java streams?

I have 2 classes going like this:
public class Seat extends Moviefiable {
private int number;
private Hall hall;
public Seat(int id, boolean active, int number, Hall hall) {
super(id, active);
this.number = number;
this.hall = hall;
}
public Seat() {this(-1, false, -1, null);}
public static Seat getById(int idSeat) throws SQLException {
Seat seat = SeatDAO.getById(idSeat);
return seat;
}
}
public class Ticket extends Moviefiable {
private Projection projection;
private Seat seat;
private LocalDateTime purchasingDate;
private User user;
public Ticket(int id, boolean active, Projection projection, Seat seat, LocalDateTime purchasingDate, User user) {
super(id, active);
this.projection = projection;
this.seat = seat;
this.purchasingDate = purchasingDate;
this.user = user;
}
public Ticket() {this(-1, false, null, null, null, null);}
Now, I need to create two or more Tickets, depending how many tickets loggedInUser chooses. From my JSP page, I'll get something like this:
String uri = request.getQueryString(); //uri looks like this: seats=2&seats=3
I want to create two Ticket objects for two Seat objects. In uri string, chars 2 and 3 are primary keys for seats.
The idea is to use Java streams to perform splitting and creating objects. This is I have so far.
ArrayList<Ticket> newTicketsForSeats = Stream.of(uri.split("&"))
.map(s -> s.split("seats=")[1])
//.flatMap(Arrays::stream)
.mapToInt(Integer::valueOf)
.mapToObj(s -> {
try {
return new Ticket(-1, true, projection, SeatDAO.getById(s),
LocalDateTime.now(), loggedInUser);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
})
.collect(Collectors.toCollection(ArrayList::new));
When I made a test, like this to see the output I'm getting:
Stream.of(uri.split("&"))
.map(s -> s.split("seats=")[1])
.mapToInt(Integer::valueOf)
.forEach(System.out::println); //2 3
Which is okey. But when I run code above, I got:
java.lang.ArrayIndexOutOfBoundsException: 1
at servlets.ConfirmPurchaseServlet.lambda$0(ConfirmPurchaseServlet.java:62)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
projection is also object, which I obtained, this is just reference, it is elsewhere in servlet. loggedInUser I got from session. I'm newbie regarding Java streams, any additional explanation would be excellent.
I get that map() requires IntUnaryOperator lambda expression, something like this:
s -> s * 10
I don't know how to "map" from int (primary key for Seat) to Seat object.
Cheers.
Your main problem is that you are trying to assign the stream output to an ArrayList. Collectors.toList() returns a List that cannot be expected to be a particular implementation.
So either do this:
List<SomeClass> list = ...collect(Collectors.toList());
or this
ArrayList<SomeClass> list = ... collect(Collectors.toCollection(ArrayList::new));
The following should work but it may need some tweaking based on your other classes.
ArrayList<Ticket> newTicketsForSeats = Stream.of(uri.split("&"))
.map(s -> s.split("seats="))
.flatMap(Arrays::stream)
.mapToInt(Integer::valueOf)
.mapToObj(s -> new Ticket(-1, true, projection, SeatDAO.getById(s),
LocalDateTime.now(), loggedInUser))
.collect(Collectors.toCollection(ArrayList::new));
Either of these would probably work:
List<Ticket> newTicketsForSeats = Stream.of(uri.split("&"))
.map(s -> s.replace("seats=", ""))
.mapToInt(Integer::valueOf)
.mapToObj(s -> new Ticket(-1, true, projection, SeatDAO.getById(s), LocalDateTime.now(), loggedInUser))
.collect(Collectors.toList());
or
List<Ticket> newTicketsForSeats = Stream.of(uri.split("&"))
.map(s -> s.replace("seats=", ""))
.map(s -> new Ticket(-1, true, projection, SeatDAO.getById(Integer.valueOf(s)), LocalDateTime.now(), loggedInUser))
.collect(Collectors.toList());
BTW: you don't seem to have a Ticket constructor which matches the signature:
Ticket(int, boolean, Projection, Seat, LocalDateTime, boolean)

Outer Joining two List to generate a new List in Java

I'm trying to find a way to join two List of objects with two unique attributes to produce a new List of Object.
Users Class
public class Users {
String id;
String name;
String gender;
}
Adding some data here
List<Users> userList=new ArrayList<Users>();
userList.add(new Users("1","AA","Male"));
userList.add(new Users("2","BB","Male"));
userList.add(new Users("3","CC","Female"));
Academics Class
public class Academics {
String id;
String name;
String grade;
String professional;
}
Adding some data here
List<Academics> academicsList=new ArrayList<Academics>();
academicsList.add(new Academics("1","AA","A","Doctor"));
academicsList.add(new Academics("2","BB","B","Carpenter"));
academicsList.add(new Academics("3","CC","C","Engineer"));
My Profile
Public class Profile {
String id;
String name;
String gender;
String grade;
String professional;
}
Here I need to calculate the List by Outer joining the UserList and academicsList with the 2 common attributes of id and name
I do need to do this as a bulk operation instead of going with any For/While loops one by one.
Is there is any way to use Stream to achieve this?
Update 1:
The Joining here would be like Outer-Joining where some id would not be present in academics but it would be present in Users.
In such case We need to show empty values for the grade/professional in Profile List(s)
Thanks in advance,Jay
It would make sense to convert one of the input Lists to a Map, in order to quickly correlate between entries of the first and second Lists.
Map<String,Users> userByID = userList.stream().collect(Collectors.toMap(Users::getID,Function.identity));
Now you can Stream over the elements of the second List:
List<Profile> profiles =
academicsList.stream()
.map(a -> {
Profile p = null;
Users u = userByID.get(a.getID());
if (u != null) {
p = new Profile();
// now set all the Profile fields based on the properties
// of the Users instance (u) and the Academics instance (a)
}
return p;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
Given your additional requirement, you should create a Map for the second List and Stream the first List:
Map<String,Academics> academicsByID = userList.stream().collect(Collectors.toMap(Academics::getID,Function.identity));
List<Profile> profiles =
userList.stream()
.map(u -> {
Profile p = new Profile ();
Academics a = academicsByID.get(u.getID());
// now set all the Profile fields based on the properties
// of the Users instance (u) and the Academics instance (a)
// (if available)
return p;
})
.collect(Collectors.toList());

How do I conditionally go through an array of objects and add them to a new array depending on a field value?

I have a generic Object, let's say it's user:
#Data
class User {
public String userName;
public int age;
}
I create a bunch of them:
User user = new User("Freddy", 22);
User user = new User("Freddy", 18);
User user = new User("Hans", 21);
User user = new User("Michael", 5);
User user = new User("Freddy", 29);
User user = new User("Hans", 33);
User user = new User("Michael", 20);
User user = new User("Freddy", 15);
These are stored in let's say an ArrayList(assume they are all there)
List<User> userList = New ArrayList<>();
Now I wish to make three lists:
List<User> freddyList;
List<User> hansList;
List<User> michaelList;
I want to loop through userList and as you can predict, add the user to freddyList if userName equals "Freddy", and so forth.
How do I solve this with forEach?
usersList.foreach(
if (usersList.getUsername().equals("Freddy")){
freddyList.add(usersList.Object) // This doesn't work
}
)
Lambda expressions should be short and clean. With forEach you will end up using multi expression lambda that would look ugly.
My suggestion is to use use Java stream groupingBy:
Map< String, List<User> > map = userList.stream().collect( Collectors.groupingBy( u -> u.getUserName() ) );
List<User> freddyList = map.get("Freddy");
List<User> hansList = map.get("Hans");
List<User> michaelList = map.get("Michael");
Adding to the comment, your code should look something like this:
usersList.forEach( user ->
if (user.getUsername().equals("Freddy")){
freddyList.add(user);
}
)
You need to consume the user object from forEach (look here) and add it to the respective list as shown below:
List<User> userList = new ArrayList<>();
//Add your users here
//Declare your freddyList, hansList, etc..
userList.forEach(user -> {
if (user.getUsername().equals("Freddy")){
freddyList.add(user);
} else if(user.getUsername().equals("Hans")) {
hansList.add(user);
} else if(user.getUsername().equals("Michael")) {
michaelList.add(user);
}
});
But, I suggest you can directly get the list of users by grouping by userName using Java streams (Collectors.groupingBy) as shown below in a single line:
Map<String, List<User>> groupByUser =
userList.stream().collect(Collectors.groupingBy(User::getUserName));
You seems to misunderstand some things :
usersList.getUsername() does not exists, you cannot get a username from a list
usersList.Object does not belong to anything too
You'd better use filter() :
List<User> freddyList = usersList.stream().filter(u -> u.getUserName().equals("Freddy"))
.collect(Collectors.toList());
List<User> hansList = usersList.stream().filter(u -> u.getUserName().equals("Hans"))
.collect(Collectors.toList());
List<User> michaelList = usersList.stream().filter(u -> u.getUserName().equals("Michael"))
.collect(Collectors.toList());
These will iterate over the list which contains all the guys, keep the ones that have the good username, and collect them into the good list
You can do a for each user if the value of the current user is equal to either one of these do that bit of code.
Example:
for(User user : userList){
if (user.getUsername().equals("Freddy"))
{
freddyList.add(user);
}
else if(user.getUsername().equals("Hans"))
{
hansList.add(user);
}
else
{
michaelList.add(user);
}
You can change it to a switch if you like, use another method, but that is the best logic you need to get it working.
Also make sure your object class User has the getUsername() method defined so you can use it in your code.

Return a predicate as a lambda in Java

In this online tutorial they use a lambda to filter a list of users:
List<User> olderUsers = users.stream().filter(u -> u.age > 30).collect(Collectors.toList());
Now I want to make a function which accepts a lambda (the u -> u.age > 30 part) so that I can put any criterion I want inside a this lambda. But I'm a bit stuck on how to implement this. I have made an empty interface Filter and I made the following methods:
public List<User> filterUsers(Filter f, List<User users>){
return users.stream().filter(f).collect(Collectors.toList());
}
public Filter olderThan(int age) {
return u -> u.getAge() > age;
}
But this gives a number of errors.
(I made a getter of the agefield in user).
Is there a way to adjust the interface Filter or the other methods to make this work?
You don't need your Filter type. Just make the filterUsers method take a Predicate<User> and pass it in:
public List<User> filterUsers(Predicate<User> p, List<User> users) {
return users.stream().filter(p).collect(Collectors.toList());
}
This would allow you to write something like:
List<User> filtered = filterUsers(u -> u.age > 10, users);
If you wanted to make your "olderThan" thing for convenience, you can write:
public Predicate<User> olderThan(int age) {
return u -> u.getAge() > age;
}
And then you could write:
List<User> filtered = filterUser(olderThan(15), users);
You should simply use Predicate instead of Filter.

Difference bettween transformTuple and For loop on query Result - Hibernate

I have an HQL as select p,c from Person p,ContactCard c where c.fk_pid=p.id I executed this query as HQL using this code:
List<Person> personsWithContactCard = new ArrayList<Person>();
List<object[]> quryResult = new ArrayList<object[]>();
String qry = "select p,c from Person p,ContactCard c where c.fk_pid=p.id";
quryResult = session.createQuery(qry).list();
for(object[] obj : quryResult )
{
Person person = new Person();
person = (Person)obj[0];
person.setContactCard = (ContactCard )obj[1];
personsWithContactCard.add(person);
person=null;
}
By taking query result in list of object array and looping on query result I fill persons list.
But after reading about ResultTransformer Interface I come to know that with this interface I can transform queryResult in to desired list so I changed my code To :
String qry = "select p,c from Person p,ContactCard c where c.fk_pid=p.id";
personsWithContactCard = session.createQuery(qry).setResultTransformer(new ResultTransformer() {
#Override
public Object transformTuple(Object[] tuple, String[] aliases)
{
Person person = new Person();
person = (Person)obj[0];
person.setContactCard = (ContactCard )obj[1];
return person ;
}
#Override
public List transformList(List collection)
{
return collection;
}
}).list();
This code gives me persons list with for looping.
So my question is : What is the difference between transformTuple and For loop?
Does the both are same in performance and processing sense?
Which will be more good as per performance?
And what is the use of transformList()?
Update :
After understanding use of ResultTransformer as explained in answer given by #bellabax I did one small change in code as follows:
personsWithContactCard = session.createQuery(qry).setResultTransformer(new ResultTransformer() {
#Override
public Object transformTuple(Object[] tuple, String[] aliases)
{
Person person = new Person();
person = (Person)obj[0];
person.setContactCard = (ContactCard )obj[1];
return person ;
}
#Override
public List transformList(List collection)
{
return null;
}
}).list();
I changed transformList() method to return null if I execute this code I am getting null personsWithContactCard list. Why transformList() method is need to return collection when I am not using it? And when I supposed to use transformList() and transformTuple() means how I can decide which to use?
There aren't differences in terms of result usually, but using a ResultTransformer:
is the standard Hibernate method to process tuple and future (break) changes about how HQL is processed and tuple returned will be masked by a ResultTransformer without code changes
give you the possibilities to decorate or delegate (for example)
so the choice is the ResultTransformer.
About ResultTransformer.transformList():
Here we have an opportunity to perform transformation on the query
result as a whole
instead in transformTuple you can manipulate only one row of returned set.
EDIT:
As quoted above the javadoc of ResultTransformer.transformList() is pretty clear: this function allow to modify the whole list to remove duplicate, apply type conversion and so on and the result of ResultTransformer.transformList() is forwarded to Query.list() method so, return null from transformList whill return null from list().
This is how Query and ResultTransformer are tied.

Categories

Resources