How to write a spring boot jpa specification joining multiple tables - java

I want to write below query using spring boot specification.
SELECT o.*
from orders as o
inner join user as u on o.user_id = u.id
inner join user_group as ug on u.user_group_id = ug.id
left join order_product op on o.id = op.order_id
left join mobile_order_product mop on op.id = mop.order_product_id
left join mobile_device as md on mop.mobile_device_id = md.id
left join tablet_order_product top on op.id = top.order_product_id
left join tablet_device as td on top.tablet_device_id = td.id
where ug.id = 1
and (md.imei = 123456789 or td.imei = 123456789)
I try to write specification like below but I couldn't find a way to join order_product table.
public static Specification<Order> filterOrdersByGroupIdAndImei(int userGroupId, int imei) {
return (root, query, cb) -> {
Join<Object, User> user = root.join("user");
Join<Object, UserGroup> userGroup = user.join("userGroup");
// how to join order_product and other join tables
Predicate equalPredicate = cb.equal(userGroup.get("id"), userGroupId);
return cb.and(equalPredicate);
};
}

I am going to put answer in my own question.
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "id", nullable = false)
#JsonIgnore
private User user;
#OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderProduct> orderProducts ;
}
#Entity
#Table(name = "order_product")
public class OrderProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "id", nullable = false)
#JsonIgnore
private Order order;
#OneToMany(mappedBy = "orderProduct", fetch = FetchType.LAZY)
private List<MobileOrderProduct> mobileOrderProducts;
#OneToMany(mappedBy = "orderProduct", fetch = FetchType.LAZY)
private List<TabletOrderProduct> tabletOrderProducts;
}
#Entity
#Table(name = "mobile_order_product")
public class MobileOrderProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String mobileCode;
private String mobileNumber;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "id", nullable = false)
#JsonIgnore
private MobileDevice mobileDevice;
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
#JoinColumn(referencedColumnName = "id", nullable = false)
private OrderProduct orderProduct;
}
#Entity
#Table(name = "mobile_device")
public class MobileDevice {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String serialNumber;
private String imei;
#OneToMany(mappedBy = "mobileDevice", fetch = FetchType.LAZY)
#JsonIgnore
private List<MobileOrderProduct> mobileOrderProducts;
}
Here I only included couple of my entity class because then you can understand the table structure correctly
public static Specification<Order> filterOrdersByGroupIdAndImei(int userGroupId, String imei) {
return (root, query, cb) -> {
List<Predicate> list = new ArrayList<Predicate>();
Join<Order, User> user = root.join("user");
Join<User, UserGroup> userGroup = user.join("userGroup");
Join<Order, OrderProduct> orderProduct = root.join("orderProducts", JoinType.INNER);
Join<OrderProduct, MobileDevice> mobileDevice = orderProduct
.join("mobileOrderProducts", JoinType.LEFT)
.join("mobileDevice", JoinType.LEFT);
Join<OrderProduct, TabletDevice> tabletDevice = orderProduct
.join("tabletOrderProducts", JoinType.LEFT)
.join("tabletDevice", JoinType.LEFT);
list.add(cb.equal(userGroup.get("id"), userGroupId));
list.add(cb.or(cb.equal(mobileDevice.get("imei"), imei), cb.equal(tabletDevice.get("imei"), imei)));
Predicate[] p = new Predicate[list.size()];
return cb.and(list.toArray(p));
}

Related

Associated entity with OneToOne mapping coming in as null - Hibernate

I have two entities: TableA and TableB. When I fetch TableA, TableB is always null. What am I doing wrong?
This is how I am getting it:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<TableA> criteriaQuery = builder.createQuery(TableA.class);
Root<TableA> from = criteriaQuery.from(TableA.class);
criteriaQuery.select(from);
This is the Query made:
select generatedAlias0 from TableA as generatedAlias0
TableA:
#Entity
#Table(name = "TableA")
public class TableA implements Serializable {
#Id
#Column(name = "id", nullable = false)
private Integer id = null;
#Id
#Column(name = "active", nullable = false)
private Integer active = null;
private Integer parentId = null;
private String myColA = null;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "id", referencedColumnName = "parentId"),
#JoinColumn(name = "active", referencedColumnName = "active")}
)
#Where(clause = "active=1")
private TableB TableB = null;
private Boolean ignorePlanCutoff = null;
}
TableB:
#Entity
#Table(name = "TableB")
public class TableB implements Serializable {
#Id
#Column(name = "id", nullable = false)
private Integer id = null;
#Id
#Column(name = "active", nullable = false)
private Integer active = null;
private Integer parentId = null;
private Boolean colB = null;
}
Both these entities have composite IDs, not showing that here for brevity.

How to join multiple columns using Specification in JPA?

I'm trying to convert a query into a JPA Specification, the query contains JOIN operation with OR condition.
Here is the Query :
select u from User u inner join Operation o on (u.id = o.verificateur1 or u.id = o.verificateur2) where o.id not in (:ids)
I tried to write a specification but I'm blocked on how to join multiple column with OR condition.
public class UserSpecification {
public static Specification<User> UsersNotInSelectedOperations(final List<Long> operationId ){
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Join<User, Operation> userJoinOp = root.join("fk_user1_id");
final Path<User> users = userJoinOp.get("id");
return criteriaBuilder.not(users.in(operationId));
}
};
}
}
the User entity :
#Entity
#Table(name = "aigle_user")
public class User extends AbstractEntity implements UserDetails {
private static final long serialVersionUID = 2840226091237599675L;
#Column(name = "mail", nullable = true)
private String mail;
#Column(name = "password")
private String password;
#Column(name = "is_activated")
private boolean isActivated;
#Column(name = "is_admin")
private boolean isAdmin;
#ManyToMany(cascade = { CascadeType.PERSIST })
#LazyCollection(LazyCollectionOption.FALSE)
#JoinTable(name = "aigle_group_user", joinColumns = #JoinColumn(name = "fk_user_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "fk_group_id", referencedColumnName = "id"))
private Set<Group> groups;
#ManyToMany(cascade = { CascadeType.PERSIST })
#LazyCollection(LazyCollectionOption.FALSE)
#JoinTable(name = "aigle_role_user", joinColumns = #JoinColumn(name = "fk_user_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "fk_role_id", referencedColumnName = "id"))
private Set<Role> roles;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "destinataire", orphanRemoval = true)
private Set<Tache> taches;
#Column(name = "last_name", unique = false)
private String lastName;
#Column(name = "first_name", unique = false)
private String firstName;
#Column(name = "telephone")
private String telephone;
private String salesforceId;
-----
}
The Operation Entity
public class Operation extends OperationField {
...
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "fk_user1_id")
private User verificateur1;
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "fk_user2_id")
private User verificateur2;
.....
}
I expect a Specification that replace the query above
You can try to use a subquery instead of the join expression
public static Specification<User> UsersNotInSelectedOperations(final List<Long> operationIds) {
return (root, query, builder) -> {
Subquery<Operation> subquery = query.subquery(Operation.class);
Root<Operation> operation = subquery.from(Operation.class);
Predicate predicate1_1 = builder.equal(operation.get("verificateur1").get("id"), root.get("id"));
Predicate predicate1_2 = builder.equal(operation.get("verificateur2").get("id"), root.get("id"));
Predicate predicate1 = builder.or(predicate1_1, predicate1_2);
Predicate predicate2 = operation.get("id").in(operationIds).not();
subquery.select(operation).where(predicate1, predicate2);
return builder.exists(subquery);
};
}
Using this specification you get an HQL query like this
from User u
where exists( // subquery ->
from Operation o
where (o.verificateur1.id = u.id or o.verificateur2.id = u.id) // predicate 1
and o.id not in (:ids) // predicate 2
// <- subquery
)
from User u is a shorter replacement for select u from User u
I wrote this code without running, so it needs a revision

Join queries with JPQL in Spring Data Jpa

I created a left join query with JPQL in spring data jpa but failed in my unit test. There are two entities in the project.
Product entity:
#Entity
#Table(name = "t_goods")
public class Product implements Serializable {
#Id
#GeneratedValue
#Column(name = "id", length = 6, nullable = false)
private Integer id;
#Column(name = "name", length = 20, nullable = false)
private String name;
#Column(name = "description")
private String desc;
#Column(name = "category", length = 20, nullable = false)
private String category;
#Column(name = "price", nullable = false)
private double price;
#Column(name = "is_onSale", nullable = false)
private Integer onSale;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "brand_id")
private Brand brand;
// getter and setter
}
Brand entity:
#Entity
#Table(name = "tdb_goods_brand")
public class Brand implements Serializable {
#Id
#GeneratedValue
#Column(name = "id", length = 6, nullable = false)
private Integer id;
#Column(name = "brand_name", unique = true, nullable = false)
private String name;
#OneToMany(mappedBy = "brand", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Product> products;
// getter and setter
}
And a third class Prod to map the query results to Object:
public class Prod implements Serializable {
private Integer id;
private String name;
private double price;
//private String brandName;
// getter and setter
}
It works fine with this query:
public interface ProductRepository extends JpaRepository<Product, Integer> {
#Query(value = "select new com.pechen.domain.Prod(p.id, p.name, p.price) from Product p ")
Page<Prod> pageForProd(Pageable pageRequest);
}
But if I add new property brandName for Prod and refactor the query with left join, it test fails:
#Query(value = "select new com.pechen.domain.Prod(p.id, p.name, p.price, b.name) from Product p left join com.pechen.domain.Brand b on p.brand_id = b.id")
Page<Prod> pageForProd(Pageable pageRequest);
The problem seems to be here on p.brand_id = b.id because there is not a brand_id property in Product, it's just a column name. So how can I make this work?
Update:
There turned to be some sytax errors in the JPQL query, just fix it as the following:
#Query(value = "select new com.pechen.domain.Prod(p.id, p.name, p.price, b.name) from Product p left join p.brand b")
Page<Prod> pageForProd(Pageable pageRequest);
Besides, it's very troublesome in this way to create another class everytime to map the query results into object(I mean the Prod class). So is there a good way to work with it? Any help would be appreciated.
Instead of p.brand_id = b.id you should do p.brand.id = b.id

Hibernate Exception - could not locate named parameter

i am trying to extract a list of objects from database from entity (table) StudySeries:
#Entity
#Table(name="StudySeries", uniqueConstraints = {
#UniqueConstraint(columnNames = "SeriesInstanceUID")})
public class StudySeries implements Serializable {
...
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "SeId", unique = true, nullable = false)
private Long seId;
#Column(name="SeriesInstanceUID", unique=true, nullable = false)
private String seriesInstanceUID;
...
#ManyToOne
#JoinColumn(name = "StId", referencedColumnName="StId")
private StudyDetails studyDetails;
...
}
This entity is N-1 joined with StudyDetails (on StudyDetails has many StudySeries):
#Entity
#Table(name="StudyDetails", uniqueConstraints = #UniqueConstraint(columnNames = "StudyInstanceUID"))
public class StudyDetails implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name="StId", unique = true, nullable = false)
private Long stId;
#Column(name="StudyInstanceUID", unique=true, nullable = false)
private String studyInstanceUID;
...
#OneToMany(fetch = FetchType.LAZY, mappedBy = "studyDetails", cascade = CascadeType.ALL)
private Set<StudySeries> studySeries = new HashSet<StudySeries>(0);
...
}
In my StudySeriesDAOImpl() i am trying to:
#Override
public List<StudySeries> getStudySeriesObjectsByStudyId(Long stId) {
List<StudySeries> results=new ArrayList<>();
Session s=HibernateUtil.openSession();
s.beginTransaction();
String hql = "from StudySeries E where E.studyDetails.stId = stId";
Query query = s.createQuery(hql);
query.setParameter("stId", stId);
results = query.list();
s.getTransaction().commit();
s.close();
log.info(">>>>> list size: " + results.size());
return results;
}
I have also tried the hql query as:
String hql = "from StudySeries E where E.stId = stId";
However i am getting:
org.hibernate.QueryParameterException: could not locate named parameter [stId]
at org.hibernate.engine.query.spi.ParameterMetadata.getNamedParameterDescriptor(ParameterMetadata.java:100) at org.hibernate.engine.query.spi.ParameterMetadata.getNamedParameterDescriptor(ParameterMetadata.java:100)
at org.hibernate.engine.query.spi.ParameterMetadata.getNamedParameterExpectedType(ParameterMetadata.java:106)
at org.hibernate.internal.AbstractQueryImpl.determineType(AbstractQueryImpl.java:466)
at org.hibernate.internal.AbstractQueryImpl.setParameter(AbstractQueryImpl.java:436)
at com.npap.dao.StudySeriesDAOImpl.getStudySeriesObjectsByStudyId(StudySeriesDAOImpl.java:239)
Any ideas what is wrong?
In the StudySeries class, the id is named as 'seId', not 'stId'.
You should do like this: String hql = "from StudySeries E where E.seId = stId";

JPA 2.0 CriteriaQuery on tables in #ManyToMany with JoinTable and Lazy Fetch

I have two entities in a #ManyToMany relationship like this:
#Entity
#Table(name = "USERS")
public class User implements EntityMetaModel, Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Integer id;
#Column(name = "USERNAME", unique = true, length = 20)
private String username;
#Column(name = "PASSWORD", nullable = false, length = 32)
private String password;
#Column(name = "ENABLED")
private Boolean enabled;
#ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = (FetchType.LAZY)
)
#JoinTable(
name = "USER_ROLES",
joinColumns = #JoinColumn(name="USERID", referencedColumnName="ID"),
inverseJoinColumns = #JoinColumn(name="ROLEID", referencedColumnName="ID")
)
#SequenceGenerator(
name = "sgIdUserRoles",
sequenceName = "SQ_ID_USER_ROLES"
)
#CollectionId(
columns = {#Column(name="ID")},
type = #Type(type="integer"),
generator = "sgIdUserRoles"
)
#Fetch(value = FetchMode.SELECT)
private Collection<Role> roles = new HashSet<Role>();
}
#Entity
#Table(name = "ROLES")
public class Role implements EntityMetaModel, Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Integer id;
#Column(name = "ROLENAME", unique = true, length = 50)
private String rolename;
#ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = (FetchType.LAZY)
)
#JoinTable(
name = "USER_ROLES",
joinColumns = #JoinColumn(name="ROLEID", referencedColumnName="ID"),
inverseJoinColumns = #JoinColumn(name="USERID", referencedColumnName="ID")
)
#SequenceGenerator(
name = "sgIdUserRoles",
sequenceName = "SQ_ID_USER_ROLES"
)
#CollectionId(
columns = {#Column(name="ID")},
type = #Type(type="integer"),
generator = "sgIdUserRoles"
)
#Fetch(value = FetchMode.SELECT)
private Collection<User> users = new HashSet<User>();
}
I've created the metamodel:
#StaticMetamodel(User.class)
public class User_ {
public static volatile SingularAttribute<User, Integer> id;
public static volatile SingularAttribute<User, String> username;
public static volatile SingularAttribute<User, String> password;
public static volatile SingularAttribute<User, Boolean> enabled;
public static volatile CollectionAttribute<User, Role> roles;
}
#StaticMetamodel(Role.class)
public class Role_ {
public static volatile SingularAttribute<Role, Integer> id;
public static volatile SingularAttribute<Role, String> rolename;
public static volatile CollectionAttribute<Role, User> users;
}
Class User has a Collection of Role. What I need to do from JPA2 CriteriaQuery is to find the Roles that havent an User given by id.
How can I do a CriteriaQuery like this?
select r.id, r.rolename
from roles r
where not exists
(select 1
from user_roles ur
where ur.roleid = r.id
and ur.userid = :userid)
If a query is:
List<Role> roles = session.createQuery("select u.roles from User u left join fetch u.roles where u.id= :idParam").
setParameter("idParam", param).
asList();
the following criteria should work, as I remember:
List<Role> roles = session.
createCriteria(Role.class).
add(Restrictions.eq("users.id", param)).
setFetchMode("users", FethchMode.EAGER).
list();
Otherwise, you return a query with a non-entity structure, you may need a ResultTransformer.

Categories