How to use hibernate search criteria on parameter with set - java

I have several entities where one of them has a parameter with Set containing other entities from many-to-many join like this (I am using only two of them for simplicity).
How can I use search criteria to filter out Users who have Avatar with id==1?
#Entity
public class User
{
#Id
private String loginId;
private String screenName;
#ManyToMany(mappedBy = "user")
private Set<Avatar> avatars;
}
#Entity
public class Avatar
{
#Id
private Integer id;
#ManyToMany
#JoinTable(
name = "user",
joinColumns = #JoinColumn(name = "id"),
inverseJoinColumns = #JoinColumn(name = "loginId"))
private User user;
private String url;
}

You can use a native query in order to obtain what you want.
The syntax will look like this:
#Query(
value = "select * from ...",
nativeQuery = true
)
Maybe you can refactor the tables from database without affecting the business logic. I don't know what requirements do you have but I think you can use #ManyToOne.

You can use root.join to achieve this.
int avatarId = 1;
CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);
Root<User> user = query.from(User.class);
criteriaBuilder.and(criteriaBuilder.equal (
user.join("avatars").get("id"), avatarId));

Related

How do I select information from ManyToMany Table in JPA?

I have two entities mapped Board and Tag by #ManyToMany to a join table board_tag_table.
How would I return the top 5 most common tag_id in the board_tag_table?
enter image description here
public class Board {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinTable(name = "board_tag_table",
joinColumns = {
//primary key of Board
#JoinColumn(name = "id", referencedColumnName = "id")
},
inverseJoinColumns = {
//primary key of Tag
#JoinColumn(name = "tag_id", referencedColumnName = "tag_id")
})
private Set<Tag> tags = new HashSet<>();
}
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer tag_id;
private String tagname;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "tags")
private Set<Board> boards = new HashSet<>();
}
Unable to find how to query within a many to many table
you can pass through foreach and write your query in Tag repository, but I think you can't write query, because they are have list from two sides
Consider using a native query instead.
If you want to use the JPA, you can add a field (Eg. usedCount) in the Tag entity and follow the instructions here https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.limit-query-result.
The query should look like this:
List<Tag> findByUsedCount(Sort sort, Pageable pageable);
Don't look at it as trying to access the board_tag_table, and instead look at how you would do this with the java entities themselves. This would be just selecting the 5 top Tags based on the number of boards they have. "select t.tag_id, count(b) as boardCount from Tag t join t.boards b group by t.tag_id order by boardCount", then use maxResults to limit the returned values to 5

Spring Boot entity many-to-many native query

I have category class as list in entity. How can I fill this entity with a native query?
Product.java
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer pid;
private String name;
#ManyToMany
#JoinTable(
name = "product_category ",
joinColumns = #JoinColumn(
name = "pro_id", referencedColumnName = "pid"),
inverseJoinColumns = #JoinColumn(
name = "cat_id", referencedColumnName = "cid"))
private List<Category> Categories;
}
Category.java
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long cid;
private String catname;
private String desc;
#ManyToMany(mappedBy = "categories")
private List<User> users;
}
How should I write a query? How can I fill the Categories list
#Query(value = "*****", nativeQuery = true)
List<Product> productList();
Instead of native query you can use hibernate queries like the below to find something useful.
Find all
ProductRepository.findAll();
Hibernate query
#Query(select product from Product product join product.categories categories)
List<Product> getAllProducts();
you can also sort the above queries using Pageable Object.
Could you please elaborate your question. As you can get the collection list if it is mapped just by find all function of Product Repository.
However, if you are looking to insert collection you can use cascade type in Categories field of Product Repository. This will automatically insert the categories entity once you save the Product entity to database by setting categories list in Product entity.

#Query did not match expected type [java.util.Collection (n/a)]

I have a table named Tasks with following columns in it: author, responsible and observers all of them are FKs of user ids which are located in Users table. And I have the table representing many-to-many relation between Users and Tasks which contains only two columns: task_id and user_id. I am using #Query from Spring boot to select all the rows from Tasks. My issue is that I want to select all the tasks in which currently authorized user is being observer.
My task model:
#Entity
#Table
public class Task {
private String title;
private String description;
#Column(name = "delete_status")
private Boolean deleteStatus;
#OneToOne
#JoinColumn(name = "task_status_id")
private TaskStatus status;
#OneToMany(mappedBy = "task")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Comment> comments;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
#Column(name = "due_date")
private Date dueDate;
#ManyToOne
#JoinColumn(name = "author_id", updatable = false)
private User author;
#ManyToOne
#JoinColumn(name = "responsible_id")
private User responsible;
#JsonManagedReference
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "observers_users",
joinColumns = {#JoinColumn(name = "observers_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "users_id", referencedColumnName = "id")})
#LazyCollection(LazyCollectionOption.FALSE)
private List<User> observers;
}
My query:
#Query("FROM Task t WHERE t.observers = ?1 ORDER BY t.id DESC")
List<Task> findWhereUserObserver(
#Param("observers") List<User> user
);
No matter how I change the query, I keep getting the same error: Parameter value [User (...)] did not match expected type [java.util.Collection (n / a)]
Because t.observers is type of List I can't figure out how to use it within SQL query in WHERE condition. I want to find my currently authorized user in this list but don't understand how should I do so in SQL query. Or should I at all?
Well when you say sth. like T.OBSERVERS IN it means that you will have more than one observer in a list such as IN (User1, User2, User3) etc. But you only have 1 User. Checkout SQL IN statement https://www.w3schools.com/sql/sql_in.asp
I know it's a little bit too late, but...
You have to search through the collection T.OBSERVERS via left join. This is what to do:
"SELECT t FROM Task t LEFT JOIN t.observers o where o.id= ?1 ORDER BY t.id DESC")

Hibernate/JPQL: How to load parent with all childs based on query on a child

So we have a service with simplified these two entities
#Entity
public class Ticket {
/* simplified*/
#OneToMany(fetch = FetchType.LAZY, mappedBy = "ticket", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Grant> grants = new HashSet<>();
}
#Entity
public class Grant {
/* simplified*/
#NotNull
#ManyToOne(fetch = LAZY)
#JoinColumn(name = UsageGrant.FK_TICKET, nullable = false)
private Ticket ticket;
#NotNull
#Column(name = "specialNumber", nullable = false)
private Integer specialNumber;
}
I'd like to have a query that selects all tickets that contain a grant with a specific "specialNumber". The catch is that I want to have the ticket returned with all grants, not only the one matching. I tried it with
#Repository
public interface TicketRepository extends JpaRepository<Ticket, String> {
#Query("SELECT DISTINCT ti FROM Ticket ti JOIN FETCH ti.grants g WHERE
g.specialNumber = :specialNumber "
)
List<Ticket> findBySpecialNumberAndLoadAllGrantsOnTicket(
#NotNull #Param("specialNumber") Integer specialNumber);
}
but this gives me just the matching one. Do I need to split it up into two queries? Criteria API also doesn't help, because RIGHT JOIN is also not supported there.
Update
I can achieve it with
SELECT g FROM Grant g LEFT JOIN FETCH g.ticket ti JOIN FETCH ti.grants WHERE g.specialNumber = :specialNumber
and accessing the ticket with g.getTicket(). The resulting query looks crazy and I'm not sure if this is a clever approach at all.
You can use #EntityGraph for fetch grants and query using JPA method for select by specialNumber
#EntityGraph(attributePaths = {"grants"})
public List<Ticket> findByGrantsSpecialNumber(Integer specialNumber);
Or
You can use #NamedEntityGraph
#NamedEntityGraph(name = "Ticket.Grants", attributeNodes = { #NamedAttributeNode("grants") })
public class Ticket {
And
#EntityGraph(value = "Ticket.Grants", type = EntityGraphType.LOAD)
public List<Ticket> findByGrantsSpecialNumber(Integer specialNumber);

JPA persist many to many

I have a pretty standard scenario whereby I have a table of Users with user_id as the PK and a table of Roles with role_id as the PK. The two tables are related via a many to many relationship (ie. Users can have many roles and a role can be applied to many users) and subsequently I have a joining table called users_has_roles. The only two columns in users_has_roles are users_user_id and roles_role_id.
I have generated the entity classes (see below) and I have no problem persisting data to the users and roles tables but I have failed miserably persist anything to the users_has_roles joining table so currently none of my users are being assigned a role. Before I go crazy could somebody put me out of my misery and show me how I should go about adding a users_user_id with a corresponding roles_role_id to the users_has_roles table so my users can have roles?
My Users.java entity class:
#Entity
#Table(name = "users")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Users.findAll", query = "SELECT u FROM Users u"),
#NamedQuery(name = "Users.findByUserId", query = "SELECT u FROM Users u WHERE u.userId = :userId"),
#NamedQuery(name = "Users.findByUsername", query = "SELECT u FROM Users u WHERE u.username = :username"),
#NamedQuery(name = "Users.findByPassword", query = "SELECT u FROM Users u WHERE u.password = :password")})
public class Users implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 60)
#Column(name = "user_id")
private String userId;
#Basic(optional = false)
#NotNull
#Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", message="Invalid email")
#Size(min = 1, max = 45)
#Column(name = "username")
private String username;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 120)
#Column(name = "password")
private String password;
#JoinTable(name = "users_has_roles", joinColumns = {
#JoinColumn(name = "users_user_id", referencedColumnName = "user_id")}, inverseJoinColumns = {
#JoinColumn(name = "roles_role_id", referencedColumnName = "role_id")})
#ManyToMany
private Collection<Roles> rolesCollection;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "usersUserId")
private Collection<UserAccount> userAccountCollection;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "usersUserId")
private Collection<UserDetails> userDetailsCollection;
...
All the getter and setter methods etc.
My Roles.java entity class:
#Entity
#Table(name = "roles")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Roles.findAll", query = "SELECT r FROM Roles r"),
#NamedQuery(name = "Roles.findByRoleId", query = "SELECT r FROM Roles r WHERE r.roleId = :roleId"),
#NamedQuery(name = "Roles.findByRoleName", query = "SELECT r FROM Roles r WHERE r.roleName = :roleName"),
#NamedQuery(name = "Roles.findByRolePermission", query = "SELECT r FROM Roles r WHERE r.rolePermission = :rolePermission"),
#NamedQuery(name = "Roles.findByRoleDescription", query = "SELECT r FROM Roles r WHERE r.roleDescription = :roleDescription")})
public class Roles implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 60)
#Column(name = "role_id")
private String roleId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 45)
#Column(name = "role_name")
private String roleName;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 45)
#Column(name = "role_permission")
private String rolePermission;
#Size(max = 45)
#Column(name = "role_description")
private String roleDescription;
#ManyToMany(mappedBy = "rolesCollection")
private Collection<Users> usersCollection;
...
All the getter and setter methods etc.
Thanks
---- UPDATE ----
// New Users
Users currentUser = new Users();
currentUser.setUserId(userId);
currentUser.setUsername(email);
currentUser.setPassword(password);
getUsersFacade().create(currentUser);
Ok first off thanks to Mikko for leading me to the answer. I just wanted to post an answer that might be directly helpful to anybody else that might be in the position I was in. Also this is based on a Eureka moment so it might not be technically correct but this is how I see it.
The big issue that I faces was that in MySQL I could see the bridging table as an individual table! (sorry I can't post an image of my EER diagram but I don't seem to have enough privileges at the moment) So I assumed that Java would also see the bridging table as a table! Well it doesn't. That bridging table doesn't really exist in Java as a conventional table it is in fact represented by the opposing tables collection type that you associate with it.
The easiest way to see it for me was to completely forget the bridging table and concentrate on the two 'real' tables and associating the data in those. The following code is NOT best practice as I'm simply setting the role_id but it's fine just to show my point.
List<Roles> userRoleList = new ArrayList<Roles>();
Users currentUser = new Users();
currentUser.setUserId(userId);
currentUser.setUsername(email);
currentUser.setPassword(password);
Roles userRole = new Roles();
userRole.setRoleId("2");
userRoleList.add(userRole);
currentUser.setRolesCollection(userRoleList);
getUsersFacade().create(currentUser);
Hope that helps anybody else that is struggling with many to many relationships.
(NB. I've edited the original question code to use a List instead of a Collection for ease but you can just as well use any other type that fits your needs.)
Your example works fine (EclipseLink 2.3, MySQL). Likely problem is in part of the code that you do not show. For example in adding element to rolesCollection. Typical mistake is for example to add element only to the non owning side.
For persisting it you have to keep care about relation in owning side (one without mappedBy), for keeping also in-memory object graph consistent with database, you should always modify both sides of relation.
I tried it with following:
// method to add element to rolesCollection
public void addRoles(Roles r) {
rolesCollection.add(r);
}
//first created new instances of Users and Roles
// then:
tx.begin();
users.addRoles(r);
//it is not needed for persisting, but here you can also
//add user to roles.
em.persist(users);
em.persist(r);
tx.commit();

Categories