Spring Boot 2 - JPA + Pagable problem on ManyToMany tables - spring-boot

In Spring Boot 2 JPA, I have the following two many to many Entities.
1- Labor:
#Entity
public class Labor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(length = 100, nullable = false)
private String name;
#Column(length = 50)
private String mobile;
private Date dateOfBirth;
private boolean male;
private boolean active;
private String brife;
#Column(length = 500)
private String specifcation;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "labor_tag",
joinColumns = #JoinColumn(name = "labor_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id"))
private Set<Tag> tags = new HashSet<>();
}
and Tag table:
#Entity
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(length = 100, unique = true)
private String name;
private boolean active = true;
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "tags")
#JsonIgnore
private Set<Labor> labors = new HashSet<>();
}
Then I defined Labor Repository to query Labors with certain Tag ID ,gender, or ages
#Repository
public interface LaborReoistory extends JpaRepository<Labor, Long> {
#Query("select l from Labor l join l.tags t where (:tid is null or t.id in :tid) and " +
"(:isMale is null or :isMale = TRUE) and " +
"((:startYear is null or :endYear is null or :startYear > :endYear) or year(l.dateOfBirth) >= :startYear and year(l.dateOfBirth) <= :endYear)")
Page<Labor> findLaborsByCondition(#Param("tid") final long[] tid,
#Param("isMale") final Boolean isMale,
#Param("startYear") final Integer startYear,
#Param("endYear") final Integer endYear,
final Pageable pageable);
}
When I use this repository in my controller, I find the totalElements property of the Pagable returned counts to records in labor_tag(in this case 16 records),but what I actually want is to have totalElements count on Labors with given conditions. does JPA Pagable support such query or how can I find a workaround?
Thanks

After joining there will be duplicate Labor but totalElements is the count of total number of row using the query. So you should use Distinct on Labour to get the count of distinct Labour
#Query("select distinct l from Labor l join l.tags t where (:tid is null or t.id in :tid) and " +
"(:isMale is null or :isMale = TRUE) and " +
"((:startYear is null or :endYear is null or :startYear > :endYear) or year(l.dateOfBirth) >= :startYear and year(l.dateOfBirth) <= :endYear)")

Related

Trying to Convert a SQL Query to a JPA Query

I want to convert this sql query to a JPA query, but I can't seem make sense of it... Should I use findByMarinaIdAndMovementGroupMeanId?? or findByMarinaIdAndMovementGroupMeanIdAndMovementMeanId??
Sql:
select m.* from movement_group m
join movement_group_mean mgm on m.id = mgm.movement_group_id
join movement_mean mm on mgm.movement_mean_id = mm.id
where mm.id = 1 and m.marina_id = :marinaId and mm.active = true;
MovementGroup:
#Entity
public class MovementGroup {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String code;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private Boolean active;
private String iconUrl;
#OneToMany(mappedBy = "movementGroup")
private Set<MovementGroupMean> movementGroupMeans;
#JsonIgnore
#ManyToOne()
#JoinColumn(name = "marina_id")
private Marina marina;
MovementGroupMean:
#Entity
public class MovementGroupMean {
#EmbeddedId
#JsonIgnore
private MovementGroupMeanPK movementGroupMeanPK;
#JsonBackReference
#ManyToOne
#JoinColumn(name = "movement_group_id", insertable = false, updatable = false)
private MovementGroup movementGroup;
#ManyToOne
#JoinColumn(name = "movement_mean_id", insertable = false, updatable = false)
private MovementMean movementMean;
MovementMean:
#Entity
public class MovementMean {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#Enumerated(EnumType.STRING)
private MovementMeanType movementMeanType;
private Boolean active;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
#JsonBackReference
#ManyToOne()
#JoinColumn(name = "marina_id")
private Marina marina;
Not sure where the problem lies, so excuse the lengthy explanation on SQL->JPQL:
Replace your table names with your entity names
movement_group -> MovementGroup
Replace your joins with the java references, letting JPA use the relationship mapping you've defined instead.
"join movement_group_mean mgm on m.id = mgm.movement_group_id" becomes "join m.movementGroupMeans mgm"
"join movement_mean mm on mgm.movement_mean_id = mm.id becomes "join mgm.movementMean mm"
Only tricky spot is your entities do not define a basic mapping for the marina_id value. So to get at m.marina_id, you will have to use the 'marina' reference and use its presumably ID value:
"m.marina_id = :marinaId" -> "m.marina.id = :marinaId"
Giving you JPQL:
"Select m from MovementGroup m join m.movementGroupMeans mgm join mgm.movementMean mm where mm.id = 1 and m.marina.id = :marinaId and mm.active = true"

In Hibernate how to ignore associated table in selection

I am using spring boot and Hibernate and I have two tables order and orderdetails and I need two queries, first I want to select records in order along with records that relate to that order in order detail table which works fine, but when I want to select only records from order table I can't because it will bring all associated records for each order in the order detail(just like the first one). How I can select records from the order without selecting related records in order detail table?
I also tried to use #JsonIgnore annotation but it doesn't work like what I want.
My entity classes
orders
#Entity
#Setter
#Getter
public class Orders {
#Id
#NotNull
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "order_id")
private Long orderId;
#Column(name = "user_id")
private Long userId;
#Column(name = "user_name")
private String userName;
#Column(name = "created_date", columnDefinition = "DATE")
#JsonFormat(pattern = "MM/dd/yyyy")
private LocalDate createdDate;
private String status;
private float amount;
#Transient
String error;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "order_id")
private List<OrderDetail> orderDetails;
}
order detail
#Entity
#Setter
#Getter
public class OrderDetail {
public OrderDetail() {
}
public OrderDetail(Long productId, String productName, int quantity, float unitPrice, float subtotalPrice){
this.productId = productId;
this.productName = productName;
this.quantity = quantity;
this.unitPrice = unitPrice;
this.subtotalPrice = subtotalPrice;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "order_detail_id", nullable = false)
Long orderDetailId;
#Column(name = "product_id")
Long productId;
#Column(name = "product_name")
String productName;
int quantity;
#Column(name = "unit_price")
float unitPrice;
#Column(name = "subtotal_price")
float subtotalPrice;
}
Queries that I tried:
#Query(value = "select orders.order_id, orders.amount, orders.created_date, orders.status, orders.user_id, " +
"orders.user_name from orders where orders.user_id = :userid", nativeQuery = true)
List<Orders> selectAllOrdersOfSpecificUser(#Param("userid") Long userId);
==================================================================================
List<Orders> findAllByUserId(Long userId);
Both of them bring all the records from the orders table and associated records in the order detail table.
FetchType.EAGER will retrive all the orderdetails as well. Use FetchType.LAZY and create a special JOIN FETCH query for getting orderdetails with orders.

How to write custom findAll() with Specification in JPA repository

When I use skuRepository.getAll(), it works OK, but when I apply filters, defined in Specification (List filteredRegs = skuRepository.getAll(specification)) I still get all the rows of the table
What should i do to apply the specifications to my custom method?
public interface SkuRepository extends CrudRepository<Sku, Integer>, JpaSpecificationExecutor<Sku> {
#Query("select s from Sku s join fetch s.unit un join fetch s.supplier sup WHERE un.id = sku_unit_id AND sup.id = supplier_id")
List<Sku> getAll(#Nullable Specification<Sku> var1);
#Query("select s from Sku s join fetch s.unit un join fetch s.supplier sup WHERE un.id = sku_unit_id AND sup.id = supplier_id")
List<Sku> getAll();
}
UPD:
Here is my entities.
When I make sampling by a Sku table using the Specification API, I see three separate selects in log: one for Sku entity, one for Unit and one for Suppliers. I want my app to make one select with joins.
I read that this is due to the fact that I use EAGER fetch type, so I change it to LAZY, but then I got another problem: "InvalidDefinitionException: No serializer found..." This is logical because related entities Unit and Supplier are not loaded.
Then I decided to write my custom getAll() with request:
#Query("select s from Sku s join fetch s.unit un join fetch s.supplier sup WHERE un.id = sku_unit_id AND sup.id = supplier_id ORDER BY s.name")
But now it does not support Specification.
Please advise what to do.
#Entity
#Table(name = "sku")
public class Sku implements Cloneable, CloneableEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "sku_code", length = 6, nullable = false, unique = true)
private String code;
#Column(name = "sku_name", nullable = false)
private String name;
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "sku_unit_id", nullable = false)
private Unit unit;
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "supplier_id", nullable = false)
private Supplier supplier;
#Column(name = "qty_in_sec_pkg")
private int quantityInSecondaryPackaging;
#Column(name = "sku_is_active", nullable = false)
private boolean isActive;
//constructors, getters, setters
}
#Entity
#Table(name = "units")
public class Unit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id ")
private int id;
#Column(name = "unit", nullable = false, unique = true)
private String unit;
#Column(name = "description")
private String description;
//constructors, getters, setters
}
#Entity
#Table(name = "suppliers")
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id ")
private int id;
#Column(name = "supplier_code", length = 6, nullable = false, unique = true)
private String supplierCode;
#Column(name = "supplier_name", nullable = false)
private String name;
#Column(name = "create_date", length = 19, nullable = false)
private String createDate;
#Column(name = "update_date", length = 19)
private String updateDate;
//constructors, getters, setters
}
You can't mix #Query and Specification
You can only use JpaSpecificationExecutor interface methods to use Specification.
Find more details here

JPA Join query inside #Query

i have three model class there are - User, Menu, Sub-menu.
#Data
#Entity
#Table(name = "CBR_USER")
public class User {
#Id
#GeneratedValue
#Column(name = "CBR_USER_ID")
private Integer cbrUserId;
#Column(name = "LOG_IN_ID")
private String logInId;
private String userId;
private String password;
#Column(name = "FULL_NAME")
private String FULL_NAME;
private String EMAIL;
private String PHONE;
private Integer ROLE_ID;
private String DESIGNATION;
private String branchId;
private Integer IS_VALID;
#ManyToMany(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "Conf_menu_Access", joinColumns = #JoinColumn(name = "CBR_USER_ID"), inverseJoinColumns = #JoinColumn(name = "id"))
private List<Menu> menuList;
}
Menu class is :
#Data
#Entity
#Table(name = "CONF_MENU")
public class Menu {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_menu")
#SequenceGenerator(name = "seq_menu", sequenceName = "seq_menu", allocationSize = 1)
private Integer id;
private String name;
private String url;
private Integer accessBy;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "menuId")
private List<SubMenu> menuList;
}
and sub-menu class is
#Data
#Entity
#Table(name = "conf_sub_menu")
public class SubMenu {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_sub_menu")
#SequenceGenerator(name = "seq_sub_menu", sequenceName = "seq_sub_menu", allocationSize = 1)
private Integer id;
#Column(name = "MENU_ID")
private Integer menuId;
private String name;
private String url;
}
after compile my code it's generate another mapping table name as
Conf_menu_Access
this table map user access able menu , it's define in User class.
now i need to implement a sql query which is
SELECT ID ,NAME,
CASE
WHEN (SELECT ID FROM CONF_MENU_ACCESS WHERE CBR_USER_ID = 150 AND ID = CMA.ID )>0 THEN 1
ELSE 0
END AS ACCESSBY
FROM CONF_MENU CMA ORDER BY ID ASC
i want to write this query inside #Query tag, any one can help me how to do this......
You can use
#Query(value = "SELECT ID ,NAME,
CASE
WHEN (SELECT ID FROM CONF_MENU_ACCESS WHERE CBR_USER_ID = 150 AND ID = CMA.ID )>0
THEN 1 ELSE 0
END AS ACCESSBY
FROM CONF_MENU CMA ORDER BY ID ASC", nativeQuery = true)
It might be that column name which you are using it should match the column name in the database and the error which is coming is may be that the column name you are passing is of entity field.

SQL to JPQL, How to query Nested JPQL

I wonder if JPQL can be nested query. I am studying Spring Data JPA, and I also have uploaded several related questions.
If I have below sql in MySQL, how do I produce JPQL:
select
c.*
from
cheat c
left join (select * from cheat_vote where val = 1) v on c.cheat_seq = v.cheat_fk
group by
c.cheat_seq
having
count(*) < 10
limit 5
I have two entities.
public class Cheat implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "cheat_seq", length = 10)
private Long cheatSeq;
#Column(name = "question", unique = true, nullable = false)
private String question;
#Column(name = "answer", unique = true, nullable = false)
private String answer;
#Column(name = "writer_ip", nullable = false)
private String writerIP;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "reg_date", nullable = false)
private Date regDate;
#Transient
private String regDateText;
#OneToMany(mappedBy = "cheat", fetch=FetchType.LAZY)
private Set<CheatVote> vote;
#Override
public String toString() {
return "Cheat [cheatSeq=" + cheatSeq + "]";
}
}
Above entity has a #OneToMany collection, and the collection entity is below.
public class CheatVote implements Serializable{
private static final long serialVersionUID = 1L;
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Id
#Column(name="seq", nullable=false)
private Long seq;
#Column(name="val", nullable=false)
#NonNull
private Integer value;
#Column(name="ip_address", nullable=false)
#NonNull
private String ipAddress;
#JoinColumn(name="cheat_fk", referencedColumnName="cheat_seq")
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
#NonNull
private Cheat cheat;
#Override
public String toString() {
return "CheatVote [seq=" + seq + "]";
}
}
I want to get Cheat entitiy which has less than 10 children CheatVote entities.
You can try it:
#Query("SELECT c FROM Cheat c LEFT JOIN c.vote v WHERE v.value = 1 GROUP BY c.cheatSeq HAVING count(c) < 10")
About 'LIMIT' you can use parameter Pageable of Spring Data JPA

Resources