say I have an array List of type Order Details
private static List<OrderDetails> orderDetails = new ArrayList<OrderDetails>();
and the fields in orderDetails are
private String productCode = null;
private int revenue = 0;
my arrayList contains the values`
A012 69
A012 36
I need to change the output of the list so that if something is added to the list of the same productCode the revenues get added together
so the output of the example above would be
A012 105
how will the method work
This should work assuming you have the appropriate getters for the class. It just creates a stream of OrderDetails objects, filters out null productCodes and creates a map of the revenue sums.
Map<String,Integer> results = orderDetails.stream()
.filter(od->od.getProductCode() != null)
.collect(Collectors.groupingBy(OrderDetails::getProductCode,
Collectors.summingInt(OrderDetails::getRevenue)));
If desired, you can then return the values back to a list by creating a new instance of each OrderDetails class.
orderDetails = results.entrySet().stream()
.map(e->new OrderDetails(e.getKey(), e.getValue()))
.collect(Collectors.toList());
Using a map might be more useful since you can get the revenue for any product code.
For example
System.out.println(results.get("A012"));
The small code snippet by using a Map as below may be helpful:
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Test {
private static List<OrderDetails> orderDetails = new ArrayList<OrderDetails>();
public static void main(String[] args) {
final OrderDetails o1 = new OrderDetails("A12", 69);
final OrderDetails o2 = new OrderDetails("A12", 36);
final OrderDetails o3 = new OrderDetails("A13", 136);
addToList(o1);
addToList(o2);
addToList(o3);
orderDetails.stream().map(orderDetail -> orderDetail.getProductCode() + " " + orderDetail.getRevenue()).forEach(System.out::println);
}
private static synchronized void addToList(OrderDetails orderDet) {
final Map<String, OrderDetails> map = orderDetails.stream().collect(Collectors.toMap(OrderDetails::getProductCode, orderDetail -> orderDetail, (a, b) -> b));
final OrderDetails objectFromMap = map.get(orderDet.getProductCode());
if (objectFromMap != null) {
objectFromMap.setRevenue(objectFromMap.getRevenue() + orderDet.getRevenue());
} else {
map.put(orderDet.getProductCode(), orderDet);
}
orderDetails.clear();
orderDetails.addAll(map.values());
}
}
class OrderDetails {
private String productCode = null;
private int revenue = 0;
public OrderDetails(String productCode, int revenue) {
this.productCode = productCode;
this.revenue = revenue;
}
public String getProductCode() {
return productCode;
}
public int getRevenue() {
return revenue;
}
public void setRevenue(int revenue) {
this.revenue = revenue;
}
}
You can use a Map like this:
Map<String, Integer> orderDetails = new new HashMap<>();
and use this method for adding a new order:
void addOrder(Order orderToAdd) {
Order findOrder = orderDetails.get(orderToAdd.productCode);
if (findOrder != null) {
findOrder.revenue += orderToAdd.revenue ;
} else {
orderDetails.put(orderToAdd.productCode, orderToAdd.revenue );
}
}
Related
I have a map in java which has String as Key and an integer list as value. My query returns the below set
"Day:1, Day:2, Day:3, Month:1, Year:15, Year:20, Year:25"
Meaning, I have keys and different values for each keys in random.. I am expecting an output of a map which has unique key and corresponding list of int in values as seen below:
{
"day": [1,2,3],
"Month": [1],
"year": [15,20,25]
}
Kindly help
public static void main(String[] args){
String input = "Day:1, Day:2, Day:3, Month:1, Year:15, Year:20, Year:25";
List<String> list = Arrays.asList(input.split(","));
JSONObject jsonObject = new JSONObject();
for (String val : list) {
String[] splitedValues = val.split(":");
if(jsonObject.has(splitedValues[0].trim())) {
jsonObject.getJSONArray(splitedValues[0].trim()).put(Integer.valueOf(splitedValues[1]));
}else {
List<Integer> integers = new ArrayList<Integer>();
integers.add(Integer.valueOf(splitedValues[1]));
jsonObject.put(splitedValues[0].trim(), integers);
}
}
System.out.println(jsonObject);
}
output
{"Month":[1],"Year":[15,20,25],"Day":[1,2,3]}
Please find below code. I have created an inner class to map type and value and then collected it by grouping the type.
import java.util.Map;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
public class StringToMap {
public static void main(String[] args) {
String queryOutput = "Day:1, Day:2, Day:3, Month:1, Year:15, Year:20, Year:25";
System.out.println(groupByTypeAndAddValuesToList(queryOutput));
}
private static Map<String, List<Integer>> groupByTypeAndAddValuesToList(String queryOutput) {
final String[] split = queryOutput.split(",");
return Arrays.stream(split).map(s -> {
final String trimmed = s.trim();
final String[] splitByColon = trimmed.split(":");
return new TypeValues(splitByColon[0], Integer.parseInt(splitByColon[1]));
}).collect(groupingBy(TypeValues::getType, Collectors.mapping(TypeValues::getValue, toList())));
}
private static class TypeValues {
private final String type;
private final Integer value;
public TypeValues(String type, Integer value) {
this.type = type;
this.value = value;
}
public String getType() {
return type;
}
public Integer getValue() {
return value;
}
}
}
I have List of stories. Using unique property(id) I want to collect keyword and targeting as list of values. Can I do this with MultiMap? Or is there other library for this?
[{
id = 1,
title = Onboarding,
keyword = new joinee,
targeting = finance
}, {
id = 1,
title = Onboarding,
keyword = training,
targeting = HR
}]
The Desired output must like this :
{
id = 1,
title = Onboarding,
keyword = [new joinee,training], //may be keywords - plural
targeting = [HR,finance]
}
Sample my tried Code as follows:
package prac;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JavaPrac {
public static void main(String[] args) {
Multimap<Integer, Map> multiMap = ArrayListMultimap.create();
List<Map> stories=new ArrayList();
Map story1=new HashMap();
story1.put("id", 1);
story1.put("title", "Onboarding");
story1.put("keyword","new joinee");
story1.put("targeting","finance");
Map story2=new HashMap();
story2.put("id", 1);
story2.put("title", "Onboarding");
story2.put("keyword","training");
story2.put("targeting","HR");
stories.add(story1);
stories.add(story2);
System.out.println(stories);
stories.forEach((story) -> {
multiMap.put((Integer) story.get("id"), story);
});
}
}
A multimap can only store multiple values per key but what you want is to combine those multiple values so that you get one element that has the same id and title as well as a collection of keywords and targeting information. Thus it would probably be best to either have something like MultiStory or already have Story contain those collections.
I'd suggest using proper objects instead of just maps but with maps and Java 8 lambdas you could use compute() etc. to build maps that contain collections and combine maps that don't.
Here's an example of how you'd do it with maps. Note that this is very bad style and an example using proper pojos will follow:
Disclaimer: example based on the OP's code, not recommended (read text above)
//Problem 1: we don't know the type of the values, i.e. we could put anything for "id" etc.
Map<String, Object> story1=new HashMap<>();
story1.put("id", 1);
story1.put("title", "Onboarding");
story1.put("keyword","new joinee");
story1.put("targeting","finance");
Map<String, Object> story2=new HashMap<>();
story2.put("id", 1);
story2.put("title", "Onboarding");
story2.put("keyword","training");
story2.put("targeting","HR");
List<Map<String, Object>> stories=new ArrayList<>();
stories.add(story1);
stories.add(story2);
Map<Integer, Map<String, Object>> combined = new HashMap<>();
stories.forEach((story) -> {
//Problem 2: because we don't know the type of the values we need a lot of nasty casts
Map<String, Object> combinedStory = combined.computeIfAbsent( (Integer)story.get( "id" ), k -> new HashMap<String, Object>() );
combinedStory.put("id", story.get( "id" ) );
combinedStory.put("title", story.get( "title" ) );
//Problem 3: the combined map would look a lot like your "story" maps but would contain different types
((List<String>)combinedStory.computeIfAbsent( "keyword", v -> new List<String>() )).add( (String)story.get("keyword") );
((List<String>)combinedStory.computeIfAbsent( "targeting", v -> new List<String>() )).add( (String)story.get("targeting") );
});
Using POJOs
Here's a greatly simplified example of how you'd do it with proper Java objects (POJOs). Note that those are meant to resemble your code as much as possible and there are a lot of other issues but addressing those would be way too much here and better designed code would be a lot larger and probably harder to understand - after all it's just meant to show you a difference.
First let's define our classes (for simplicity I made the fields public, you'd normally not do that):
class Story {
public final int id;
public String title;
public String keyword;
public String targeting;
public Story(int storyId) {
id = storyId ;
}
}
class MultiStory {
public final int id;
public String title;
public Set<String> keywords = new HashSet<>();
public Set<String> targetingInfo = new HashSet<>();
public MultiStory( int storyId ) {
id = storyId ;
}
}
Then let's reiterate the code above:
Story story1=new Story( 1 );
story1.title = "Onboarding";
story1.keyword = "new joinee";
story1.targeting = "finance";
Story story2=new Story( 1 );
story2.title = "Onboarding";
story2.keyword = "training";
story2.targeting = "HR";
List<Story> stories=new ArrayList<>();
stories.add(story1);
stories.add(story2);
Map<Integer, MultiStory> combined = new HashMap<>();
stories.forEach((story) -> {
MultiStory multiStory = combined.computeIfAbsent( story.id, v -> new MultiStory( story.id ) );
multiStory.title = story.title;
multiStory.keywords.add( story.keyword );
multiStory.targetingInfo.add( story.targeting );
});
As you can see, there are no casts needed and it's clear what fields are available (though not necessarily filled) which makes it easier to reason about the code and spot errors (the compiler can help a lot here which it couldn't to in the example that uses maps).
Here is a solution using classes to represent the story and tags:
public static void main(String[] args) {
TagsCollector app = new TagsCollector();
app.go();
}
private void go() {
List<Story> stories = createStories();
System.out.println(stories);
Map<Long, Tags> tagsById = collectTags(stories);
tagsById.forEach((aLong, tags) -> System.out.println(tags));
}
private List<Story> createStories() {
return Arrays.asList(
new Story(1, "Onboarding", "new joinee", "finance"),
new Story(1, "Onboarding", "training", "HR")
);
}
private Map<Long, Tags> collectTags(List<Story> stories) {
Map<Long, Tags> tagsById = new HashMap<>();
stories.forEach(s -> {
Tags tags = tagsById.computeIfAbsent(s.id, v -> new Tags(s));
tags.getKeywords().add(s.getKeyword());
tags.getTargetings().add(s.getTargeting());
});
return tagsById;
}
Class used to represent the Story:
public class Story {
private final long id;
private final String title;
private final String keyword;
private final String targeting;
public Story(long id, String title, String keyword, String targeting) {
this.id = id;
this.title = title;
this.keyword = keyword;
this.targeting = targeting;
}
public long getId() {
return id;
}
public String getTitle() {
return title;
}
public String getKeyword() {
return keyword;
}
public String getTargeting() {
return targeting;
}
#Override
public String toString() {
return String.format("Story %s, title=%s, keyword=%s, targeting=%s", id, title, keyword, targeting);
}
}
Class used to represent the Tags:
public class Tags {
private final long id;
private final String title;
private final List<String> keywords = new ArrayList<>();
private final List<String> targetings = new ArrayList<>();
Tags(Story story) {
this.id = story.id;
this.title = story.title;
}
public List<String> getKeywords() {
return keywords;
}
public List<String> getTargetings() {
return targetings;
}
#Override
public String toString() {
return String.format("Tags for id %s, title:%s: keywords=%s, targetings=%s", id, title, keywords, targetings);
}
}
Output
[Story 1, title=Onboarding, keyword=new joinee, targeting=finance, Story 1, title=Onboarding, keyword=training, targeting=HR]
Tags for id 1, title:Onboarding: keywords=[new joinee, training], targetings=[finance, HR]
Yes, you can do that with a Multimap. First I would define a pojo for Story in order to make things clearer:
public class Story {
private int id;
private String title;
private String keyword;
private String targeting;
//getters setters
}
Second you need to define a key with hashcode and equals.
public static class StoryKey {
private final int id;
private final String title;
public StoryKey(int id, String title) {
this.id = id;
this.title = title;
}
//getters
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StoryKey storyKey = (StoryKey) o;
if (id != storyKey.id) return false;
return title != null ? title.equals(storyKey.title) : storyKey.title == null;
}
#Override
public int hashCode() {
int result = id;
result = 31 * result + (title != null ? title.hashCode() : 0);
return result;
}
The code will look like:
ArrayListMultimap<StoryKey, Story> multiMap = ArrayListMultimap.create();
List<Story> stories = new ArrayList();
Story story1 = new Story();
story1.setId(1);
story1.setTitle("Onboarding");
story1.setKeyword("training");
story1.setTargeting("HR");
Story story2 = new Story();
story2.setId(1);
story2.setTitle("Onboarding");
story2.setKeyword("new joinee,");
story2.setTargeting("finance");
stories.add(story1);
stories.add(story2);
System.out.println(stories);
stories.
forEach((story) -> {
multiMap.put(new StoryKey(story.getId(), story.getTitle()), story);
});
multiMap.keys().forEach(key ->
System.out.println(
"id =" + key.getId() +
" title =" + key.getTitle()+
"keyword =" + multiMap.get(key).stream().map(story->story.getKeyword()).collect(Collectors.toList()).toString()+
"targeting ="+ multiMap.get(key).stream().map(story->story.getTargeting()).collect(Collectors.toList()).toString())
);
We are trying to access the array of object that placed inside a map.
Can any one guide us to get length of the array as well as fetching each element from the list. Sample map object given bellow.
{
storeId = 1,
ShipToStreetLine1 = test 123,
ShipToCity = Seattle,
OrderDetails = [{
distributor_name = SS,
product_name = HORN 80897 AM GUN 300BO 125 HP 50/10
}]
}
We need to get the size of orderDetails array and if data present, then I want to fetch product_name.
You can try this:
Create a POJO which is type of what you are getting in orderDetails
Like
public class OrderDetailElement{
private String distributor_name;
private String product_name;
public String getDistributor_name() {
return distributor_name;
}
public void setDistributor_name(String distributor_name) {
this.distributor_name = distributor_name;
}
public String getProduct_name() {
return product_name;
}
public void setProduct_name(String product_name) {
this.product_name = product_name;
}
}
in your logic class you can do is
ArrayList<OrderDetailElement> orderDetails = yourMap.get("OrderDetails");
List<String> products = new ArrayList<String>();
if (orderDetails.size() > 0) {
for (OrderDetailElement orderDetailElement : orderDetails) {
products.add(orderDetailElement.getProduct_name());
}
}
I like convert below code to java stream,
HashMap<String, List<Data>> heMap = new HashMap<String, List<Data>>();
for (Data heData : obj) {
String id = heData.getData().getId() + heData.getPlanData().getCode()
+ heData.getPlanData().getId();
if (!heMap.containsKey(id)) {
CitizenHElist = new ArrayList<Data>();
CitizenHElist.add(heData);
heMap.put(id, CitizenHElist);
} else {
heMap.get(id).add(heData);
}
}
I tried the below code using stream, but i am not succeed on this.
heMap=obj.stream().collect(Collectors.toMap(t->getKey(t), obj.stream().collect(Collectors.toList())));
private String getKey(Data heData){
String id = heData.getData().getId() + heData.getPlanData().getCode()
+ heData.getPlanData().getId();
return id;
}
This is the job for groupingBy collector:
import static java.util.stream.Collectors.groupingBy;
Map<String, List<Data>> heMap = obj.stream().collect(groupingBy(d -> getKey(d)));
Note that this will use some unspecified implementations of Map and List. Currently, it happens to be HashMap and ArrayList, but that might change in the future.
Grouping on the bases of a field -
import java.util.*;
import java.util.stream.*;
public class Main
{
public static void main (String[]args)
{
System.out.println ("Hello World");
List < Data > dataList = getDataList();
System.out.println (dataList);
Map < String, List < Data >> dataMap =
dataList.stream ().collect (Collectors.groupingBy (d->d.code));
System.out.println (dataMap);
}
static List < Data > getDataList(){
List < Data > dataList = new ArrayList <> ();
dataList.add (new Data (1, "Prince", "102"));
dataList.add (new Data (2, "Rahul", "102"));
dataList.add (new Data (3, "Sunny", "103"));
dataList.add (new Data (4, "Mitul", "104"));
dataList.add (new Data (5, "Amit", "105"));
dataList.add (new Data (6, "Ashish", "105"));
return dataList;
}
}
class Data
{
int id;
String name;
String code;
public Data (int id, String name, String code)
{
this.id = id;
this.name = name;
this.code = code;
}
public String toString ()
{
return String.format ("id:%s,name:%s,code:%s", id, name, code);
}
}
not sure your data structure but you want to do something like below, which is working.
import java.util.*;
import java.util.stream.Collectors;
class Data {
String stud_id;
String stud_name;
String stud_location;
public Data(String string, String string2, String string3) {
this.stud_id=string;
this.stud_location=string2;
this.stud_name=string3;
}
public Object getData() {
return this.stud_id;
}
}
class Temp3
{
public static void main(String args[])
{
Map<String, List<Data>> heMap=new HashMap<String, List<Data>>();
Data data1=new Data("1","11","111");
Data data2=new Data("2","22","222");
List<Data> obj=new ArrayList<Data>();
obj.add(data1);
obj.add(data2);
for (Data heData : obj)
{
String id = "2";
if (!heMap.containsKey(id))
{
ArrayList<Data> CitizenHElist = new ArrayList<Data>();
CitizenHElist.add(heData);
heMap.put(id, CitizenHElist);
}
else
{
heMap.get(id).add(heData);
}
}
heMap=obj.stream().collect(Collectors.groupingBy(w -> w.stud_location));
System.out.println(heMap);
}
}
I want to take Nested object values in Hibernate Projection List. I having Pojo 'Charge' and 'Tariff' class with OneToMany and ManyToOne relations.
My sample code is as following:
Charge
private String id;
private Tariff tariff;
private String name;
#OneToMany(cascade= {CascadeType.ALL},fetch=FetchType.EAGER,mappedBy="charge")
public Tariff getTariff() {
return tariff;
}
public void setTariff(Tariff tariff) {
this.tariff = tariff;
}
Tariff
private String id;
private String amount;
private Charge charge;
#ManyToOne(cascade={CascadeType.PERSIST},fetch=FetchType.EAGER)
#JoinColumn(name="charge_id")
public Charge getCharge() {
return charge;
}
public void setCharge(Charge charge) {
this.charge = charge;
}
I want to take amount value from tariff by charge model.
I write sql criteria that works ie.
SELECT tariff.amount,charge.name FROM charge,tariff WHERE
charge.name LIKE 's%';
and i tried with following criteria.
Criteria cr = getSession().createCriteria(Charge.class,"charge")
.createAlias("charge.tariff","tariff")
.setProjection(Projections.projectionList()
.add(Projections.property("chargeName"),"chargeName")
.add(Projections.property("id"),"id")
.add(Projections.property("tariff.amount"),"amount"))
.add(Restrictions.like("chargeName", name+"%"))
.setResultTransformer(Transformers.aliasToBean(Charge.class));
return cr.list();
I just check with restclient it returns null Value. How to write for Criteria for this sql Query ?
I've experienced this kind of requirement. I tried to get nested objects as nested objects using Transformers.aliasToBean, which will not work. By default, Transformers.aliasToBean don't have the capability to select nested object as nested object.
You can take a look at my question
Using Projecions to fetch a particular column from child table
To get Nested object as nested object, you need a Custom Transformer which is capable of doing that.
Here's a Custom Transformer written by samiandoni
https://github.com/samiandoni/AliasToBeanNestedResultTransformer
From the provided Readme in that link
class Person {
private Long id;
private String name;
private Car car;
// getters and setters
}
class Car {
private Long id;
private String color;
// getters and setters
}
List<Person> getPeople() {
ProjectionList projections = Projections.projectionList()
.add(Projections.id().as("id"))
.add(Projections.property("name").as("name"))
.add(Projections.property("c.id").as("car.id"))
.add(Projections.property("c.color").as("car.color"));
Criteria criteria = getCurrentSession().createCriteria(Person.class)
.createAlias("car", "c")
.setProjection(projections)
.setResultTransformer(new AliasToBeanNestedResultTransformer(Person.class));
return (List<Person>) criteria.list();
}
// each car of Person will be populated
The above transformer is capable of Fetching first level Nested object as Nested object and it doesn't support further deep nested objects. So after some digging I've found another Custom transformer which is capable of Fetching deep Nested objects as Nested objects
Note:
Author: Miguel Resendiz
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.property.PropertyAccessor;
import org.hibernate.property.PropertyAccessorFactory;
import org.hibernate.property.Setter;
import org.hibernate.transform.AliasToBeanResultTransformer;
import org.hibernate.transform.AliasedTupleSubsetResultTransformer;
import org.hibernate.transform.ResultTransformer;
/**
* Help to transform alises with nested alises
*
* #author Miguel Resendiz
*
*/
public class AliasToBeanNestedResultTransformer extends
AliasedTupleSubsetResultTransformer {
private static final long serialVersionUID = -8047276133980128266L;
private static final int TUPE_INDEX = 0;
private static final int ALISES_INDEX = 1;
private static final int FIELDNAME_INDEX = 2;
private static final PropertyAccessor accessor = PropertyAccessorFactory
.getPropertyAccessor("property");
private final Class<?> resultClass;
private Object[] entityTuples;
private String[] entityAliases;
private Map<String, Class<?>> fieldToClass = new HashMap<String, Class<?>>();
private Map<String, List<?>> subEntities = new HashMap<String, List<?>>();
private List<String> nestedAliases = new ArrayList<String>();
private Map<String, Class<?>> listFields = new HashMap<String, Class<?>>();
public boolean isTransformedValueATupleElement(String[] aliases,
int tupleLength) {
return false;
}
public AliasToBeanNestedResultTransformer(Class<?> resultClass) {
this.resultClass = resultClass;
}
public Object transformTuple(Object[] tuple, String[] aliases) {
handleSubEntities(tuple, aliases);
cleanParams(tuple, aliases);
ResultTransformer rootTransformer = new AliasToBeanResultTransformer(
resultClass);
Object root = rootTransformer.transformTuple(entityTuples,
entityAliases);
loadSubEntities(root);
cleanMaps();
return root;
}
private void handleSubEntities(Object[] tuple, String[] aliases)
throws HibernateException {
String fieldName = "";
String aliasName = "";
try {
for (int i = 0; i < aliases.length; i++) {
String alias = aliases[i];
if (alias.contains(".")) {
String[] sp = alias.split("\\.");
StringBuilder aliasBuilder = new StringBuilder();
for (int j = 0; j < sp.length; j++) {
if (j == 0) {
fieldName = sp[j];
} else {
aliasBuilder.append(sp[j]);
aliasBuilder.append(".");
}
}
aliasName = aliasBuilder.substring(0,
aliasBuilder.length() - 1);
nestedAliases.add(alias);
manageEntities(fieldName, aliasName, tuple[i]);
}
}
} catch (NoSuchFieldException e) {
throw new HibernateException("Could not instantiate resultclass: "
+ resultClass.getName() + " for field name: " + fieldName
+ " and alias name:" + aliasName);
}
}
private Class<?> findClass(String fieldName) throws NoSuchFieldException,
SecurityException {
if (fieldToClass.containsKey(fieldName)) {
return fieldToClass.get(fieldName);
} else {
Class<?> subclass = resultClass.getDeclaredField(fieldName)
.getType();
if (subclass.equals(List.class) || subclass.equals(Set.class)) {
if (subclass.equals(List.class)) {
listFields.put(fieldName, LinkedList.class);
} else {
listFields.put(fieldName, HashSet.class);
}
Field field = resultClass.getDeclaredField(fieldName);
ParameterizedType genericType = (ParameterizedType) field
.getGenericType();
subclass = (Class<?>) genericType.getActualTypeArguments()[0];
}
fieldToClass.put(fieldName, subclass);
return subclass;
}
}
#SuppressWarnings("unchecked")
private void manageEntities(String fieldName, String aliasName,
Object tupleValue) throws NoSuchFieldException, SecurityException {
Class<?> subclass = findClass(fieldName);
if (!subEntities.containsKey(fieldName)) {
List<Object> list = new ArrayList<Object>();
list.add(new ArrayList<Object>());
list.add(new ArrayList<String>());
list.add(FIELDNAME_INDEX, subclass);
subEntities.put(fieldName, list);
}
((List<Object>) subEntities.get(fieldName).get(TUPE_INDEX))
.add(tupleValue);
((List<String>) subEntities.get(fieldName).get(ALISES_INDEX))
.add(aliasName);
}
private void cleanParams(Object[] tuple, String[] aliases) {
entityTuples = new Object[aliases.length - nestedAliases.size()];
entityAliases = new String[aliases.length - nestedAliases.size()];
for (int j = 0, i = 0; j < aliases.length; j++) {
if (!nestedAliases.contains(aliases[j])) {
entityTuples[i] = tuple[j];
entityAliases[i] = aliases[j];
++i;
}
}
}
#SuppressWarnings({ "unchecked", "rawtypes" })
private void loadSubEntities(Object root) throws HibernateException {
try {
for (String fieldName : subEntities.keySet()) {
Class<?> subclass = (Class<?>) subEntities.get(fieldName).get(
FIELDNAME_INDEX);
ResultTransformer subclassTransformer = new AliasToBeanNestedResultTransformer(
subclass);
Object subObject = subclassTransformer.transformTuple(
((List<Object>) subEntities.get(fieldName).get(0))
.toArray(),
((List<Object>) subEntities.get(fieldName).get(1))
.toArray(new String[0]));
Setter setter = accessor.getSetter(resultClass, fieldName);
if (listFields.containsKey(fieldName)) {
Class<?> collectionClass = listFields.get(fieldName);
Collection subObjectList = (Collection) collectionClass
.newInstance();
subObjectList.add(subObject);
setter.set(root, subObjectList, null);
} else {
setter.set(root, subObject, null);
}
}
} catch (Exception e) {
throw new HibernateException(e);
}
}
private void cleanMaps() {
fieldToClass = new HashMap<String, Class<?>>();
subEntities = new HashMap<String, List<?>>();
nestedAliases = new ArrayList<String>();
listFields = new HashMap<String, Class<?>>();
}
}
Just replace samiandoni's Transformer with the above transformer. It's capable of fetching further deep Nested Objects as respective Objects.
My solution is very basic. It's not as clean as a proper result transformer but it's useful when you just need to do a quick projection for a few properties.
Instead of .add(Projections.property("tariff.amount"),"amount"))
type .add(Projections.property("tariff.amount"),"tariffAmount"))
Then, just add a setter on your root object "setTariffAmount".
public void setTariffAmount(String tariffAmount) {
this.tariff = (this.tariff==null) ? new Tariff() : tariff;
tariff.setAmount(tariffAmount);
}
The drawback is that it "dirties" your object with extra methods.
The AliasToBeanNestedResultTransformer does not handle Multilevel Nested DTO's. Meaning, you won't be able to do company.employee.location each in its own DTO.
Here is a Transformer I wrote that handles Multilevel Nested DTOs. You may used it by calling:
criteria.setResultTransformer(
AliasToBeanNestedMultiLevelResultTransformer(mappingBean));
Hope it helps.
public class AliasToBeanNestedMultiLevelResultTransformer extends AliasedTupleSubsetResultTransformer {
private static final long serialVersionUID = -8047276133980128266L;
public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
return false;
}
private boolean initialized;
private Class<?> resultClass;
private Map<String,Class<?>> clazzMap = new HashMap<>();
private Map<String,Setter> settersMap = new HashMap<>();
public AliasToBeanNestedMultiLevelResultTransformer(Class<?> resultClass) {
this.resultClass = resultClass;
}
public Object transformTuple(Object[] tuples, String[] aliases) {
Map<String,Object> nestedObjectsMap = new HashMap<>();
Object result;
try {
result = resultClass.newInstance();
if (!initialized){
initialized = true;
initialize(aliases);
}
for (int a=0;a<aliases.length;a++){
String alias = aliases[a];
Object tuple = tuples[a];
Object baseObject = result;
int index = alias.lastIndexOf(".");
if(index>0){
String basePath = alias.substring(0, index);
baseObject = nestedObjectsMap.get(basePath);
if (baseObject == null){
baseObject = clazzMap.get(basePath).newInstance();
nestedObjectsMap.put(basePath, baseObject);
}
}
settersMap.get(alias).set(baseObject, tuple,null);
}
for (Entry<String,Object> entry:nestedObjectsMap.entrySet()){
Setter setter = settersMap.get(entry.getKey());
if (entry.getKey().contains(".")){
int index = entry.getKey().lastIndexOf(".");
String basePath = entry.getKey().substring(0, index);
Object obj = nestedObjectsMap.get(basePath);
setter.set(obj, entry.getValue(), null);
}
else{
setter.set(result, entry.getValue(), null);
}
}
}catch ( InstantiationException | IllegalAccessException e) {
throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
}
return result;
}
private void initialize(String[] aliases) {
PropertyAccessor propertyAccessor = new ChainedPropertyAccessor(
new PropertyAccessor[] {
PropertyAccessorFactory.getPropertyAccessor( resultClass, null ),
PropertyAccessorFactory.getPropertyAccessor( "field" )
}
);
for (int a=0;a<aliases.length;a++){
String alias = aliases[a];
Class<?> baseClass = resultClass;
if (alias.contains(".")){
String[] split = alias.split("\\.");
StringBuffer res = new StringBuffer();
for (int i=0;i<split.length;i++){
if (res.length()>0) res.append(".");
String item = split[i];
res.append(item);
String resString = res.toString();
if (i==split.length-1){
clazzMap.put(resString,baseClass);
settersMap.put(resString, propertyAccessor.getSetter(baseClass, item));
break;
}
Class<?> clazz = clazzMap.get(resString);
if (clazz==null){
clazz = propertyAccessor.getGetter(baseClass,item).getReturnType();
settersMap.put(resString, propertyAccessor.getSetter(baseClass, item));
clazzMap.put(resString,clazz);
}
baseClass = clazz;
}
}
else{
clazzMap.put(alias, resultClass);
settersMap.put(alias, propertyAccessor.getSetter(resultClass, alias));
}
}
}
}