How to join multiple columns using Specification in JPA? - java

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

Related

How to write a spring boot jpa specification joining multiple tables

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));
}

Problem with saving data in ManyToMany tables, hibernate, spring

Below - parent table - users:
#Entity
#Table(name = "user")
public class User {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "username", length = 50, unique = true)
#NotNull
#Size(min = 4, max = 50)
private String username;
#Column(name = "password", length = 100)
#NotNull
#Size(min = 4, max = 100)
private String password;
#ManyToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
#JoinTable(
name = "user_role",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "role_id", referencedColumnName = "id")})
private List<Role> roles;
//constructor, getters/setters
Below You can find table with roles:
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", length = 50)
#NotNull
#Enumerated(EnumType.STRING)
private RoleName name;
#ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
private List<User> users;
//constructor, getters/setters
And I will try save User by belows method:
private static SessionFactory sessionFactory;
private Session session;
#Override
public void saveUser(User user) {
Role role = getRoleByName(RoleName.ROLE_ADMIN);
user.getRoles().add(role );
role.getUsers().add( user );
session.persist(user);
}
public Role getRoleByName(RoleName name) {
Query query = session.createQuery("SELECT r FROM Role r WHERE r.name= :name");
query.setParameter("name", name);
return (Role) query.uniqueResult();
}
But I have two problems:
- first - the main problem - I did something wrong and I can't save new user.
- second one - I have null result in quer result in getRoleByName, but of course, that role exists in table user_roles.
And any solution will be perfect for me.
Thank you in advance.

How to limit the max rows without Using Pageable for JPASpecificationsExecutor?

I am trying to understand and figure out the solution for the following use case
These are my entity classes
User
#Entity
#Table(name = "USER")
public class User {
private UserID id;
private Set<UserAddress> addresses = new HashSet<UserAddress>(0);
#EmbeddedId
#AttributeOverrides( {
#AttributeOverride(name = "userId", column = #Column(name = "USER_ID", nullable = false, length = 32)),
#AttributeOverride(name = "userType", column = #Column(name = "USER_TYPE", nullable = false, precision = 12, scale = 0)) })
public User getId() {
return this.id;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "user", cascade={CascadeType.ALL})
#BatchSize(size=50)
public Set<UserAddress> getAddresses() {
return this.addresses;
}
........
}
UserAddress
#Entity
#Table(name = "USERADDRESS")
public class UserAddress {
private UserID id;
Private User user;
private String address;
#EmbeddedId
#AttributeOverrides( {
#AttributeOverride(name = "userId", column = #Column(name = "USER_ID", nullable = false, length = 32)),
#AttributeOverride(name = "userType", column = #Column(name = "USER_TYPE", nullable = false, precision = 12, scale = 0)) })
public User getId() {
return this.id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns( {
#JoinColumn(name = "userId", referencedColumnName = "USER_ID", nullable = false, insertable=false, updatable=false),
#JoinColumn(name = "userType", referencedColumnName = "USER_TYPE", nullable = false, insertable=false, updatable=false) })
public User getUser() {
return this.user;
}
........
}
UserId
#Embeddable
public class UserId implements Serializable {
private String userNo;
private Long UserType;
.......
.......
}
I have created a staticmetamodel class for User, UserID and UserAddress and created query based on Specifications.
Metamodel class for User
#StaticMetamodel(User.class)
public abstract class User_ {
public static volatile SetAttribute<User, UserAddress> addresses;
public static volatile SingularAttribute<User, UserID> id;
}
Metamodel for UserId
#StaticMetamodel(UserID.class)
public abstract class UserID_ {
public static volatile SingularAttribute<UserID, String> userNo;
public static volatile SingularAttribute<UserID, Long> UserType;
}
I am trying to retrieve maximum of 10 User objects ordered by UserType and searched based on userId. The query has to retrieve the UserAddresses as an eager fetch.
My Specification Object is
UserSpecification
public class UserSpecifications {
public static Specification<User> userNoIs(String userNo) {
return (root, query, cb) -> {
root.fetch(User_.addresses);
return cb.equal(root.get(User_.id).get(UserID_.userNo),userNo);
};
}
}
DAO Function:
Sort sortInstructions = new Sort(Sort.Direction.DESC, "id.userNo");
Pageable pageInfo = new PageRequest(0, maxCount, sortInstructions);
Specifications<User> specifications = Specifications.where(userNoIs(input.getUserNo()));
Page<User> responseList= userRepository.findAll(specifications,pageInfo);
I am getting the following exception when I run the above statement.
Caused by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=com.entity.User.addresses,tableName=USERADDRESS ,tableAlias=useraddress1_,origin=USER user0,columns={user0.USER_TYPE user0.USER_ID ,className=com.entity.UserAddress}}]
But apart from that I also need to understand how to limit the no of rows returned using Specification but without using Pageable . If I use Pageable, then a separate query will be fired to retrieve the count of rows and then actual query is being fired. My application is performance oriented, and I do not want to have any extra queries being fired.
Is there any way where I can limit the no of rows without using Pageable, but using Specifications ?

Hibernate criteria cannot retrieve values of a join

I have following entities and need to retrieve a list of names of all stores that are in a specific group and have branches in a specific city. Majority of tutorials and articles that Ive found are related to creating this type of relationships but none of them is about retrieval!
I changed the criteria for many times but Hibernate shows different errors for each. The commented parts of the code are those that I tried and the respective thrown exception is also written in front of each.
Entities
#Entity
public class Store {
#Id
String id;
String name;
#JoinTable(name = "store_groups", joinColumns = { #JoinColumn(name = "id", nullable = false, updatable = false) }, inverseJoinColumns = { #JoinColumn(name = "code", nullable = false, updatable = false) })
private Set<Group> groups = new HashSet<Group>(0);
private Set<StoreAddress> storeAddresses = new HashSet<StoreAddress>(0);
....
}
#Entity
public class Group {
#Id
String code;
#Column(nullable = false, unique = true)
String name;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "groups")
Set<Store> storees = new HashSet<Store>(0);
}
#Entity
#Table(name = "StoreAddresses")
#AssociationOverrides({
#AssociationOverride(name = "muJoinTable.store", joinColumns = #JoinColumn(name = "id", nullable = false)),
#AssociationOverride(name = "myJoinTable.city", joinColumns = #JoinColumn(name = "cityCode", nullable = false)) })
public class StoreAddress {
#EmbeddedId
private StoreCitysId myJoinTable = new StoreCitysId();
...
}
#Embeddable
public class StoreCitysId {
#ManyToOne
private Store store;
#ManyToOne
private City city;
}
#Entity
public class City {
#Id
short code;
#Column(nullable = false)
String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "myJoinTable.city")
private Set<StoreAddress> storeAddresses = new HashSet<StoreAddress>(
0);
}
Criteria
List<String> storees = (List<String>) sessionFactory
.getCurrentSession()
.createCriteria(Store.class)
.setProjection(
Projections.property("name").as(
"storeName"))
.createAlias("groups", "group")
.createAlias("storeAddresses", "address")
// .createAlias("address.myJoinTable.city", "city")
// .createAlias("address.myJoinTable", "myJoinTable")
// .createAlias("myJoinTable.city", "city") Error: Criteria objects cannot be created directly on components
.setFetchMode("group", FetchMode.JOIN)
.add(Restrictions.ilike("group.code", store))
.add(Restrictions.eq("address.myJoinTable.cityCode",
1)).list(); //city.code -> Error: could not resolve property: cityCode of:com.example.entity.StoreAddress address.myJoinTable.cityCode could not resolve property: myJoinTable.cityCode of:com.example.entity.StoreAddress
Your criterion Restrictions.eq("address.myJoinTable.cityCode", 1) doesn't reference a property but the name of the column. You could instead use address.myJoinTable.city and set the value to session.load(City.class, 1) making Restrictions.eq("address.myJoinTable.city", session.load(City.class, 1))
And this:
.createAlias("address.myJoinTable", "myJoinTable")
.createAlias("myJoinTable.city", "city")
Should be:
.createAlias("address.myJoinTable.city", "city")

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