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();
Related
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.
Hi I'm trying to select records from one table which doesn't have records in connected many-to-many table with specific values.
I will explain on sample tables:
documentation:
id_documentation
irrelevant_data
user:
id_user
irrelevant_data
documentation_user:
id_documentation
id_user
role
What I want to achieve is to select every single documentation which doesn't have user in specific role. Any ideas?
The main problem is that I'm using java's CriteriaBuilder to create query so using subqueries is impossible (I think).
You can add restrictions on your left join using: createAlias(java.lang.String, java.lang.String, int, org.hibernate.criterion.Criterion) method, see API.
Check this answer for an example on how to use the left join with a criteria.
Main problem does not exist - Criteria API do have SubQuery. Query itself selects instances of User and uses not in construct to limit results based to subquery. Subquery selects all users that are connected to document with specific role via DocumentationUser.
Try something like this (code not tested):
CriteriaQuery<Documentation> cq = cb.createQuery(Documentation.class);
Root<Documentation> u = cq.from(Documentation.class);
Subquery<Integer> sq = cq.subquery(Integer.class);
Root<User> su = sq.from(User.class);
sq.select(su.get("id_user"));
Join<User, DocumentationUser> du = su.join("documentationUserCollection");
sq.where(cb.equals(du.get("role"), "mySpecificRole"));
cq.where(cb.not(cb.in(u.get("id_user")).value(sq)));
See also this useful answer on SO.
I hava a java app, with 2 objects: User.java and Review.java.
Each User can have many Reviews.
Review object has a User object on it (eg: review.getUser())
I need an hql query that will get all Users that have no Reviews. How do I do this?
Try
from User u where u.reviews is empty
assuming your User class has a collection of reviews, of course...
from User u where not exists (from Review r where r.user = u)
I do not know your tables and the columns in these tables, but anyway you should have a query like the following:
select *
from User
where not exists (select Review where Review.userId = User.id )
I am new to the Hibernate and HQL. I want to write an update query in HQL, whose SQL equivalent is as follows:
update patient set
`last_name` = "new_last",
`first_name` = "new_first"
where id = (select doctor_id from doctor
where clinic_id = 22 and city = 'abc_city');
doctor_id is PK for doctor and is FK and PK in patient. There is one-to-one mapping.
The corresponding Java classes are Patient (with fields lastName, firstName, doctorId) and Doctor (with fields doctorId).
Can anyone please tell what will be the HQL equivalent of the above SQL query?
Thanks a lot.
String update = "update Patient p set p.last_name = :new_last, p.first_name = :new_first where p.id = some (select doctor.id from Doctor doctor where doctor.clinic_id = 22 and city = 'abc_city')";
You can work out how to phrase hql queries if you check the specification. You can find a section about subqueries there.
I don't think you need HQL (I know, you ask that explicitly, but since you say you're new to Hibernate, let me offer a Hibernate-style alternative). I am not a favor of HQL, because you are still dealing with strings, which can become hard to maintain, just like SQL, and you loose type safety.
Instead, use Hibernate criteria queries and methods to query your data. Depending on your class mapping, you could do something like this:
List patients = session.CreateCriteria(typeof(Patient.class))
.createAlias("doctor", "dr")
.add(Restrictions.Eq("dr.clinic_id", 22))
.add(Restrictions.Eq("dr.city", "abc_city"))
.list();
// go through the patients and set the properties something like this:
for(Patient p : patients)
{
p.lastName = "new lastname";
p.firstName = "new firstname";
}
Some people argue that using CreateCriteria is difficult. It takes a little getting used to, true, but it has the advantage of type safety and complexities can easily be hidden behind generic classes. Google for "Hibernate java GetByProperty" and you see what I mean.
update Patient set last_name = :new_last , first_name = :new_first where patient.id = some(select doctor_id from Doctor as doctor where clinic_id = 22 and city = abc_city)
There is a significant difference between executing update with select and actually fetching the records to the client, updating them and posting them back:
UPDATE x SET a=:a WHERE b in (SELECT ...)
works in the database, no data is transferred to the client.
list=CreateCriteria().add(Restriction).list();
brings all the records to be updated to the client, updates them, then posts them back to the database, probably with one UPDATE per record.
Using UPDATE is much, much faster than using criteria (think thousands of times).
Since the question title can be interpreted generally as "How to use nested selects in hibernate", and the HQL syntax restricts nested selects only to be in the select- and the where-clause, I would like to add here the possibility to use native SQL as well. In Oracle - for instance - you may also use a nested select in the from-clause.
Following query with two nested inner selects cannot be expressed by HQL:
select ext, count(ext)
from (
select substr(s, nullif( instr(s,'.', -1) +1, 1) ) as ext
from (
select b.FILE_NAME as s from ATTACHMENT_B b
union select att.FILE_NAME as s from ATTACHEMENT_FOR_MAIL att
)
)
GROUP BY ext
order by ext;
(which counts, BTW, the occurences of each distinct file name extension in two different tables).
You can use such an sql string as native sql like this:
#Autowired
private SessionFactory sessionFactory;
String sql = ...
SQLQuery qry = sessionFactory.getCurrentSession().createSQLQuery(sql);
// provide an appropriate ResultTransformer
return qry.list();
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");