how to construct spring JPA/JPQL query in JpaRepository - java

I've got a DB structure like this:
topics 1:n posts 1:n comments and users n:n roles.
I want to get all comments that user have an access.
Giving access for me means (when I create post object I automaticly create role object called PostName with prefix role_comment_ e.g. post called abc have role called: role_comment_abc)
Now I try to create jpa/jpql query like below:
find all comments by User where user_id is =:1 and role_name contaings =:2
findByUserIdAndRoleNameContaining(Integer userId, String roleName);
This is how my User, Role and comment tables looks like:
Roles table:
#Entity
#Table(name = "roles")
public class Role {
#Id
private Integer id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>();
Users and user_role tables:
#Entity
#Table(name = "users")
public class User {
#Id
private Integer id;
private String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
#JoinTable(
name = "user_role",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") }
)
private Set<Role> roles = new HashSet<>();
And this is comments table:
#Entity
#Table(name = "comments")
public class Comments{
#Id
private Integer id;
private String name;
private String description;
#ManyToOne(optional = false)
#JoinColumn(nullable = false, name = "user_id")
private User user
Unfortunetly if I create query in JpaRepository called:
List<Comments> findByUserId(Integer id);
If i'm not wrong it will print comments created by that specific user.
So what I really want to achive? Let me show you this on example data:
roles:
100;"role_comment_ab"
101;"role_comment_cd"
102;"role_comment_ef"
103;"something_else"
Comments in post with name ab:
1;"Test1";"Test description";10
2;"Test2";"Test description";10
comments in post with name cd:
3;"Test3";"Test description";10
4;"Test4";"Test description";10
comments in post with name ef:
5;"Test5";"Test description";10
6;"Test6";"Test description";10
users:
10;"Thomas" (logged user)
11;"John"
users_roles:
10;100
11;101
10;102
10;103
input:
findByUserIdAndRoleNameContaining(10, "role_comment_");
output:
1;"Test1";"Test description";10
2;"Test2";"Test description";10
5;"Test5";"Test description";10
6;"Test6";"Test description";10
I'm really out of clue how my query should look like. Pleast atleast give me a small hint.
UPDATE:
After adding #Bohdan Petrenko solution:
#Query("select c from Comment c join c.user u join u.roles r where u.id = :userId and lower(r.name) like lower(:roleName)")
List<Comment> findByUserIdAndRoleNameContaining(#Param("userId") Integer userId, #Param("roleName") String roleName);
roleName = "%" + roleName.trim() + "%";
I noticed that this solution prints all comments if #Param roleName contains "roleName" String.
So if I have role_postName1 and role_postName2
it prints:
comment1FromPost1
comment2FromPost1
comment1FromPost2
comment2FromPost2
comment1FromPost1
comment2FromPost1
comment1FromPost2
comment2FromPost2
It would've be great to find solution to print comments from posts only if user have role called role_postName.

#Query("select t from Topic t
join t.user u
where u.id = :userId
and u.roles in :roleNames")
List<Topic> findByUserIdAndRoleNameContainedIn(#Param("userId") Integer userId, #Param("roleNames") List<String> roleNames);

Please be more specific in your questions.
If I understood you correctly - You want to select Topics by user id and Role name. If yes you may try something like this:
#Query("select t from Topic t join t.user u join u.roles r where u.id = :userId and lower(r.name) like lower(:roleName)")
List<Topic> findByUserIdAndRoleNameContaining(#Param("userId") Integer userId, #Param("roleName") roleName);
But you'll also need to change role name before passing it to the repository method findByUserIdAndRoleNameContaining:
roleName = "%" + roleName.trim() + "%";
UPDATE
This will work without any custom SQL and roleName modifications
List<Comment> findByUser_IdAndUser_Roles_NameContainingIgnoreCase(Integer userId, String roleName);
And also I don't understand the trouble you are faced with now. So, provide us some examples with a test data (from the data base), correct and incorrect expected results of the query. As I don't understand the reason of your problem I can't help you to solve it.

Related

Hibernate Query Language: Getting a SQL Syntax Error for my Query

I already checked many related questions but the answers there did not fix my problem yet.
I have entities User and Character that are mapped via Hibernate.
#Entity
#Table(name = "user", uniqueConstraints = #UniqueConstraint(columnNames = { "username" }))
public class User {
#Id
#NotEmpty
#Column(name = "username")
private String username;
...
}
#Entity
#Table(name = "character", uniqueConstraints = #UniqueConstraint(columnNames = { "charId" }))
public class Character {
...
#NotEmpty
#ManyToOne
#JoinColumn(name = "username", referencedColumnName = "username")
private User user;
...
}
I want to select all characters that have a specific username, so the query that I use looks like this:
private final static String RETRIEVE_CHARACTERS_FOR_USER = "select c from Character as c inner join User as u where u.username = :paramUsername";
and my Code looks like this:
result = session.createQuery(RETRIEVE_CHARACTERS_FOR_USER, Character.class)
.setParameter(PARAM_USERNAME, user.getUsername()).getResultList();
I also tried to remove the foreign key, use String username instead of User user and just query for c.username, so I am not even sure if the problem is Hibernate related...
I hope you can help me! :)
This should work:
private final static String RETRIEVE_CHARACTERS_FOR_USER = "select c from Character c where c.user.username = :paramUsername";
Ok, actually it was really a problem with the 'character' keyword... changed the table name to 'charac' and it worked fine.

How to avoid joining table when querying by a foreign key?

I have a Comment class with a user property defined as:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
#NonNull
private User user;
I have a CommentRepository:
public interface CommentRepository extends CrudRepository<Comment, Integer> {
List<Comment> findByUserId(Integer userId);
}
I want to query a particular user's comments by his id.
I'm doing this:
commentRepository.findByUserId(userId);
everything works fine except the query looks like:
select
comment0_."id" as id1_1_,
comment0_."text" as url2_1_,
comment0_."user_id" as user_id3_1_
from
"comments" comment0_
left outer join
"users" user1_
on comment0_."user_id"=user1_."id"
where
user1_."id"=?
I want to avoid this join as I can query directly by the user_id column in a comments table.
I don't want to use a #Query annotation, I think there should be a smarter way.
The default value for #ManyToOne(optional = true) and #JoinColumn(nullable = true) causes this extra join. You may try,
#ManyToOne(fetch=FetchType.LAZY, optional = false)
#JoinColumn(name="`user_id `", nullable = false)

Hibernate nested JoinColumns results in a big query from the database with unnecessary data

I'm working on some personal project but i have a question about hibernate.
I have a class structure like this :
#Entity
public class User {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyUser")
private Company company = new Company();
}
But inside the company i have another join.
#Entity
public class Company {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyEstimateOption")
private EstimateOptions estimateOptions = new EstimateOptions();
}
Now i do a query to get the estimate options.
But if i do it like this it loads lots of unnecessary stuff .
#RequestMapping(value = "/estimateoptions")
public EstimateOptions getCompanyEstimateOptions(#AuthenticationPrincipal Principal user) {
User getuser = userDao.findByEmail(user.getName());
EstimateOptions estimateOptions = getuser.getCompany().getEstimateOptions();
return estimateOptions;
}
is there a better approach for this ?
There are a lot of ways to do such optimization. The simplest one is add bidirectional associations to Company and EstimateOptions with lazy loading.
An example for Company ( I don't test. It is just a sketch.)
#Entity
public class Company {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fkIdCompanyEstimateOption")
private EstimateOptions estimateOptions = new EstimateOptions();
#OneToOne(mappedBy="company", fetch = FetchType.LAZY)
private User user;
}
And do something like this (this is HQL but you can use a criteria API too)
from EstimateOptions options inner join options.company company inner join company.user user where user.name = :userName
You can see HQL joined query to eager fetch a large number of relationships for additional thoughts.
Updated
I am not sure but may be you can do something like this (without additional associations)
select options from User user inner join user.company company inner join company.estimateOptions options where user.name = :userName

Ebean - Not all records are returned

I am totally new in the world of the Ebean ORM persistence layer used in Play Framework 2. My initial impression is really good but in my hobby project I stumbled on the issue I don't know how to solve. I am not sure whether there is something I did wrong (models?) or maybe it's just lack of my knowledge on Ebean.
I have two models: User and UserAccount:
User may or may not have an account.
UserAccount always points to exactly one User.
So there is User model:
#Entity
public class User extends Model {
#Id
private Long id;
#NotNull
private String name;
#NotNull
private String surname;
#OneToOne(cascade=CascadeType.ALL, mappedBy = "user")
private UserAccount userAccount;
public static Finder<Long, User> find = new Finder<>(
Long.class, User.class
);
// ... Getters and Setters ...
}
and there is UserAccount model:
#Entity
public class UserAccount extends Model {
#Id
private Long id;
#NotNull
private String username;
#NotNull
private String password;
#NotNull
#OneToOne
#JoinColumn(name = "user_id")
private User user;
public static Finder<Long, UserAccount> find = new Finder<>(
Long.class, UserAccount.class
);
// ... Getters and Setters ...
}
What I would like to do is to fetch all users with and without accounts.
I was sure that this piece of code will do the job:
User.find.all();
... but on my surprise only users that had an associated account record were returned.
Another interesting thing is that this call:
User.find.findRowCount();
... returned the actual number of users with and without accounts so it works like I was expecting both of those queries to work.
So what am I missing there? Is there anything wrong with my models? Why not all users are returned?
[EDIT]
I am attaching a little bit more info.
Executed SQL for:
User.find.findRowCount();
is:
select count(*) from user t0
Executed SQL for:
User.find.all();
is
select t0.id c0, t0.name c1, t0.surname c2, t1.id c3 from user t0 join user_account t1 on t1.user_id = t0.id
It looks to me that inner join is used instead of left join. What is reason for this?
In your User class you use mappedBy = "user" for the column name although in UserAccount class you use name = "user_id".

How to inner join two tables using Hibernate HQL or Criteria?

#Entity
public class doctor {
#Id
private int id;
private String username;
private String password;
private String phone;
private String email;
#OneToMany(targetEntity = patient.class, cascade = CascadeType.ALL, mappedBy = "doctor")
#Cascade(value = org.hibernate.annotations.CascadeType.ALL)
private Collection<patient> patients = new ArrayList<patient>();
}
#Entity
public class patient {
#Id
private int id;
private String name;
private String surname;
private String phone;
private int systolic;
private int diastolic;
#ManyToOne
private doctor doctor;
}
For now i can retreive only doctors information by this criteria:
Criteria query = session.createCriteria(doctor.class);
query.createCriteria("patients", "p");
query.add(Restrictions.eq("p.phone", phone));
List<doctor> doctorList = (ArrayList<doctor>) query.list();
How i can with hibernate criteria retreive by giving patient phone, his doctor information and some patients information?
Something like : phone=3423423424 , then answear is :
-------------doctor's info----------------------------------patientinfo(systolic,diastolic)-----------------------
1 "Dr dre" sdfssd 243242 drdre#gmail.com 160 170
where 160 170 are the patient's information
If not with criteria, with HQL?
What you want is the following.
With Hibernate Criteria API:
Criteria query = sessionFactory.getCurrentSession().
createCriteria(Patient.class, "patient");
query.setProjection(Projections.projectionList().
add(Projections.property("patient.doctor")).
add(Projections.property("patient.systolic")).
add(Projections.property("patient.diastolic")));
query.add(Restrictions.eq("patient.phone", phone));
return query.list();
With HQL (actually just JPQL):
select p.doctor, p.systolic, p.diastolic from Patient p where p.phone = ?1
What you get in the result is value of Type List<Object[]>.
Also add #Cascade(value=CascadeType.SAVE_UPDATE) to doctor field on your Patient class.
You have bidirectional mapping so from each doctor you can get his patients and from each patient you can get his doctor information. If you need a list with patients instead a list of doctors just create analogous Criteria for patients. session.createCriteria(patient.class), with needed restrictions. you don't have to make it eager. In most cases we don't need eager fetching. It is much better initialize (Hibernate.initialize) the collection or proxy if you would need the objects outside the hibernate session.
btw use camel case when you name java classes. It's widely used convention.
The reason why your query only returns the doctors information (and not the patient's information) is because for a OneToMany relation, FetchType by default is set to LAZY, if you specify fetch type to be EAGER, hibernate will also return patients.
#OneToMany(targetEntity = patient.class, cascade = CascadeType.ALL, mappedBy = "doctor", fetch = FetchType.EAGER)
In case you are using HibernateTemplate
String hql = "from Boo where id in (:listParam)";
String[] params = { "listParam" };
Object[] values = { list};
List boos = getHibernateTemplate().findByNamedParam(hql, params, values);
Quoted from Spring Forum

Categories

Resources