Select Count using Criteria builder in a OneToMany Relationship - spring

I'm building a Spring web app and i'm new to JPA and I need to get the number of users in a specific group in my database.
Here is the sample code :
public long countAllUsersByGroup(int groupId) {
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
cq.select(qb.count(cq.from(User.class)));
//cq.where(qb.equal(cq.from(User.class).get("userGroup").get("id"),groupId));
return em.createQuery(cq).getSingleResult();
}
This code is working, it allows me to retrieve the number of users I have in the database which is pretty trivial.
This is my user Model :
public class User {
private String userFirstName;
private String userLastName;
/* some stuff */
#ManyToOne
private Group userGroup;
}
And my group model has an int attribute annotated with #Id and named id. How Can I get the number of user by group id in this case ?
P.S : I've tried and commented my try in the code above, unfortunately it was a failure ...

You can try the code below
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> q = cb.createQuery(Long.class);
ParameterExpression<Integer> p = cb.parameter(Integer.class);
q.select(q.count(q.from(User.class))).where(cb.gt(c.get("userGroup"), someId));

Related

Spring boot hibernate #ManyToMany doesn't commit or returns incomplete data when I execute any method on the junction table from non-owner entity

I'm currently working on a Spring Boot project for an online shop. It's my first project with Spring Boot (and my first post here), so my coding is not the best.
Context for the questions:
My shop (for now) has a lists of products and whishlists of different users (shopping lists), which have a bidirectional #ManyToMany relation (i left here the relevant details for my question(s)):
Product.java entity:
#Entity
public class Product extends RepresentationModel\<Product\>{
#Id
#GeneratedValue
#JsonView(ProductView.DescriptionExcluded.class)
private Integer id;
#ManyToMany()
#JoinTable(
name = "Shopping_Product",
joinColumns = { #JoinColumn(name = "id", referencedColumnName = "id") },
inverseJoinColumns = { #JoinColumn(name = "list_id", referencedColumnName = "list_id") })
#JsonIgnore
private Set<ShoppingList> shoppinglists = new HashSet<>();
// Constructor, getters, setters ....
ShoppingList.java entity:
#Entity
public class ShoppingList {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(ShoppingListView.ProductsExcluded.class)
private Integer list_id;
#JsonView(ShoppingListView.ProductsIncluded.class)
#ManyToMany(mappedBy = "shoppinglists")
private Set<Product> products = new HashSet<>();
// Constructor, getters, setters ...
I chose Product as the owner because i wanted to delete (tho it would be more fit to show something like "offer expired", but I'll stick to delete for now) the product from all existing lists when the admin takes it down from the shop, which works as expected:
ProductResource.java (controller):
#DeleteMapping("/categs/*/sub/*/products/{id}")
public ResponseEntity<String> deleteProduct(#PathVariable int id) {
Optional<Product> optional = productRepository.findById(id);
if(!optional.isPresent()) throw new NotFoundException("Product id - " + id);
Product prod = optional.get();
productRepository.delete(prod);
return ResponseEntity.ok().body("Product deleted");
}
My problems now are related to the ShoppingList entity, which is not the owner.
Any call I make to the Product resource (controller) works as expected, but anything from the other side either fails or returns incomplete results, like the following:
1.
I call retrieve all products from a list and it returns only the first object (the list has at least 2):
ShoppingListResource.java (controller):
#RestController
public class ShoppingListResource {
#Autowired
private ProductRepository productRepository;
#Autowired
private UserRepository userRepository;
#Autowired
private ShoppingListRepository shoppinglistRepository;
#GetMapping("/user/lists/{id}")
public Set<Product> getShoppinglistProducts(#PathVariable int id) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
ShoppingList shoppingList = shoppinglistRepository.findById(id).get();
String name = shoppingList.getUser().getUsername();
if(!Objects.equals(currentPrincipalName, name)) throw new IllegalOperation("You can only check your list(s)!");
// All lists are shown for a product
// Product p = productRepository.findById(10111).get();
// Set<ShoppingList> set = p.getShoppinglists();
// set.stream().forEach(e -> log.info(e.toString()));
// Only first product is shown for a list
return shoppingList.getProducts();
This is what hibernate does on the last row (only returns 1/2 products)
Hibernate: select products0_.list_id as list_id2_3_0_,
products0_.id as id1_3_0_,
product1_.id as id1_1_1_,
product1_.description as descript2_1_1_,
product1_.name as name3_1_1_,
product1_.price as price4_1_1_,
product1_.subcat_id as subcat_i5_1_1_ from shopping_product products0_ inner join product product1_ on products0_.id=product1_.id where products0_.list_id=?
As i said above, I can delete a product and it gets removed automatically from all existing lists, but when i try the same from ShoppingList entity does nothing:
Same controller
#DeleteMapping("/user/lists/{id}")
public ResponseEntity<String> deleteShoppinglist(#PathVariable int id) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
ShoppingList shoppingList = shoppinglistRepository.findById(id).get();
String name = shoppingList.getUser().getUsername();
if(!Objects.equals(currentPrincipalName, name)) throw new IllegalOperation("You can only delete your list(s)!");
shoppinglistRepository.delete(shoppingList);
return ResponseEntity.ok().body("Shopping list deleted");
}
Also, when i try to add/delete product from an existing list, does nothing.
This is my repo with full code, if you'd like to test directly (dev branch is up to date):
https://github.com/dragostreltov/online-store/tree/dev
You can just use admin admin as authentication (on the H2 console too). More details on the readme.
All DB data at app start is inserted from a .sql file.
I checked other similar questions and tried different methods on my ShoppingList entity (on the delete issue), like:
#PreRemove
public void removeListsFromProducts() {
for(Product p : products) {
p.getShoppinglists().remove(this);
}
}
Spring/Hibernate: associating from the non-owner side
And still doesn't work.
UPDATE:
I found out what issues I was having, I'll post an answer with the solution.
For anyone who's got the same/similar problems as I did, this is how I resolved them:
For point 1
(Hibernate only retrieves the first product from a shoppingList (Set))
I made multiple tests on my retrieve method and found out my Set was only containing 1 object, despite calling .add(product) twice.
As you can see, I'm using HashSet for both entities:
In Product (owner):
private Set<ShoppingList> shoppinglists = new HashSet<>();
In ShoppingList (mappedBy):
private Set<Product> products = new HashSet<>();
Thanks to this answer: https://stackoverflow.com/a/16344031/18646899
I learnt:
HashSet (entirely reasonably) assumes reflexivity, and doesn't check for equality when it finds that the exact same object is already in the set, as an optimization. Therefore it will not even call your equals method - it considers that the object is already in the set, so doesn't add a second copy.
In particular, if x.equals(x) is false, then any containment check would also be useless.
Taking this into account, I overwrote the hashCode() and equals() methods in Product.class and now
shoppingList.getProducts()
works as expected.
For point 2
(not being able to delete associations of non-owner entity before deleting the row from it's table)
Added lazy fetch and cascade to Product #ManyToMany:
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH})
And added the following methods:
In Product class:
public void addShoppinglist(ShoppingList list) {
this.shoppinglists.add(list);
list.getProducts().add(this);
}
public void removeShoppinglist(ShoppingList list) {
this.shoppinglists.remove(list);
list.getProducts().remove(this);
}
In ShoppingList class:
public void addProduct(Product product) {
this.products.add(product);
product.getShoppinglists().add(this);
}
public void removeProduct(Product product) {
this.products.remove(product);
product.getShoppinglists().remove(this);
}
Added #Transactional and modified the method inside the controller (ShoppingListResource) for deleteShoppingList:
#RestController
public class ShoppingListResource {
...
#Transactional
#DeleteMapping("/user/lists/{id}")
public ResponseEntity<String> deleteShoppinglist(#PathVariable int id) {
...
shoppingList.getProducts().stream().forEach(e -> {
e.removeShoppinglist(shoppingList);
});
shoppinglistRepository.delete(shoppingList);
return ResponseEntity.ok().body("Shopping list deleted");
}
}
And now this is working as expected, the shoppingList's associations are deleted first then the shoppingList itself.

Query in arraylist by one element in spring boot h2

I have a ClassRoom class, in my Spring Boot application. Here is what it looks like :
public class ClassRoom {
#Id #GeneratedValue Long classRoomID;
ArrayList<User>adminList=new ArrayList<>();
}
And in my ClassRoomRepository class, I have :
public interface ClassRoomRepository extends JpaRepository<ClassRoom,Long> {
#Query("select ClassRoom from ClassRoom c where c.adminList = ?1")
ArrayList<ClassRoom> findByAdminList(ArrayList<User> adminList);
/*
#Query("select ClassRoom from ClassRoom c where c. = ?1")
ArrayList<ClassRoom> findByAdmin(User admin);
*/
}
I can query to select ClassRoom where ArrayList of ClassRoom gets passed parameter.
But I want to query to select ClassRoom where I pass only one User as parameter and returns ArrayList of ClassRoom.(Commented section-nothing done so far)
If it is possible in this interface, how can I do so?
Asuming you habe many-to-many association using join table, this is what you need,
#Query(value = "SELECT DISTINCT cr FROM ClassRoom cr JOIN cr.admins a WHERE a = ?1")
List<ClassRoom> findAllByAdmin(final Admin admin);
When mapping collection , Hibernate requires it to declare it using interface type such as List , Set , Map etc. But you are using ArrayList to declare the admin list , I am guessing the whole list will be stored to a single column with some binary data type in DB . No one would store the data in this funny way when using RDBMS unless you have strong reason to do it.
From what you describe , ClassRoom and User seem to be many to many. For demonstration convenience , I would map it as #ManyToMany:
#Entity
#Table
public class ClassRoom {
#ManyToMany
#JoinTable(name ="classroom_user",
joinColumns = #JoinColumn(name = "classroom_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> admins = new ArrayList();
}
#Entity
#Table
public class User{
#ManyToMany(mappedBy = "admins")
private List<ClassRoom> classRooms = Lists.newArrayList();
}
Then given an user admin ,to get all of his administrative class rooms :
#Query("select distinct cr from ClassRoom cr left join fetch cr.admins admin where admin = ?1 ")
public List<ClassRoom> findByAdmin(User admin);
Or :
#Query("select distinct cr from ClassRoom cr where ?1 member of cr.admins")
public List<ClassRoom> findByAdmin(User admin);

Selecting from Multiple Tables in Spring JPA with Pageable and Sorting

I saw the Selecting from Multiple Tables in Spring Data already had the solution for multiple tables.
I would like to know if it is possible to write custom query that has tables with pageable and sorting feature at the same time in Spring JPA/DATA.
SELECT s.service_id, s.name, us.rating_id
FROM services s,
ratings r,
user_services us
where
us.service_id = s.service_id and
us.rating_id = r.rating_id and
us.user_id= ?
;
Thanks for you help in advance.
Sorting feature is under question, but pagination is possible to use.
Assume that we have:
#Entity
public class Service {
#Id
private Long id;
private String name;
//...
}
#Entity
public class UserService {
#Id
private Long id;
#ManyToOne
User user;
#ManyToOne
Service service;
#ManyToOne
Rating rating;
//...
}
Then we create a projection:
public interface ServiceRating {
Long getServiceId();
String getServiceName();
Long getRatingId();
}
And then create a query method supported pagination:
public interface UserServiceRepo extends CrudRepository<UserService, Long> {
#Query("select s.id as serviceId, s.name as serviceName, us.rating.id as ratingId from UserService us join us.service s where us.user.id = ?1")
Page<ServiceRating> getServiceRating(Long userId, Pageable pageable);
}
(Since this query does not contain grouping it's not necessary to use an additional countQuery (see the parameter of #Query)).
Test:
Page<ServiceRating> pages = userServiceRepo.getServiceRating(1L, new PageRequest(0, 10));
assertThat(pages.getContent()).hasSize(10));
UPDATE
Sorting also working perfectly.
Just create a Sort object, specify direction and filed name (from the projection):
Sort sort = new Sort(Sort.Direction.ASC, "serviceName");
userServiceRepo.getServiceRating(1L, new PageRequest(0, 10, sort));

JPA Bi-directional OneToMany does not give me the desired collection

Im trying to map a oracle db model with JPA entities (Using eclipselink) - i have following simple setup :
table program with primary key id
table multi_kupon with compound primary keys id, spil and foreign key program_id
when i try to fetch program with a simple select i would expect the go get a list
of multi_kupon's but im getting a list of size 0. I've made certain that when i do
a select with joins i get data from both program and multi_kupon.
I believe that its got to do with my relations of the 2 entities - hope somebody can point me to my mistake(s)
Snippet from Entity 'Program' :
#Entity
#Table(name = "PROGRAM", schema = "", catalog = "")
public class Program implements Serializable{
#Id
private Integer id;
private List<MultiKupon> MultiKuponList;
#OneToMany(mappedBy = "program", fetch = FetchType.EAGER)
#JoinColumns({#JoinColumn(name = "id", referencedColumnName = "id"),
#JoinColumn(name = "spil", referencedColumnName = "spil")})
public List<MultiKupon> getMultiKuponList() {
return multiKuponList;
}
Snippet from Entity 'MultiKupon' :
#Entity
#Table(name = "MULTI_KUPON", schema = "", catalog = "")
public class MultiKupon implements Serializable {
private Integer id;
private String spil;
private Program program;
#ManyToOne
public Program getProgram() {
return program;
}
My stateless bean :
#Stateless
public class PostSessionBean {
public Program getProgramById(int programId) {
String programById = "select p from Program p where p.id = :id";
Program program = null;
try {
Query query = em.createQuery(programById);
query.setParameter("id", programId);
program = (Program) query.getSingleResult();
I do get the correcct program entity with data, but the list with
multi_kupon data is size 0
What im i doing wrong here ??
The mapping is incorrect, as you have specified both that the OneToMany is mappedBy = "program" and that it should use join columns. Only one or the other should be used, and since there is a 'program' mapping, I suggest you use:
#OneToMany(mappedBy = "program", fetch = FetchType.EAGER)
public List getMultiKuponList()
And then define the join columns on MultiKupon's ManyToOne mapping to define which fields should be used for the foreign keys to the Program table.
Since you have made this a bidirectional relationship, you must also maintain both sides of your relationship. This means that every time you add a MultiKupon and want it associated to a Program you must both have it reference the Program, AND add the instance to the Program's collection. If you do not, you will run into this situation, where the cached Program is out of sync with what is in the database.
It is much cheaper generally to keep both sides in sync, but if this is not an option, you can correct the issue (or just verify that this is the situation) by calling em.refresh(program). If the mappings are setup correctly, the instance and its list will be repopulated with what is in the database.
According to http://sysout.be/2011/03/09/why-you-should-never-use-getsingleresult-in-jpa/
Try to retrieve first program object of List:
List<Program> pList = query.getResultList();
if(!pList.isEmpty()){
return pList.get(0);
}

Join 3 tables in Spring Jpa Data

I've been struggling lately to join 3 tables with spring data jpa. I have 3 entities, Series, Dossier and Item. Series has many Dossiers, and Dossier has many Items (Relationships). I do something like Series.join(Dossier_.series).join(Dossier_.items) and I end up with a Join set. I want to make the following query:
Select Items from Series,Dossier,Item
Where Series.Id=Dossier.seriesId
and Dossier.id=Item.dossierId
and series.projectId = :param
I can't express this statement with Spring Specifications and criteria api....Please shed some light
It is more a JPA question.
First, I always emphasize, you are not access "tables". You should view them as domain entities. Lot of misuse of JPA/Hibernate/other ORMs actually comes from direct "translate" of SQL or database concepts.
Back to your question, the answer is simple. First make sure you actually have the "relationships" in your domain entities. Storing IDs is not helping to build a concrete domain model. For example, you have something like :
#Entity
class Series {
#Id
Long id;
#OneToMany(mappedBy="series")
List<Dossier> dossiers;
}
#Entity
class Dossier{
#Id
Long id;
#ManyToOne
Series series;
#OneToMany(mappedBy="dossier"
List<Item> items;
}
#Entity
class Item{
#Id
Long id;
#ManyToOne
Dossier dossier;
}
The query is straight-forward:
select s.dossiers.items from Series s where s.projectId = :param
Or, if it is more reasonable to have only the #ManyToOnes and omit the #OneToManys, the query is still straight-forward:
from Item where i.dossier.series.projectId = :param
[Still rocky here] Maybe i didn't make myself clear.I know how to express the query in HQL.The problem is to use Spring Data's Specifications,with the help of the criteria api to build that query.
//Let's exampine the following piece of code
public class CustomItemSpecs {
public static Specification<Item> createSpecificationFromSearchForm(final SearchForm searchForm) {
return new Specification<Item>() {
#Override
public Predicate toPredicate(Root<Item> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
CriteriaQuery<Item> cq = cb.createQuery(Item.class);
CriteriaQuery<Series> sb = cb.createQuery(Series.class);
Root<Series> series = sb.from(Series.class);
Join<Series,Dossier> join1 = series.join(Series_.dossiers);
Join<Dossier, Item> join2 = join1.join(Dossier_.items);
}
}
}
As you can see i manage to do two seperate joins.The problem is when i want to join Series,Dossier And Items to execute the above query.Notice that join2 is a Dossier-Item set.I can't make criteria like cb.equals(join2.get(Series_.projectId),)
im using spring data interface projection .
example like this
note items must be interface class
Repository Class
#Repository
public interface ItemRepository extends JpaRepository<Items,Long> {
#Query(nativeQuery = true,value = "Select Items from Series,Dossier,Item Where Series.Id=Dossier.seriesId and Dossier.id=Item.dossierId and series.projectId = :param")
public Items findTransaksisByAccountIdOrderById(#Param("param") Long projectId);
}

Resources