I have a REST API which will provide search results based on specific search criteria.
User can search using Department Id, User Id and Joined Date.
So here, user can search in 7 different combinations.
Search only with Dept Id
Search only with User Id
Search only with Joined Date
Search with Dept Id & User Id
Search with User Id & Joined Date
Search with Dept Id & Joined Date
Search will all 3 criteria (Dept Id, User Id & Joined Date)
According to our application's current design, For each criteria I would have to call different services in different order for information.
For instance : We have an existing User service which has user details, a Department service which has details. So if user searches only for User ID, I would directly go to UserService. If user searches with Dept ID & User I would first have to query Dept service and then the User service.
My current code is:
if(Util.hasOnlyUserId(searchCriteria)) {
searchResponse = searchWithUserId(searchRequest);
} else if(Util.hasOnlyJoinedDate(searchCriteria)) {
searchResponse = searchWithDate(searchRequest);
} else if (Util.hasOnlyDeptID(searchCriteria)) {
searchResponse = searchWithDeptId(searchRequest);
} else if (Util.hasUserIdAndDeptId(searchCriteria)) {
searchResponse = searchWithUserIdAndDeptId(searchRequest);
} else if (Util.hasUserIdAndDate(searchCriteria)){
searchResponse = searchWithUserIdAndDate(searchRequest);
} else if (Util.hasDeptIdAndDate(searchCriteria)) {
searchResponse = searchWithDeptIdAndDate(searchRequest);
} else if (Util.hasAllCriteria(searchCriteria)) {
searchResponse = searchWithAllCriteria(searchRequest);
}
Here, searchCriteria is my request object (POJO) which has these search elements as private members.
searchResponse here is a type of SearchResponse object which is the actual response of the API and it contains the user's details like name, id, position, lastSalaryDrawn, CommuteType, MaritalStatus and Last Promoted Date
I have been trying to optimize this multiple if-else condition in other words, avoid having this multiple condition.
I tried to separate each of them and put them into different methods. But this just isn't satisfying me and it seems like it is in an un-optimizable state.
Any ideas on how I can go about doing this would be greatly appreciated.
Edit : We are using JPA to interact with the MySQL.
The problem here is I will not get the information in a straight forward manner from a DB. So building a dynamic query might not work.
The information needs to be fetched from multiple micro services, which will in turn make a call to their native DB and send the required information as a response.
I hope this make sense.
You can move the code to the place where the criteria is executed and build the query dynamically:
public SearchResponse searchWithCriteria(Criteria searchCriteria) {
StringBuilder query = new StringBuilder("SELECT * FROM ... WHERE ...");
if (util.hasUserId(searchCriteria))
query.append(" AND userId = :userId");
if (util.hasJoinedDate(searchCriteria))
query.append(" AND joinedDate = :joinedDate");
...
PreparedStatement stmt = connection.prepareStatement(query);
if (util.hasUserId(searchCriteria))
stmt.setString("userId", searchCriteria.getUserId());
if (util.hasJoinedDate(searchCriteria))
stmt.setDate("joinedDate", searchCriteria.getJoinedDate());
...
ResultSet res = stmt.executeQuery();
...
}
Code could be optimized with Strategy pattern.
Various patterns can be used here, and you have to choose one depending on how the code of the search looks like. Some people have already mentioned the Strategy and Chain of Responsibility patterns. Another one you could look at is the Builder pattern. It allows you to have optional parts which are only specified if you need them, to build the final object.
So you could have a SearchBuilder class that updates the information about the search as you add more filterning criteria. It could have methods like setUserId(...), setDeptId(...) etc.
For example: If the search was being translated to a plain SQL query in a PreparedStatement, whenever one of the filtering fields is set, the builder would store them (they would start as null). So if in your builder you had called setUserId() and setDeptId(), those would not be null inside the builder and you generate a query with the string (appending AND ... for each extra field you want to filter with):
select * from users where id=? AND dept_id=?
The SearchBuilder could offer a build() method which then returns a Search object which carries the query and maybe even offers the method that does the query, or whatever you need to do.
Related
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
I have a table PERSON with more than 5 millions rows and I need to update field NICKNAME on each one of them based on the field NAME inside the same table.
ResultSet rs = statement.executeQuery("select NAME from PERSON");
while(rs.next())
{
// some parsing function like:
// Nickname = myparsingfunction(rs.getString("NAME"));
rs.updateString( "NICKNAME", Nickname );
rs.updateRow();
}
But I got this error:
not implemented by SQLite JDBC driver
I'm using sqlite-jdbc-3.8.11.2.jar downloaded at https://bitbucket.org/xerial/sqlite-jdbc/downloads.
I know I could use the following SQL query:
statement.executeUpdate("update PERSONS set NICKNAME = Nickname where ID = Id");
But that would take forever and I understand updating ResultSet would be faster. So what options do I have to update the table on the fastest way? Any other driver available? Should I move out of Java?
UPDATE
I was able to find a fast solution using below syntax. The block between CASE and END was a concatenated string that I built before executing the SQL query, so I could send all updates at once.
update PERSON
set NICKNAME= case ID
when 173567 then 'blabla'
when 173568 then 'bleble'
...
when 173569 then 'blublu'
end
where ID in (173567, 173568, 173569)
As you have encountered, the SQLite JDBC driver does not currently support the updateString operation. This can be seen in the source code for this driver.
I can think of three options:
As you stated in your question, you can select the name and ID of the person and then update the person by its ID. Those updates could be done in a batch (using PreparedStatement.addBatch()) to improve performance (tutorial).
Implement the method myparsingfunction in pure SQL so that the query could become UPDATE PERSONS SET NICKNAME = some_function(NAME).
Create an user-defined function (using org.sqlite.Function), implemented in Java, and call it inside the SQL. Example, taken from this answer:
Function.create(db.getConnection(), "getNickName", new Function() {
protected void xFunc() throws SQLException {
String name = value_text(0);
String nickName = ...; // implement myparsingfunction here
result(nickName);
}
});
and use it like this: UPDATE PERSONS SET NICKNAME = getNickName(NAME);
SQLite does not support stored procedures so that option is out of the table.
I'm not sure which of these options would provide the best performance (certainly using pure SQL would be faster but that may not be a viable solution). You should benchmark each solution to find the one that fits you.
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();
I have an application developed based on MySQL that is connected through Hibernate. I used DAO utility code to query the database. Now I need optimize my database query by indexes. My question is, how can I query data through Hibernate DAO utility code and make sure indexes are used in MySQL database when queries are executed. Any hints or pointers to existing examples are appreciated!
Update: Just want to make the question more understandable a little bit. Following is the code I used to query the MySQL database through Hibernated DAO utility codes. I'm not directly using HQL here. Any suggestions for a best solution? If needed, I will rewrite the database query code and use HQL directly instead.
public static List<Measurements> getMeasurementsList(String physicalId, String startdate, String enddate) {
List<Measurements> listOfMeasurements = new ArrayList<Measurements>();
Timestamp queryStartDate = toTimestamp(startdate);
Timestamp queryEndDate = toTimestamp(enddate);
MeasurementsDAO measurementsDAO = new MeasurementsDAO();
PhysicalLocationDAO physicalLocationDAO = new PhysicalLocationDAO();
short id = Short.parseShort(physicalId);
List physicalLocationList = physicalLocationDAO.findByProperty("physicalId", id);
Iterator ite = physicalLocationList.iterator();
while(ite.hasNext()) {
PhysicalLocation physicalLocation = (PhysicalLocation)ite.next();
List measurementsList = measurementsDAO.findByProperty("physicalLocation", physicalLocation);
Iterator jte = measurementsList.iterator();
while(jte.hasNext()){
Measurements measurements = (Measurements)jte.next();
if(measurements.getMeasTstime().after(queryStartDate)
&& measurements.getMeasTstime().before(queryEndDate)) {
listOfMeasurements.add(measurements);
}
}
}
return listOfMeasurements;
}
Just like with SQL, you don't need to do anything special. Just execute your queries as usual, and the database will use the indices you've created to optimize them, if possible.
For example, let's say you have a HQL query that searches all the products that have a given name:
select p from Product where p.name = :name
This query will be translated by Hibernate to SQL:
select p.id, p.name, p.price, p.code from product p where p.name = ?
If you don't have any index set on product.name, the database will have to scan the whole table of products to find those that have the given name.
If you have an index set on product.name, the database will determine that, given the query, it's useful to use this index, and will thus know which rows have the given name thanks to the index. It willl thus be able to only read a small subset of the rows to return the queries data.
This is all transparent to you. You just need to know which queries are slow and frequent enough to justify the creation of an index to speed them up.
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");