In my project I have two entities: Race and RaceDriver, which has-a Race in it:
class RaceDriver {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "race", nullable = false)
private Race race;
...
#Column(name = "starting_nr")
private Integer startingNr;
...
#Column(name = "disqualified", nullable = false)
private boolean disqualified;
}
Now, what I wanted is to get the list of the startingNrs of the disqualified RaceDrivers in a Race, which looked like this:
public List<Integer> findDisqualifiedDriversStartingNumbers(Integer raceId) {
ProjectionList projection = Projections.projectionList()
.add(Projections.property("startingNr").as("startingNr"));
return getSession()
.createCriteria(RaceDriver.class)
.setProjection(projection)
.add(Restrictions.eq("race.id", raceId))
.add(Restrictions.eq("disqualified", true))
.list();
}
The thing is that now I need the same, but for the few Races. How can I achieve this without making a separate DAO calls? Because I've heard that it is better to make as much as possible in a single database call.
My idea is to simply get the list of the drivers which are disqualified in the given races, and then parse it in the Java code, which I think will require few loops, and make some map of disqualified RaceDriver's starting numbers, where the key would be Race.id.
The DAO attempt looks like that:
public List<RaceDriver> findDisqualifiedDriversInRaces(List<Integer> raceIds) {
return getSession()
.createCriteria(RaceDriver.class)
.add(Restrictions.in("race.id", raceIds))
.add(Restrictions.eq("disqualified", true))
.list();
}
The problem is that I will get that big objects, instead of some map or list of the only data I need (startingNr and race.id).
So the question is - can I do it somehow using only Hibernate?
Personally I would use the solution in plain java because it'll be more clear for any developer supporting your code in the future.
Answering the question "can it be done via Hibernate?": yes, it can be, ResultTransformer is the right way, especially if map-of-lists conversion is required more than once in your program. There is no standard transformer for your needs but you can write your own one:
public class MapOfListsResultTransformer<K, V> extends BasicTransformerAdapter {
public List transformList(List collection) {
final Map<K, List<V>> map = new HashMap<>();
for (Object object : collection) {
final Object[] objects = (Object[]) object;
final K key = (K) objects[0];
final V value = (V) objects[1];
if (!map.containsKey(key)) {
final List<V> list = new ArrayList<V>();
list.add(value);
map.put(key, list);
} else {
map.get(key).add(value);
}
}
return Arrays.asList(map);
}
}
And its usage is the following:
public Map<Integer, List<Integer>> findDisqualifiedDriversInRaces(List<Integer> raceIds) {
ProjectionList projection = Projections.projectionList()
.add(Projections.property("race.id").as("race.id"))
.add(Projections.property("startingNr").as("startingNr"));
return (Map<Integer, List<Integer>>) getSession()
.createCriteria(RaceDriver.class)
.setProjection(projection)
.add(Restrictions.in("race.id", raceIds))
.add(Restrictions.eq("disqualified", true))
.setResultTransformer(new MapOfListsResultTransformer<Integer, Integer>())
.uniqueResult();
}
You'll want to create a Query with hibernate that has two WHERE conditions.
#PersistenceContext protected EntityManager em;
List<Integer> raceIds = Arrays.asList(1, 2, 3);
Query query = em.createQuery("FROM RaceDriver r WHERE r.raceId IN (:raceIds) AND r.disqualified = true");
query.setParameter("raceIds", raceIds);
List<RaceDriver> drivers = query.getResultList();
This will return a list of disqualified drivers within the raceIds that you've provided.
Update
After getting List<RaceDriver> you can for loop on drivers and make List of HashMaps with raceId as a key, and a List of startingNr (of disqualified drivers) as a value.
HashMap<Integer,List<Integer>> startingNrHashMap=new...;
for(RaceDriver driver:drivers)
{
List<Integer> strNr = new ArrayList<Integer>();
if(startingNrHashMap.containsKey(driver.race.raceid))
{
//if race id is already present in hash map then
strNr = startingNrHashMap.get(driver.race.raceid);
strNr.add(driver.startingNr);
startingNrHashMap.put(driver.race.raceid,strNr);
}
else
{
// if race id is NOT present in hash map
strNr.add(driver.startingNr);
startingNrHashMap.put(driver.race.raceid,strNr);
}
strNr =null;
}
This is how I did it:
DAO method:
public List<RaceDriver> findDisqualifiedDriversInRaces(List<Integer> raceIds) {
if (!raceIds.isEmpty()) { // it will crash if the list will have no elements!
return getSession()
.createCriteria(RaceDriver.class)
.add(Restrictions.in("race.id", raceIds))
.add(Restrictions.eq("disqualified", true))
.list();
}
return Collections.emptyList();
}
Then to extract from it what I wanted, I created an auxiliary method:
// key = race.id, value = list of disqualified raceDrivers' starting numbers
private HashMap<Integer, List<Integer>> extractStartingNumbersToMap(List<RaceDriver> disqualifiedDrivers) {
HashMap<Integer, List<Integer>> disqualifiedDriversMap = new HashMap<>();
for (RaceDriver raceDriver : disqualifiedDrivers) {
Integer raceId = raceDriver.getRace().getId();
if (!disqualifiedDriversMap.containsKey(raceId)) {
disqualifiedDriversMap.put(raceId, new ArrayList<>(Arrays.asList(raceDriver.getStartingNr())));
} else {
disqualifiedDriversMap.get(raceId).add(raceDriver.getStartingNr());
}
}
return disqualifiedDriversMap;
}
I did it before Mark T has answered, as you can see it's very similar. However I'm posting it, as it could be helpful to someone.
I asked the same kind of question, Its about NHibernate and .NET MVC but you can get idea of DAO and ResultTransformer
Question
Related
I have a requirement where I want to read multiple project pom files and display data in below format
{
"java" : {"1.7" : ["project1"],"1.8": ["project2"]},
"junit" : {"4.12" : ["project1"]},
"hsqldb" : {"1.8" : ["project3"],"1.1": ["project6"]}
}
My coding is getting input on project , ver and technlogy and displaying, but however I couldnt second value inside the internal
private void addTechnologyData(String projName,String techName,String ver)
{
String keyFormat=techName;
if (technologyMap.containsKey(keyFormat)) {
Map preValue=technologyMap.get(keyFormat);
if(!preValue.containsValue(projName)) {
Map<String,String> temp = new HashMap();
temp=preValue;
temp.put(ver,projName);
technologyMap.put(keyFormat, temp);
}
} else {
Map<String,String> projectVersiomap = new HashMap();
projectVersiomap.put(ver,projName);
technologyMap.put(keyFormat, projectVersiomap);
}
}
Please help me understand why I couldnt add 2nd key value pair to Internal map?
Is there a better way than what Im doing?
If my understanding is correct, you are expecting more than one project per version (since you have an array), so your Map is preventing this, you can only have one value per key. You can use a List like Map<String,Map<String, List<String>>> but I would suggest to use some POJO to keep the code cleaner.
Create a Technology class that will hold a list of Project for each version in a Map. This would look like :
public class Technology{
private String name;
private Map<String, List<Project>> projects;
public Technology(String name){
this.name = name;
this.projects = new HashMap<>();
}
public void addProjectToVersion(String version, Project project){
List<Project> l = this.projects.get(version);
if(l == null){ //doesn't exist
l = new ArrayList<>();
this.projects.put(version, l);
}
l.add(project);
}
}
Here, the logic is in the POJO. You just need to use a Collection to hold your instance, a Map if you like, or a List (implement equals and hashCode to recover easily the instance). And you can use it like :
private Map<String, Technology> techs = new HashMap<>();
....
public void addProject(String tech, String version, String projectName){
Technology t = techs.get(tech);
if(t == null){ //doesn't exist
t = new Technology(tech);
techs.put(tech, t);
}
t.addProjectToVersion(version, new Project(projectName));
}
public void insertData(){
addProject("java", "1.7", "project1");
addProject("java", "1.7", "project2");
addProject("java", "1.8", "project1");
addProject("junit", "4.12", "project1");
System.out.println(techs);
}
This will input correctly :
{junit={4.12=[project1]}, java={1.7=[project1, project2], 1.8=[project1]}}
Note that I reused your logic, based on the requirements it could be better to have a List<Project> holding each Technology with the version. But this is based on the context.
I have a List collection where each Metric contains several properties such as: metricName, namespace, fleet, type, component, firstSeenTime, lastSeenTime, etc. There are duplicates in this list such that all properties are same except for firstSeenTime and lastSeenTime. I am looking for an elegant way to filter this list and only return the metrics with the most recent lastSeenTime when there are such duplicates.
Something better than this:
private List<Metric> processResults(List<Metric metrics) {
List<Metric> results = new ArrayList<>();
for (Metric incomingMetric: metrics) {
// We need to implement "contains" below so that only properties
// other than the two dates are checked.
if (results.contains(incomingMetric) {
int index = results.indexOf(incomingMetric);
Metric existing = results.get(index);
if (incomingMetric.getLastSeen().after(existing.getLastSeen())) {
results.set(index, metricName);
} else {
// do nothing, metric in results is already the latest
}
} else {
// add incomingMetric to results for the first time
results.add(incomingMetric);
}
}
return results;
}
The results.contains check is done by iterating over all the Metrics in results and checking if each object matches the properties except for the two dates.
What could be a better approach that this for both elegance and performance?
In java the most elegant way to compare things is the Comparator interface. You should remove the duplicates using something like:
public List<Metric> removeDuplicates(List<Metric> metrics) {
List<Metric> copy = new ArrayList<>(metrics);
//first sort the metrics list from most recent to older
Collections.sort(copy, new SortComparator());
Set<Metric> set = new TreeSet<Metric>(new Comparator<Metric>() {
#Override
public int compare(Metric o1, Metric o2) {
int result = 0;
// compare the two metrics given your rules
return result;
}
});
for(Metric metric : copy) {
set.add(metric);
}
List<Metric> result = Arrays.asList(set.toArray());
return result;
}
class SortComparator implements Comparator<Metric> {
#Override
public int compare(Metric o1, Metric o2) {
int result = 0;
if(o2.getLastSeenTime() != null && o1.getLastSeenTime() != null) {
result = o2.getLastSeenTime().compareTo(o1.getLastSeenTime());
}
return result;
}
}
The strong of this approach is that you could write a family of comparators and provide a Factory to choose at runtime the best way to compare your metrics and remove or not instances as duplicates among the runtime conditions:
public void removeDuplicates(List<Metric> metrics, Comparator<Metric> comparator) {
List<Metric> copy = new ArrayList<>(metrics);
Collections.sort(copy, new SortComparator());
Set<Metric> set = new TreeSet<Metric>(comparator);
for(Metric metric : copy) {
set.add(metric);
}
List<Object> result = Arrays.asList(set.toArray());
return result;
}
I’m not sure how you are generating List<Metric>. But if you can maintain a Map<String, Metric> instead of that list you may can try the below approach.
So the key of this map will be a combination of all these values you need to compare. (except the date attributes.)
Key: “{metricName}${type}$.....”
For this you can maintain another attribute in Metric object with getter. When you call the getter it will return the key.
Then check the key is exist or not before you put into the map. If it’s exist, get the stored Metric in map for that key and do the date comparison to find the latest Metric object. If it’s the latest replace the map's stored object with new object.
PS : Do the execution time comparison for both cases. So you will find the best approach.
Thanks for the answers. I went with the map approach since it does not incur additional sorts and copies.
#VisibleForTesting
Set<Metric> removeDuplicates(List<Metric> metrics) {
Map<RawMetric, Metric> metricsMap = new HashMap<>();
for (Metric metric : metrics) {
RawMetric rawMetric = RawMetric.builder()
.metricName(metric.getName())
.metricType(metricName.getMetricType())
... // and more
.build();
// pick the latest updated metric (based on lastSeen date)
BiFunction<RawMetric, Metric, Metric> biFunction =
(k, v) -> Metric.builder()
.name(k.getMetricName())
.metricType(k.getMetricType())
... // and more
.lastSeen(v.getLastSeen().after(
metricName.getLastSeen()) ? v.getLastSeen() :
metricName.getLastSeen())
.firstSeen(v.getFirstSeen())
.build();
metricsMap.putIfAbsent(rawMetric, metric);
metricsMap.computeIfPresent(rawMetric, biFunction);
}
return ImmutableSet.copyOf(metricsMap.values());
}
#Value
#Builder
static class RawMetricName {
private String metricName;
private String metricType;
private String ad;
private String project;
private String fleet;
private String host;
private int granularity;
}
I know this kind of question has been asked thousands of times already, but I think I have a different problem. So, I have a class A, which has a collection of objects of class B and a getter for this collection:
public class A {
private String Id;
private ArrayList<B> bees;
public ArrayList<B> getBees(){
ArrayList<Criterion> criterions = new ArrayList<>();
criterions.add(Restrictions.eq("classAId", this.Id));
return (ArrayList<B>)DatabaseHelper.getObjectByCriteria(criterions, B.class);
}
}
And DatabaseHelper.getObjectByCriteria code:
public static List getObjectByCriteria(ArrayList<Criterion> cr, Class classType){
List result = new ArrayList();
Session session = MyHibernateUtil.getSessionFactory().openSession();
Criteria criteria = session.createCriteria(classType);
if (cr != null){
for (Criterion criterion : cr){
criteria.add(criterion);
}
}
result = criteria.list();
if (session.isOpen()){
session.close();
}
return result;
}
When I run this query (select * from table_b where id = 123) in MySQLWorkbench I get two different rows with different values.
However, the "result" list returned by DatabaseHelper.getObjectByCriteria contains two indentical objects with identical ids and values.
I don't use any kind of hibernate annotations
I have show_sql property set to "true", and I checked the real query being run by hibernate and its ok
I am having the use case where I construct my result by joining two tables ITEM and ITEM_DESCRIPTION. From there I am taking several columns which I then would like to conveniently convert into a list of objects. In my case these objects are actually DTO objects but of course they could be business objects as well.
This is the way I am doing it now:
public Map<Long, List<StoreItemDTO>> getItems(Long storeId) {
LOGGER.debug("getItems");
// Get all item_ids for the store
SelectHavingStep<Record1<Long>> where = this.ctx
.select(STORE_ITEM.ID)
.from(STORE_ITEM)
.where(STORE_ITEM.STORE_ID.eq(storeId))
// GROUP BY store_item.id
.groupBy(STORE_ITEM.ID);
// Get all store_item_details according to the fetched item_ids
TableLike<?> storeItemDetails = this.ctx
.select(
STORE_ITEM_DETAILS.ID,
STORE_ITEM_DETAILS.STORE_ITEM_ID,
STORE_ITEM_DETAILS.NAME,
STORE_ITEM_DETAILS.DESCRIPTION,
STORE_ITEM_DETAILS.STORE_LANGUAGE_ID
)
.from(STORE_ITEM_DETAILS)
.where(STORE_ITEM_DETAILS.STORE_ITEM_ID.in(where))
.asTable("storeItemDetails");
// Join the result and use
Field<Long> itemIdField = STORE_ITEM.ID.as("item_id");
Result<?> fetch = this.ctx
.select(
STORE_ITEM.ID.as("item_id"),
itemIdField,
storeItemDetails.field(STORE_ITEM_DETAILS.ID),
storeItemDetails.field(STORE_ITEM_DETAILS.NAME),
storeItemDetails.field(STORE_ITEM_DETAILS.DESCRIPTION),
storeItemDetails.field(STORE_ITEM_DETAILS.STORE_LANGUAGE_ID)
)
.from(STORE_ITEM)
.join(storeItemDetails)
.on(storeItemDetails.field(STORE_ITEM_DETAILS.STORE_ITEM_ID).eq(STORE_ITEM.ID))
.fetch();
Map<Long, ?> groups = fetch.intoGroups(STORE_ITEM.ID);
return null;
}
As you can see, the result should be a list of items where each item has an item-details in different languages:
StoreItemDTO
- Long id
// Maps language-id to item details
- Map<Long, StoreItemDetails> itemDetails
StoreItemDetails
- Long id
- String name
- String description
I couldn't find a version of intoGroups() that would return a useful type. I could imagine to have something like Map<Long, List<Record>> but I can't manage to do so.
However, there is a intoGroups(RecordMapper<? super R, K> keyMapper) which could be what I am looking for. If a mapper would also allow me to actually convert the resulting records into a custom object like MyCustomPojo then I could retrieve and convert the data quite conveniently. I don't know if this is somehow possible. Something like:
public static class MyCustomPojo {
public Long itemId;
// etc.
}
// ..
Map<Long, List<MyCustomPojo>> result = fetch.intoGroups(STORE_ITEM.ID, new RecordMapper<Record, List<MyCustomPojo>>() {
#Override
public List<MyCustomPojo> map(List<Record> record) {
// 'record' is grouped by STORE_ITEM.ID
// Now map each 'record' into every item of each group ..
return resultList;
}
});
But unfortunately the compiler only allows
fetch.intoGroups(new RecordMapper<Record, Result<?>>() {
#Override
public Result<?> map(Record record) {
return null;
}
});
After some fiddling around with the compiler it turns out that it can be done.
I had to "cheat" a little by declaring my resulting map as final outside of the anonymous and I am actually not "using" the keyMapper parameter as I am just returning null.
This is what I came up with:
public Map<Long, StoreItemDTO> getItems(Long storeId) {
// Get all item_ids for the store
SelectHavingStep<Record1<Long>> where = this.ctx
.select(STORE_ITEM.ID)
.from(STORE_ITEM)
.where(STORE_ITEM.STORE_ID.eq(storeId))
.groupBy(STORE_ITEM.ID);
// Get all store_item_details according to the fetched item_ids
TableLike<?> storeItemDetails = this.ctx
.select(
STORE_ITEM_DETAILS.ID,
STORE_ITEM_DETAILS.STORE_ITEM_ID,
STORE_ITEM_DETAILS.NAME,
STORE_ITEM_DETAILS.DESCRIPTION,
STORE_ITEM_DETAILS.STORE_LANGUAGE_ID
)
.from(STORE_ITEM_DETAILS)
.where(STORE_ITEM_DETAILS.STORE_ITEM_ID.in(where))
.asTable("storeItemDetails");
// Join the result and use
final Field<Long> itemIdField = STORE_ITEM.ID.as("item_id");
Result<?> fetch = fetch = this.ctx
.select(
itemIdField,
storeItemDetails.field(STORE_ITEM_DETAILS.ID),
storeItemDetails.field(STORE_ITEM_DETAILS.NAME),
storeItemDetails.field(STORE_ITEM_DETAILS.DESCRIPTION),
storeItemDetails.field(STORE_ITEM_DETAILS.STORE_LANGUAGE_ID)
)
.from(STORE_ITEM)
.join(storeItemDetails)
.on(storeItemDetails.field(STORE_ITEM_DETAILS.STORE_ITEM_ID).eq(STORE_ITEM.ID))
.fetch();
final Map<Long, StoreItemDTO> itemIdToItemMap = new HashMap<>();
fetch.intoGroups(
record -> {
Long itemDetailsId = record.getValue(STORE_ITEM_DETAILS.ID);
// ... sake of compactness
StoreItemDetailsDTO storeItemDetailsDto = new StoreItemDetailsDTO();
storeItemDetailsDto.setId(itemDetailsId);
// ... sake of compactness
Long itemId = record.getValue(itemIdField);
StoreItemDTO storeItemDto = new StoreItemDTO();
storeItemDto.setId(itemId);
storeItemDto.getItemDetailsTranslations().put(languageId, storeItemDetailsDto);
StoreItemDTO itemDetailsList = itemIdToItemMap.get(itemId);
if(itemDetailsList == null) {
itemDetailsList = new StoreItemDTO();
itemIdToItemMap.put(itemId, itemDetailsList);
}
itemDetailsList.getItemDetailsTranslations().put(languageId, storeItemDetailsDto);
return null;
});
return itemIdToItemMap;
}
Since I am not sure if this is the most elegant solution one could have I'm still open for suggestions and willing to accept any answer that can elegantly shorten this code - if that is possible at this point. :)
I am performing a scan on a DynamoDB table and I need to then add respective attributes from the returned items to a list of type User (User has a single constructor User(String uuid)). The code currently successfully scans the DB and returns a List of the scan results. However my iteration seems to return null for some reason.
AmazonDynamoDBClient client = dynamoClient.getDynamoClient();
DynamoDBMapper mapper = new DynamoDBMapper(client);
try {
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
Map<String, Condition> scanFilter = new HashMap<String, Condition>();
Condition scanCondition =
new Condition().withComparisonOperator(ComparisonOperator.NOT_NULL);
scanFilter.put("uuid", scanCondition);
scanExpression.setScanFilter(scanFilter);
List scanResults = mapper.scan(UserAccounts.class, scanExpression);
for (Iterator it = scanResults.iterator(); it.hasNext();) {
//User user = (User) it.next();
allUserSummary.add(new User(scanResults.get(1).toString()));
}
} catch (Exception e) {
// TODO
}
I suggest you start using the modern and compact list iteration by means of The For-Each Loop, which helps to avoid many common errors when using the old iteration style:
[...]
The iterator is just clutter. Furthermore, it is an opportunity for
error. The iterator variable occurs three times in each loop: that is
two chances to get it wrong. The for-each construct gets rid of the
clutter and the opportunity for error. Here is how the example looks
with the for-each construct:
void cancelAll(Collection<TimerTask> c) {
for (TimerTask t : c)
t.cancel();
}
Applying this to your use case yields the following approximately:
List<UserAccounts> scanResults = mapper.scan(UserAccounts.class, scanExpression);
for (UserAccounts userAccounts : scanResults) {
allUserSummary.add(new User(userAccounts.toString()));
}
In case this doesn't work already, it could hint towards the actual error as well, insofar your code assumes the toString() of class UserAccounts to return the uuid, which may or may not be the case. The usual approach is to have a getKey() or getUuidAttribute() method and respective annotations #DynamoDBHashKey or #DynamoDBAttribute, as shown in the example for Class DynamoDBMapper, e.g.:
#DynamoDBTable(tableName = "UserAccounts")
public class UserAccounts{
private String key; // or uuid right away
#DynamoDBHashKey
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
// ...
}
This would obviously yield the following for your example:
List<UserAccounts> scanResults = mapper.scan(UserAccounts.class, scanExpression);
for (UserAccounts userAccounts : scanResults) {
allUserSummary.add(new User(userAccounts.getKey()));
}