App engine datastore - query on Enum fields - java

I am using GAE(Java) with JDO for persistence.
I have an entity with a Enum field which is marked as #Persistent and gets saved correctly into the datastore (As observed from the Datastore viewer in Development Console). But when I query these entities putting a filter based on the Enum value, it is always returning me all the entities whatever value I specify for the enum field.
I know GAE java supports enums being persisted just like basic datatypes. But does it also allow retrieving/querying based on them? Google search could not point me to any such example code.
Details:
I have printed the Query just before being executed. So in two cases the query looks like -
SELECT FROM com.xxx.yyy.User WHERE role == super ORDER BY key desc RANGE 0,50
SELECT FROM com.xxx.yyy.User WHERE role == admin ORDER BY key desc RANGE 0,50
Both above queries return me all the User entities from datastore in spite of datastore viewer showing some Users are of type 'admin' and some are of type 'super'.

For time being, I have replaced the Enums with simple integer constants. Reported this case as an issue in the google app engine : http://code.google.com/p/googleappengine/issues/detail?id=2927

For a parameter other than a String or an int, I believe you need to use declareParameters instead. Try something like this:
Query q = pm.newQuery(com.xxx.yyy.User.class);
q.setFilter("role == p1"); //p1 is a variable place holder
q.declareParameters("Enum p1"); //here you define the data type for the variable, in this case an Enum
q.setRange(0, 50);
q.setOrdering("key desc");
AbstractQueryResult results = (AbstractQueryResult) pm.newQuery(q).execute(admin);
or if you want more gql like syntax -
Query query = pm.newQuery("SELECT FROM com.xxx.yyy.User WHERE role == p1 ORDER BY key desc RANGE 0,50");
query.declareParameters("Enum p1");
AbstractQueryResult results = (AbstractQueryResult) pm.newQuery(q).execute(admin);

You need to use your enum's class name when you declare the query parameter.
For example, if you build your query using the method style, and assuming your enum is called Role and is declared under the User class, you can do something like the following:
Query query = pm.newQuery(com.xxx.yyy.User.class);
query.setFilter("role == roleParam");
query.declareParameters(com.xxx.yyy.User.Role.class.getName() + " roleParam");

Related

Handling dynamic queries with Spring

The problem I'm trying to solve here is, filtering the table using dynamic queries supplied by the user.
Entities needed to describe the problem:
Table: run_events
Columns: user_id, distance, time, speed, date, temperature, latitude, longitude
The problem statement is to get the run_events for a user, based on a filterQuery.
Query is of the format,
((date = '2018-06-01') AND ((distance < 20) OR (distance > 10))
And this query can combine multiple fields and multiple AND/OR operations.
One approach to solving this is using hibernate and concatenating the filterQuery with your query.
"select * from run_events where user_id=:userId and "+filterQuery;
This needs you to write the entire implementation and use sessions, i.e.
String q = select * from run_events where user_id=:userId and "+filterQuery;
Query query = getSession().createQuery(q);
query.setParameter("userId", userId);
List<Object[]> result = query.list();
List<RunEvent> runEvents = new ArrayList<>();
for(Object[] obj: result){
RunEvent datum = new RunEvent();
int index = -1;
datum.setId((long) obj[++index]);
datum.setDate((Timestamp) obj[++index]);
datum.setDistance((Long) obj[++index]);
datum.setTime((Long) obj[++index]);
datum.setSpeed((Double) obj[++index]);
datum.setLatitude((Double) obj[++index]);
datum.setLongitude((Double) obj[++index]);
datum.setTemperature((Double) obj[++index]);
runEvents.add(datum);
}
This just doesn't seem very elegant and I want to use the #Query annotation to do this i.e.
#Query(value = "select run_event from RunEvent where user_id = :userId and :query order by date asc")
List<RunEvent> getRunningData(#Param("userId") Long userId,
#Param("query") String query,
);
But this doesn't work because query as a parameter cannot be supplied that way in the query.
Is there a better, elegant approach to getting this done using JPA?
Using Specifications and Predicates seems very complicated for this sort of a query.
To answer the plain question: This is not possible with #Query.
It is also in at least 99% of the cases a bad design decision because constructing SQL queries by string concatenation using strings provided by a user (or any source not under tight control) opens you up for SQL injection attacks.
Instead you should encode the query in some kind of API (Criteria, Querydsl, Query By Example) and use that to create your query. There are plenty of questions and answers about this on SO so I won't repeat them here. See for example Dynamic spring data jpa repository query with arbitrary AND clauses
If you insist on using a SQL or JPQL snippet as input a custom implementation using String concatenation is the way to go.
This opens up attack for SQL injection. Maybe that’s why this feature is not possible.
It is generally a bad idea to construct query by appending random filters at the end and running them.
What if the queryString does something awkward like
Select * from Foo where ID=1234 or true;
thereby returning all the rows and bringing a heavy load on DB possibly ceasing your whole application?
Solution: You could use multiple Criteria for filtering it dynamically in JPA, but you’ll need to parse the queryString yourself and add the necessary criteria.
You can use kolobok and ignore fields with null values.
For example create one method like bellow
findByUserIdAndDistanceaLessThanAndDistancebGreaterThan....(String userid,...)
and call that method only with the filter parameters while other parameters are null

Dynamic bind select values in MySQL query jdbi

How can i write dynamic SQL query in Jbdi, like in my project customer will ask some details like first name,last name,mobile. so i will read those values into string and my idea was directly append that to SQL query dynamically like
select first name,last name,mobile from customer
and another user ask only first name then my query will change like
select first name from customer where customer Id=12345
I am doing something similar for dynamically generated search criteria strings in a Dropwizard application using JDBI.
#UseStringTemplate3StatementLocator
public interface ThingieDao {
#SqlQuery
#MapResultAsBean
Iterator<Thingie> search(#Define("criteria") String criteria);
}
The defined string "criteria" can then be used in the SQL template:
group ThingieDao;
search(criteria) ::= <<
SELECT * FROM table_name WHERE <criteria>
>>
You can use the same technique to insert any string in the SQL, the SELECT column names in your case.
The variable name in the interface does not matter, it is the #Define annotation string that matters.. Assuming Dropwizard doesn't add anything magic, and it usually doesn't, I guess that should work using plain JDBI as well.

how to write raw play queries for a table which is not mapped to an entity

I am using play framework for the first time and I need to link objects of the same type. In order to do so I have added a self referencing many to many relationship like this:
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name="journal_predecessor", joinColumns={#JoinColumn(name="journal_id")}, inverseJoinColumns={#JoinColumn(name="predecessor_id")})
public List<Journal> journalPredecessor = new ArrayList<Journal>();
I obtain the table journal_predecessor which contains the two columns: journal_id and predecessor_id, both being FKs pointing to the primary key of the table journal.
My question is how can I query this table using raw queries if I am using H2 in-memory database. thanks!
Actually it was very easy. I just needed to create an instance of SqlQuery to create a raw query:
SqlQuery rawQuery = Ebean.createSqlQuery("SELECT journal_id from journal_predecessor where journal_id=" + successorId + " AND predecessor_id=" + predecessorId);
And because i just needed to check weather a row exists or not, I find the size of the set of the results returned by the query:
Set<SqlRow> sqlRow = rawQuery.findSet();
int rowExists = sqlRow.size();

I can't use any ID in my code

I was always taught to use IDs in my code to refer to records into the database.
But let's take the case we have same roles in the table Role. Now I want to query only the records related to the role Player:
ID ROLE
1 Admin
2 Organizer
3 Player
I don't know in my code the ID of Player, but I want to retrieve all the players, so with Hibernate I wrote:
String queryString = "from User u where u.role.role = ?";
Query queryObject = getSession().createQuery(queryString);
queryObject.setParameter(0, "player");
return queryObject.list();
As you can see I wrote "player" in the code. I think this is not the best way and I should use an ID instead. But I don't know the ID and it may change depending on the server on which I run the application. A second problem with my solution is that "player" can be capitalized into the database and this may be changed over time.
So, what should be the solution to all these problems? Is there any way to use the ID instead? Or any other way to improve this code?
In this case it seems that role should be an enum and your query would look something like:
queryObject.setParameter(0, Role.PLAYER);
Also, you might take a look at the criteria API which will help you create more type-safe queries that are more robust vs. refactoring.
You should create a enum class like this.
public enum Role {
Admin(1),
Organizer(2),
Player(3);
}
And change your code to
String queryString = "from User u where u.id= ?";
Query queryObject = getSession().createQuery(queryString);
queryObject .setParameter(0, Role.PLAYER);
return queryObject.list();
You can do using create a mapping table like ,
UserRoleMapping
ID - Incremental,
UserId - Refers to user table,
RoleId - Refers to role table
As one user can have more than one role so it will satisfy that thing too.
to Get the roles using query
select role.id from userrolemapping urm innerjoin on user u.id = urm.id where u.id = ?
using IDs or string/vachar etc. lookup is all dependent on the data that you have in the database. Some organization keep the id static and some keep the name/description static. So, make sure you have good understanding of your DB data and what will stay static . However, if the role is static you can use HQL ignore case like the example I provided for you below (I'm not adding information about the ID static path because others have already provided information about it for and don't want to duplicate it ).
--note you can take the percentages out if you only want "player"
String queryString = "from User u where lower( u.role.role ) like lower('%"+ ? +"%')";
Query queryObject = getSession().createQuery(queryString);
queryObject.setParameter(0, "player");
return queryObject.list();

How to Manipulate SQL specified in jrxml template

Problem: Report Templates are created by Admin Users who only decide what data to show where as the filter for the data is specified by the business user. In simpler SQL terms, query is specified by the Admin User, Business User specifies the WHERE clause.
Jasper allows user to specify parameters in SQL query like $P{city}. I have tried to retrieve the query dynamically using the method specfied in the link.
Possible Solution can be
Use WHERE clause parameters in JRXML and replace them while report creation - This will save me SQL parsing but I don't want to guide the admin user with this complexity. Parsing is not a huge problem.
Use my custom jdbc query executor and factory, only created to allow me extension point before jasper fire SQL query. I will be completely relying on vanilla Jasper JDBC data source but will only modify query before execution. JRAbstractQueryExecuter simplifies the query and replace the jasper related tokens before firing query - This will be very dirty and will force me to be implementation specific.
Do the same replacement as it is done in JRAbstractQueryExecuter in my application code base, parse the SQL query, modify it and set it again as specified in link
Can you please suggest a better way of doing this? I have a feeling that this can definitly be done in cleaner way.
Thanks for your help
You could create an input control to determine the desired WHERE clause and use a parameter to hold the contents of that WHERE clause. The default value expression would be something like:
$P{theParameter} == "value_1" ?
(" AND CONDITION_1 IN ('A', 'B', 'C') AND CONDITION_2 = 'Yes' "
) : " AND CONDITION_3 = 'Other' AND CONDITION_4 = 'No' "
Then in your WHERE clause you would reference it like:
WHERE
.... = .....
AND .... = ....
AND .... = ....
$P!{theParameter}
If your constraint columns are the same across your WHERE clauses, you could use $P! to bring in the parameter value literally, and reference it in your query:
WHERE
.... = .....
AND .... = ....
AND .... = ....
AND thisValue = $P!{theParameter}

Categories