I am setting up criteria for database calls and am having trouble getting understanding how to set up my code. I have put my criteria in and now need to know how I make sure I populate the variables right. This is what I have.
Public class Key extends abstractDAO<key>{
Public List<Key> getKeyValues(){
Criteria c = createCriteria();
c.add(Restrictions.lt("id", 3)).addOrder(Order.asc("id")).list();
return c.list();
Now the table has 2 rows. One is current and the second is a new request row. The database has 3 columns. Column 1 is the I'd, column two is the key, and 3 is a timestamp. I need to populate all the variables in order. I am not sure how to go about this in my key.java file
Key.java
#column(name="id")
private int actualID;
#column(name="key")
private Boolean actualKey;
#column (name="actualTime")
private Date actualTime;
Then repeats with requested, requestKey, and requestTime. Then I have public get and sets for each. Have not added any parameters to any method yet either. I am not sure how to set this file up so the list actually sets the variables when the DAO request the rows of the database.
try the following:
public List<Key> getKeyValues()
{
return createCriteria()
.add( Restrictions.lt( "actualID", 3 ) )
.addOrder( Order.asc( "actualID" ) )
.list();
}
Provided that the rest of the mapping and the implementation of createCriteria() are correct, it should return a list of Key's objects whose ids are less than 3, ordered by id.
Related
How do I create a query to my room database from example the SUM of a database column then convert it to a String to then populate text fields in a different activity? I have 5 activities in my app where I wish to customise the data that is displayed but I wish to pull that data from my Room Database.
Many Thanks
If you want a single value then you simply use an #Query annotated method that returns the the single value. e.g.
#Query("SELECT sum(the_column) FROM the_table")
String getTheSum();
could be an integer type or decimal type, which could be more appropriate
no need to convert to a string just retrieve as a String
However, you may need to format the output e.g. if you wanted a specific precision (round(sum(the_column),2) for 2dp). Some formats could be easier to apply outside of SQLite.
If you wanted many (0-n) you could return a List e.g.
#Query("SELECT sum(the_column) FROM the_table GROUP BY the_column_or_columns_to_group_by")
List<long> getTheSums();
If you want to return multiple values for row then you need a POJO where there are member variables that match the column names that are output. e.g.
#Query("SELECT sum(the_column) AS theSum, min(the_column) AS theMin, max(the_column) AS theMax FROM the_table GROUP BY the_column_or_columns_to_group_by")
List<ThePojo> getSumMinMax();
Could use the POJO :-
class ThePojo {
long theSum;
long theMin;
long theMax;
}
If you wanted all of the columns PLUS the additional columns you could for example have :-
#Query("SELECT the_table.*,sum(the_column) AS theSum, min(the_column) AS theMin, max(ithe_column) AS theMax FROM cities")
List<ThePojoPlus> getSumMinMaxPlus();
Could the the POJO :-
class ThePojoPlus {
#Embedded
TheTable the_table;
#Embedded
ThePojo pojo;
}
or (if there is no ThePojo class)
class ThePojoPlus {
#Embedded
TheTable the_table;
long theSum;
long theMin;
long theMax;
}
I'm trying to construct a where clause where a value exists either in a list or in a list of lists. The query itself is quite long, so I'll just post the WHERE-clause
WHERE site.siteID = ?3 AND items.itemID = ?4 AND ( ?5 MEMBER OF itemData.listValue )
I've tried it for a specific site and itemID and I get 3 results back which is correct. Now I've tried to also include the list of lists, but that doesn't work:
WHERE site.siteID = ?3 AND items.itemID = ?4 AND ( ?5 MEMBER OF itemData.listValue OR ?6 MEMBER OF itemListData.listValue)
This does not throw an exception, it just returns 0 results (parameters 5 and 6 are of the same value).
Here are the (shortened) corresponding entities:
public Site{
private Item item;
private List<Item> items;
}
public Item{
private List<String> listValue;
}
So really what I'm trying to do is select all entities where the passed value is either in item.listValue OR in any of the items.listValue. What baffles me is that the addition of an OR condition reduces the result set. I would much prefer a data model where Site.item is moved to the list of items, but I sadly cannot change the entities. Any help is greatly appreciated!
Currently we have a class that looks something like that (depersonalised and non-relevant parts removed):
#Entity
#Table(name = "MAIN_TABLE")
public class MainTable extends AbstractTable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "mainTable")
#OrderBy("CREATED_ON DESC")
private Set<MainTableState> states;
...
public MainTableState getActiveState(){
if(this.states == null || this.states.isEmpty()){
return null;
}
MainTableState latest = states.iterator().next();
// The reason we use this for-loop, even though we have the #OrderBy annotation,
// Is because we can later add states to this list, which aren't automatically ordered
for(MainTableState state : states){
if(state.getCreatedOn() != null && latest.getCreatedOn() != null &&
state.getCreatedOn().after(latest.getCreatedOn()){
latest = state;
}
}
return latest;
}
...
}
So currently it will retrieve all MainTableStates from the DB by default, and if we need the activeState we use the for-loop method. Obviously this is pretty bad for performance. Currently we don't use this list at all (the purpose was to have a history of states, but this has been postponed to the future), but we do use the getActiveState() method quite a bit, mostly to show a String inside of the MainTableState-class in the UI.
In addition, even if we would always use a TreeSet and keep it sorted so we won't need the loop but only need states.iterator().next() instead, it will still initialize the list of states. With some heavy performance testing we had more than 1 million MainTableState-instances when it crashed with an java.lang.OutOfMemoryError: GC overhead limit exceeded.
So, we want to change it to the following instead:
#Entity
#Table(name = "MAIN_TABLE")
public class MainTable extends AbstractEntity {
#???
private MainTableState activeState;
...
public MainTableStates getActiveState(){
return activeState;
}
...
}
So, my question, what should I put at the #??? to accomplish this? I'm assuming I need the #Formula or something similar, but how can I say to hibernate it should return a MainTableState object? I've seen #Formula being used with MAX for a date, but that was to get that date-property, not get an entire object based on that max date.
After #user2447161's suggestion I've used a #Where-annotation, which does indeed help to reduce the Collection size to 1 (sometimes), but I have two more related questions:
How to use #OnToMany and #Where but get a single object, instead of a list of objects of size one? Is this even possible? Here in a answer from December 2010 it is stated it isn't. Has this been fixed somewhere in the last six years?
How to deal with the random alias in the where clause? I could do something like this:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "mainTable")
#Where(clause = "CREATED_ON = (SELECT MAX(mts.CREATED_ON) FROM MAIN_TABLE_STATES mts WHERE mts.FK_MAIN_ID = ???.MAIN_ID)")
private Set states; // TODO Get single object instead of collection with size 1
The problem with is that ??? is a random alias generated by hibernate (sometimes it's this_, sometimes it's something along the lines of mainTable_1_, etc.). How to set this alias for the entire query to the DB to use it here? I also tried MAIN_TABLE.MAIN_ID instead which doesn't work, and with no alias it also doesn't work because it uses the MainTableState-alias instead of MainTable-alias (like this below).
from
MAIN_TABLE this_
left outer join
MAIN_TABLE_STATUSES mainstat2_
on this_.main_id=mainstat2_.fk_main_id
and (
mainstat2_.created_on = (
SELECT
MAX(mts.created_on)
FROM
MAIN_TABLE_STATUSES mts
WHERE
-- mainstat2_.main_id should be this_.main_id instead here:
mts.fk_main_id = mainstat2_.main_id
)
)
Well, regarding your question #2, as it looks like you need a quick solution with minimal impact in your existing code, this may be acceptable: you can use an Interceptor to deal with the alias and generate the right sql statement. Do this:
use a unique string as alias placeholder in your #Where clause, for instance:
...WHERE mts.FK_MAIN_ID = ${MAIN_TABLE_ALIAS}.MAIN_ID...
if your application doesn't have one yet, create an Interceptor class extending EmptyInterceptor and configure it as a SessionFactory interceptor
override the onPrepareStatement method to replace the placeholder with the alias found after 'from MAIN_TABLE' with something like this:
public String onPrepareStatement(String sql) {
String modifiedSql = sql;
if (sql.contains("${MAIN_TABLE_ALIAS}")) {
String mainTableAlias = findMainTableAlias(sql);
modifiedSql = sql.replace("${MAIN_TABLE_ALIAS}", mainTableAlias);
}
return modifiedSql;
}
Be aware that this method will be called for every sql statement that hibernate generates in your application.
Additionaly, your #Where clause only works properly when a join is used, so you should set the fetch mode explicitly adding
#Fetch(FetchMode.JOIN)
to the states property to avoid that hibernate may use the select mode.
I know there's a lot of questions about this but none of the solutions helped me.
I'm using PrimeFaces to build a lazy loadind datatable. That means that this datatable list is a LazyDataModel list, and I had to develop a LazyDataModel implementation where I overrode the load method. All of this can be learned from PrimeFaces showcase, and it works fine for most of cases, when the datable uses just one table from the database (which is not my case).
Now, I have two entities:
#Entity
#Table(name="UNIVERSITY")
public class University {
#Id
#Column(name="ID_UNIVERSITY")
#GeneratedValue(strategy = GenerationType.AUTO ,generator="SQ_UNIVERSITY")
#SequenceGenerator(name="SQ_UNIVERSITY", sequenceName="SQ_UNIVERSITY")
private Long idUniversity;
#Column(name="NAME")
private String name;
#ManyToOne
#JoinColumn(name="ID_DIRECTOR")
private Director director;
#OneToMany(fetch=FetchType.EAGER, mappedBy = "university")
private Set<Students> students = new HashSet<Students>(0);
...
public int getStudentsQty(){
return this.students.size();
}
...
Where I'll use the getStudentsQty() method to fill one column from my datatable. And here's the Students entity:
#Entity
#Table(name="STUDENTS")
public class Students
{
#Id
#Column(name="ID_STUDENTS")
#GeneratedValue(strategy = GenerationType.AUTO ,generator="SQ_STUDENTS")
#SequenceGenerator(name="SQ_STUDENTS", sequenceName="SQ_STUDENTS")
private Long idStudent;
#ManyToOne
#JoinColumn(name="ID_UNIVERSITY")
private University student;
#Column(name="NAME")
private String name;
...
Here is the search method that my load implementation will use:
public List<University> find(int startingAt, int maxPerPage,
final String sortField, final SortOrder sortOrder, Map<String, String> filters) {
session = HibernateUtil.getSession();
Criteria criteria =session.createCriteria(University.class);
List<String> aliases = new ArrayList<String>();
if(maxPerPage > 0){
criteria.setMaxResults(maxPerPage);
}
criteria.setFirstResult(startingAt);
addFiltersToCriteria(filters, criteria, aliases);
Order order = Order.desc("name");
if(sortField != null && !sortField.isEmpty()){
if(sortField.contains(".")){
String first = (sortField.split("\\."))[0];
if(!aliases.contains(first)){
criteria.createAlias(first, first);
aliases.add(first);
}
}
if(sortOrder.equals(SortOrder.ASCENDING)){
order = Order.asc(sortField);
}
else if(sortOrder.equals(SortOrder.DESCENDING)){
order = Order.desc(sortField);
}
}
criteria.addOrder(order);
return (List<University>) criteria.list();
}
And now, my problem. If I use FetchType.LAZY, everything works fine, but the performance is terrible, so I wish to use EAGER. If I use EAGER the results will come duplicated as expected and explained here. I tried to implement equals() and hashCode() methods in the University entity to use a LinkedHashSet as suggested in the last link but it didn't worked I don't know how. I also tried to use DISTINCT with Criteria but it doesn't work because of the addOrder that I use, where it asks for joins.
So, I found this other suggestion which worked perfectly. Basically the solution is to do a second Criteria query, searching only for Universities with ID included in the original search. Like this:
private List<University> removeDuplicates(Order order,
List<University> universities) {
Criteria criteria;
List<University> distinct = null;
if(universities.size() > 0){
Set<Long> idlist = new HashSet<Long>();
for(University univ: universities){
idlist.add(univ.getIdUniversity());
}
criteria = session.createCriteria(University.class);
criteria.add(Restrictions.in("id", idlist)) ;
distinct = (List<University>) criteria.list();
return distinct;
}
else{
return universities;
}
}
So it will bring, say, the first 100 lines for my lazy loadind pagination datatable. In the first Criteria search they will be sorted for the first page, and the same 100 correct rows will be present after my second Criteria search, but now they will be unsorted. It's the correct rows for the first page, but unsorted inside the first page. I cant use "addOder" in the second Criteria or else they will come duplicated.
And the strangest thing: if I try to sort the results with Collections.sort the results will be duplicated!!! How?? How can I order my result after all?
Thanks!!
EDIT: the students count is just an example, I'll need in another scenarios get information inside each associated entity.
If I understand correctly you are outputting a table listing universities and you want to show the number of students for each university. If so, loading x000 student records into memory just to get a count is crazy (regardless of whether you do it eagerly or lazily).
Try one of the following:
One
rather than loading the associated students to get the count use Hibernates #Formula functionality and add a derived property to you University entity.
#Formula(value = "select count(*) from students where university_id = ?")
private int studentCount;
Two
Create a a database view say university_summary_data which includes this count and create an Entity mapped to this view (works just like a table) then in University:
#OneToOne
private UniversitySummaryData data;
Three
Look into Hibernate's #LazyCollection(LazyCollectionOption.EXTRA) which will allow you to call size() on the mapped collection without loading all Students.
All much simpler solutions that what you have.
You say that you want to switch to EAGER loading because the performance with Lazy loading is terrible, but with EAGER loading your performance will be the same. You will still get the select n + 1 problem explained for example here and here with solution.
For performance to improve you need to modify the query that Hibernate will generate. Usually I do a left outer join in Hibernate to obtain this, e.g.
sessionFactory.getCurrentSession().createCriteria(University.class)
.createAlias("students", "students_alias", JoinType.LEFT_OUTER_JOIN)
.list();
And it's best to keep the Hibernate default of lazy loading.
I export data from MS SQLServer to an xml file, then use that dataset in running unit tests that require database. I use dbunit maven plugin for it.
Unfortunately for me, not all columns in some tables are mapped in my Entity classes.
As an example, say, we have a table called 'member'.
Member table has three columns: memberid, membername, memberrank.
When I do an export, I get all three columns exported.
But in my MemberEntity class, I only map memberid and membername, because I do not need memberrank in my application. So I would have the MemberEntity looking like this:
#Entity
#Table(name = "member")
public class MemberEntity {
#Id
#GeneratedValue()
#Column(name = "memberid", nullable = false)
private Integer memberid;
#Column(name = "membername", nullable = false)
private String membername;
...
}
Then, I try to insert dataset into HSQLDB before a test case:
IDatabaseConnection conn = new DatabaseConnection(((SessionImpl) (entityManager.getDelegate())).connection());
IDataSet dataset = new XmlDataSet(
resourceLoader.getResource("classpath:dataset.xml").getInputStream());
conn.getConfig().setProperty("http://www.dbunit.org/properties/datatypeFactory", new MsSqlDataTypeFactory());
DatabaseOperation.CLEAN_INSERT.execute(conn, dataset);
At this point, I get an exception saying the column MemberRank does not exist. It says something like the following:
org.dbunit.dataset.NoSuchColumnException: MEMBER.MEMBERRANK - (Non-uppercase input column: memberrank) in ColumnNameToIndexes cache map. Note that the map's column names are NOT case sensitive.
When I remove the column from the dataset, all is well. If I add in the memberRank mapping to my Entity class, again, all goes well.
But I cannot add the column mapping into my Entity class. Is there an easy way (other than removing the column and the associated data from the exported dataset manually) of excluding that column from being (attempted to be) added in when I do INSERT?
In hibernate every non static non transient property (field or method depending on the access type) of an entity is considered persistent, unless you annotate it as #Transient.
for example,
#Transient
public int counter; //transient property
private String firstname; //persistent property
The methods and fields annotated as #Transient will be ignored by the entity manager.See here for more information.
Maybe this answer comes a little bit late, but I've just run into a similar problem and wrote the following method to solve it (I'm using dbUnit 2.5.0). Hope it helps somebody.
/**
* Generates a new data set with the columns declared in the
* "excludedColumns" map removed.
*
* #param src
* Source data set.
* #param excludedColumns
* Map of table names and column names. Columns in this map are
* removed in the resulting data set.
* #return Data set with the columns declared in the "excludedColumns" map
* removed. Tables that are not specified in the "excludedColumns"
* map are left untouched.
* #throws DataSetException
*/
public static IDataSet filterDataSet(IDataSet src,
Map<String, Set<String>> excludedColumns) throws DataSetException {
if (excludedColumns == null) {
return src;
}
ArrayList<ITable> tables = new ArrayList<ITable>(
src.getTableNames().length);
for (String tableName : src.getTableNames()) {
if (excludedColumns.containsKey(tableName)) {
ITable filteredTable = DefaultColumnFilter
.excludedColumnsTable(
src.getTable(tableName),
excludedColumns.get(tableName).toArray(
new String[0]));
tables.add(filteredTable);
} else {
tables.add(src.getTable(tableName));
}
}
return new DefaultDataSet(tables.toArray(new ITable[0]),
src.isCaseSensitiveTableNames());
}
The core of the method is DefaultColumnFilter. I'm using a commodity static method here, but an instance of DefaultColumnFilter gives a lot of flexibility.
I wonder if there is a more straight forward way of doing this.